2 Commits

Author SHA1 Message Date
Jerry 2edf8fb4c8 feat: strengthen CAD experience distillation and routing 2026-07-23 20:03:26 +08:00
Jerry a2f3ba6a90 feat: add per-part CAD model specs 2026-07-23 15:12:28 +08:00
96 changed files with 17344 additions and 116 deletions
+10
View File
@@ -37,3 +37,13 @@ cad-experience-plugin/parser/input/*
!cad-experience-plugin/parser/input/.gitkeep
cad-experience-plugin/parser/output/*
!cad-experience-plugin/parser/output/.gitkeep
# CAD experience benchmark scratch geometry and private case evidence
cad-experience-plugin/benchmarks/**/generated/
cad-experience-plugin/benchmarks/**/quarantine-*/
cad-experience-plugin/benchmarks/**/private-case.json
cad-experience-plugin/benchmarks/**/source-inspect.json
cad-experience-plugin/benchmarks/**/train-semantic-batch.json
cad-experience-plugin/benchmarks/**/train_cases/
cad-experience-plugin/benchmarks/**/test_cases/
cad-experience-plugin/benchmarks/**/cases/
File diff suppressed because it is too large Load Diff
@@ -1,15 +1,15 @@
{
"name": "cad-experience-plugin",
"version": "0.1.0",
"description": "Parse STEP inboxes and use the active model plus deterministic evidence checks to distill backend-neutral CAD experience.",
"version": "0.1.0+codex.20260723115752",
"description": "Parse STEP inboxes and use the active model plus deterministic evidence checks to distill backend-neutral CAD reconstruction grammar and experience.",
"author": {
"name": "Local developer"
},
"skills": "./skills/",
"interface": {
"displayName": "CAD Experience",
"shortDescription": "Use model review to distill reusable CAD methods.",
"longDescription": "CAD Experience maintains a visible STEP input/output staging area, gives the active large model a sanitized semantic review packet, verifies every proposed method against the accumulated corpus, and promotes only repeatedly supported backend-neutral experience.",
"shortDescription": "Distill reusable CAD reconstruction grammar.",
"longDescription": "CAD Experience maintains a visible STEP input/output staging area, gives the active large model sanitized symbolic parameters, datums, canonical stages, constraints, and validation evidence, verifies every proposal against the accumulated corpus, and promotes only repeatedly supported backend-neutral reconstruction grammar and experience.",
"developerName": "Local developer",
"category": "Productivity",
"capabilities": [
@@ -18,9 +18,10 @@
"Batch CAD experience induction",
"Large-model semantic review",
"Deterministic proposal evidence verification",
"Family-level symbolic reconstruction grammar",
"Generalized experience queries",
"Instance-data leakage audits"
],
"defaultPrompt": "Parse the STEP input folder, distill the output JSON, and report promoted generalized CAD experience without exposing source-part geometry."
"defaultPrompt": "Parse the STEP input folder, distill symbolic family-level reconstruction grammar from the output JSON, and report promoted generalized CAD experience without exposing source-part geometry."
}
}
@@ -0,0 +1,100 @@
[
{
"id": "136930",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/136930.step\"}",
"error": ""
},
{
"id": "138022",
"variant": "baseline",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "138022",
"variant": "experience",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "136930",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/136930.step\"}",
"error": ""
},
{
"id": "134828",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/134828.step\"}",
"error": ""
},
{
"id": "116166",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/116166.step\"}",
"error": ""
},
{
"id": "134828",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/134828.step\"}",
"error": ""
},
{
"id": "145574",
"variant": "baseline",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "145574",
"variant": "experience",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "150196",
"variant": "baseline",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "150196",
"variant": "experience",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "150587",
"variant": "experience",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "150587",
"variant": "baseline",
"returncode": -9,
"output": "",
"error": ""
},
{
"id": "116166",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/116166.step\"}",
"error": ""
}
]
@@ -0,0 +1,212 @@
[
{
"id": "000166",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"skipped\", \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/000166.step\"}",
"error": ""
},
{
"id": "011680",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"skipped\", \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/011680.step\"}",
"error": ""
},
{
"id": "000166",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/000166.step\"}",
"error": ""
},
{
"id": "011680",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/011680.step\"}",
"error": ""
},
{
"id": "016033",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/016033.step\"}",
"error": ""
},
{
"id": "016033",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/016033.step\"}",
"error": ""
},
{
"id": "004764",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/004764.step\"}",
"error": ""
},
{
"id": "020543",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/020543.step\"}",
"error": ""
},
{
"id": "024675",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/024675.step\"}",
"error": ""
},
{
"id": "004764",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/004764.step\"}",
"error": ""
},
{
"id": "029190",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/029190.step\"}",
"error": ""
},
{
"id": "024675",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/024675.step\"}",
"error": ""
},
{
"id": "020543",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/020543.step\"}",
"error": ""
},
{
"id": "029190",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/029190.step\"}",
"error": ""
},
{
"id": "030352",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/030352.step\"}",
"error": ""
},
{
"id": "046014",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/046014.step\"}",
"error": ""
},
{
"id": "046014",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/046014.step\"}",
"error": ""
},
{
"id": "030352",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/030352.step\"}",
"error": ""
},
{
"id": "047048",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/047048.step\"}",
"error": ""
},
{
"id": "048346",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/048346.step\"}",
"error": ""
},
{
"id": "048346",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/048346.step\"}",
"error": ""
},
{
"id": "047048",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/047048.step\"}",
"error": ""
},
{
"id": "053664",
"variant": "experience",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/053664.step\"}",
"error": ""
},
{
"id": "065487",
"variant": "experience",
"returncode": 1,
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/065487.step\"}",
"error": ""
},
{
"id": "067186",
"variant": "baseline",
"returncode": 1,
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/067186.step\"}",
"error": ""
},
{
"id": "053664",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/053664.step\"}",
"error": ""
},
{
"id": "067186",
"variant": "experience",
"returncode": 1,
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/067186.step\"}",
"error": ""
},
{
"id": "073706",
"variant": "baseline",
"returncode": 1,
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/073706.step\"}",
"error": ""
},
{
"id": "073706",
"variant": "experience",
"returncode": 1,
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/073706.step\"}",
"error": ""
},
{
"id": "065487",
"variant": "baseline",
"returncode": 0,
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/065487.step\"}",
"error": ""
}
]
@@ -0,0 +1,28 @@
{
"schema_version": "2.0",
"library_kind": "generalized_cad_experience",
"generated_at": "2026-07-23T09:46:25+00:00",
"status": "empty",
"policy": {
"instance_parameters_allowed": false,
"absolute_coordinates_allowed": false,
"single_case_promotion_allowed": false,
"minimum_support": 20,
"minimum_confidence": 0.8
},
"corpus_summary": {
"accepted_case_count": 0,
"duplicate_case_count": 0,
"rejected_case_count": 0,
"family_count": 0
},
"experience_summary": {
"promoted_experience_count": 0,
"candidate_experience_count": 0,
"by_kind": [],
"by_scope": [],
"candidate_by_kind": []
},
"experiences": [],
"candidate_experiences": []
}
@@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""Evaluate blind baseline/experience STEP reconstructions against held-out STEP."""
from __future__ import annotations
import argparse
import csv
import importlib.util
import itertools
import json
import math
from pathlib import Path
from typing import Any
import numpy as np
from build123d import import_step
from scipy.spatial import cKDTree
HERE = Path(__file__).resolve().parent
EXTRACTOR = (
HERE.parents[1]
/ "skills/cad-experience-builder/scripts/step_to_case.py"
)
def load_extractor():
spec = importlib.util.spec_from_file_location("ab50_step_to_case", EXTRACTOR)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module
EXTRACT = load_extractor()
def rel_error(actual: float, expected: float) -> float:
return abs(actual - expected) / max(abs(expected), 1e-9)
def feature_names(payload: dict[str, Any]) -> set[str]:
result: set[str] = set()
for item in payload.get("design_ir", {}).get("features", []):
if isinstance(item, str):
result.add(item)
elif isinstance(item, dict):
name = item.get("kind") or item.get("type") or item.get("name")
if name:
result.add(str(name))
return result
def f1_score(left: set[str], right: set[str]) -> float:
if not left and not right:
return 1.0
if not left or not right:
return 0.0
overlap = len(left & right)
precision = overlap / len(right)
recall = overlap / len(left)
return 2 * precision * recall / max(precision + recall, 1e-9)
def mesh(shape, tolerance: float = 0.35) -> tuple[np.ndarray, np.ndarray]:
vertices, triangles = shape.tessellate(tolerance)
points = np.asarray([[v.X, v.Y, v.Z] for v in vertices], dtype=float)
faces = np.asarray(triangles, dtype=int)
return points, faces
def sample_surface(
vertices: np.ndarray, faces: np.ndarray, count: int, seed: int
) -> np.ndarray:
tri = vertices[faces]
cross = np.cross(tri[:, 1] - tri[:, 0], tri[:, 2] - tri[:, 0])
areas = np.linalg.norm(cross, axis=1) / 2.0
total = float(areas.sum())
if total <= 1e-12:
return vertices.copy()
rng = np.random.default_rng(seed)
chosen = rng.choice(len(faces), size=count, p=areas / total)
selected = tri[chosen]
u = rng.random(count)
v = rng.random(count)
reflected = u + v > 1.0
u[reflected] = 1.0 - u[reflected]
v[reflected] = 1.0 - v[reflected]
return (
selected[:, 0]
+ u[:, None] * (selected[:, 1] - selected[:, 0])
+ v[:, None] * (selected[:, 2] - selected[:, 0])
)
def cube_transforms() -> list[np.ndarray]:
transforms = []
for permutation in itertools.permutations(range(3)):
base = np.eye(3)[:, permutation]
for signs in itertools.product((-1.0, 1.0), repeat=3):
transforms.append(base @ np.diag(signs))
return transforms
TRANSFORMS = cube_transforms()
def surface_distance(
reference, generated, seed: int, samples: int
) -> tuple[float, float]:
ref_v, ref_f = mesh(reference)
gen_v, gen_f = mesh(generated)
ref = sample_surface(ref_v, ref_f, samples, seed)
gen = sample_surface(gen_v, gen_f, samples, seed + 1)
ref -= (ref.min(axis=0) + ref.max(axis=0)) / 2.0
gen -= (gen.min(axis=0) + gen.max(axis=0)) / 2.0
ref_tree = cKDTree(ref)
best_chamfer = math.inf
best_hausdorff = math.inf
for transform in TRANSFORMS:
candidate = gen @ transform
gen_tree = cKDTree(candidate)
d_gr = ref_tree.query(candidate, workers=-1)[0]
d_rg = gen_tree.query(ref, workers=-1)[0]
chamfer = float((d_gr.mean() + d_rg.mean()) / 2.0)
hausdorff = float(max(np.quantile(d_gr, 0.95), np.quantile(d_rg, 0.95)))
if chamfer < best_chamfer:
best_chamfer = chamfer
best_hausdorff = hausdorff
diagonal = max(float(np.linalg.norm(np.ptp(ref, axis=0))), 1e-9)
return best_chamfer / diagonal, best_hausdorff / diagonal
def task_experience(step_path: Path) -> tuple[int, int]:
task = step_path.parent / "cad-task.json"
if not task.is_file():
return 0, 0
payload = json.loads(task.read_text())
exp = payload.get("experience", {})
returned = exp.get("methods_returned")
if returned is None:
returned = len(exp.get("methods", exp.get("methods_applied", [])))
if isinstance(returned, list):
returned = len(returned)
return int(returned or 0), len(exp.get("methods_applied", []))
def evaluate_one(
case: dict[str, Any], variant: str, samples: int
) -> dict[str, Any]:
generated_path = Path(case[f"{variant}_step"])
row: dict[str, Any] = {
"id": case["id"],
"family": case["family"],
"variant": variant,
"generated": generated_path.is_file(),
}
if not generated_path.is_file():
row["error"] = "missing STEP"
return row
try:
reference = import_step(case["reference_step"])
generated = import_step(str(generated_path))
ref_case = json.loads(Path(case["case_json"]).read_text())
gen_case = EXTRACT.extract_step_case(generated_path)
ref_bbox = sorted(float(v) for v in reference.bounding_box().size)
gen_bbox = sorted(float(v) for v in generated.bounding_box().size)
bbox_error = float(
np.mean([rel_error(a, b) for a, b in zip(gen_bbox, ref_bbox)])
)
volume_error = rel_error(float(generated.volume), float(reference.volume))
area_error = rel_error(float(generated.area), float(reference.area))
face_error = rel_error(len(generated.faces()), len(reference.faces()))
edge_error = rel_error(len(generated.edges()), len(reference.edges()))
chamfer, hausdorff95 = surface_distance(
reference, generated, int(case["id"]), samples
)
feature_f1 = f1_score(feature_names(ref_case), feature_names(gen_case))
family_match = float(
ref_case["design_ir"]["part_family"]
== gen_case["design_ir"]["part_family"]
)
returned, applied = task_experience(generated_path)
# Transparent 0100 similarity index. It is a benchmark summary, not a
# claim of manufacturing interchangeability or exact B-rep identity.
score = 100.0 * (
0.18 * math.exp(-volume_error)
+ 0.12 * math.exp(-area_error)
+ 0.16 * math.exp(-bbox_error)
+ 0.24 * math.exp(-8.0 * chamfer)
+ 0.10 * math.exp(-(face_error + edge_error) / 2.0)
+ 0.15 * feature_f1
+ 0.05 * family_match
)
row.update(
{
"valid": bool(generated.is_valid),
"solid_count": len(generated.solids()),
"reference_volume": float(reference.volume),
"generated_volume": float(generated.volume),
"volume_rel_error": volume_error,
"surface_area_rel_error": area_error,
"bbox_mape": bbox_error,
"face_rel_error": face_error,
"edge_rel_error": edge_error,
"surface_chamfer_norm": chamfer,
"surface_hausdorff95_norm": hausdorff95,
"feature_f1": feature_f1,
"family_match": family_match,
"experience_methods_returned": returned,
"experience_methods_applied": applied,
"similarity_score": score,
}
)
except Exception as exc:
row["error"] = f"{type(exc).__name__}: {exc}"
return row
def mean(rows: list[dict[str, Any]], key: str) -> float:
values = [float(row[key]) for row in rows if key in row]
return sum(values) / len(values) if values else math.nan
def write_report(rows: list[dict[str, Any]]) -> None:
complete = [row for row in rows if "similarity_score" in row]
baseline = [row for row in complete if row["variant"] == "baseline"]
experience = [row for row in complete if row["variant"] == "experience"]
by_id = {}
for row in complete:
by_id.setdefault(row["id"], {})[row["variant"]] = row
pairs = [
item for item in by_id.values() if {"baseline", "experience"} <= item.keys()
]
paired_baseline = [item["baseline"] for item in pairs]
paired_experience = [item["experience"] for item in pairs]
deltas = [
item["experience"]["similarity_score"]
- item["baseline"]["similarity_score"]
for item in pairs
]
wins = sum(delta > 0.25 for delta in deltas)
ties = sum(abs(delta) <= 0.25 for delta in deltas)
losses = sum(delta < -0.25 for delta in deltas)
methods_returned = sum(
row.get("experience_methods_returned", 0) for row in experience
)
methods_applied = sum(
row.get("experience_methods_applied", 0) for row in experience
)
report = f"""# CAD 经验库 50 件 A/B 盲重建报告
## 口径
- 测试集:固定分层抽取 50 件;这 50 件不进入经验库。
- 训练经验:其余 950 件生成的通用经验库。
- 两组输入完全相同:仅多视图图片和文字描述;生成过程禁止读取参考 STEP 和解析 JSON。
- Baseline:空经验库;Experience950 件经验库。
- 参考 STEP 只在生成完成后用于评分。
- “相似度”是体积、面积、包围盒、表面距离、拓扑和语义特征的综合诊断分,不代表制造级 1:1 或可互换性。
## 当前完成度
- 有效 Baseline{len(baseline)}/50
- 有效 Experience{len(experience)}/50
- 完整配对:{len(pairs)}/50
- 经验方法返回/实际采用:{methods_returned}/{methods_applied}
## 汇总
| 指标 | 无经验 | 有经验 | 差值 |
|---|---:|---:|---:|
| 综合相似度(0–100,越高越好) | {mean(paired_baseline, "similarity_score"):.2f} | {mean(paired_experience, "similarity_score"):.2f} | {mean(paired_experience, "similarity_score") - mean(paired_baseline, "similarity_score"):+.2f} |
| 体积相对误差(越低越好) | {mean(paired_baseline, "volume_rel_error"):.3f} | {mean(paired_experience, "volume_rel_error"):.3f} | {mean(paired_experience, "volume_rel_error") - mean(paired_baseline, "volume_rel_error"):+.3f} |
| 表面积相对误差 | {mean(paired_baseline, "surface_area_rel_error"):.3f} | {mean(paired_experience, "surface_area_rel_error"):.3f} | {mean(paired_experience, "surface_area_rel_error") - mean(paired_baseline, "surface_area_rel_error"):+.3f} |
| 包围盒尺寸 MAPE | {mean(paired_baseline, "bbox_mape"):.3f} | {mean(paired_experience, "bbox_mape"):.3f} | {mean(paired_experience, "bbox_mape") - mean(paired_baseline, "bbox_mape"):+.3f} |
| 归一化表面 Chamfer | {mean(paired_baseline, "surface_chamfer_norm"):.4f} | {mean(paired_experience, "surface_chamfer_norm"):.4f} | {mean(paired_experience, "surface_chamfer_norm") - mean(paired_baseline, "surface_chamfer_norm"):+.4f} |
| 特征 F1 | {mean(paired_baseline, "feature_f1"):.3f} | {mean(paired_experience, "feature_f1"):.3f} | {mean(paired_experience, "feature_f1") - mean(paired_baseline, "feature_f1"):+.3f} |
经验组胜/平/负(±0.25 分视为平):**{wins}/{ties}/{losses}**。
"""
(HERE / "report.md").write_text(report)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--samples", type=int, default=1600)
args = parser.parse_args()
manifest = json.loads((HERE / "manifest.json").read_text())
rows = [
evaluate_one(case, variant, args.samples)
for case in manifest["cases"]
for variant in ("baseline", "experience")
]
(HERE / "metrics.json").write_text(
json.dumps(rows, indent=2, ensure_ascii=False) + "\n"
)
keys = sorted({key for row in rows for key in row})
with (HERE / "metrics.csv").open("w", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=keys)
writer.writeheader()
writer.writerows(rows)
write_report(rows)
completed = sum("similarity_score" in row for row in rows)
print(json.dumps({"evaluated": completed, "expected": 100}))
if __name__ == "__main__":
main()
@@ -0,0 +1,708 @@
{
"schema_version": "1.0",
"selection_seed": 20260723,
"holdout_count": 50,
"train_count": 950,
"cases": [
{
"id": "000166",
"family": "general_mechanical_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/000166.3fe709d953c5.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/000166.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/000166.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/000166.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/000166.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/000166.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/final.txt"
},
{
"id": "004764",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/004764.6e3a06d88adc.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/004764.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/004764.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/004764.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/004764.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/004764.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/final.txt"
},
{
"id": "011680",
"family": "plate_or_bracket",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/011680.77a85afd0f4b.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/011680.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/011680.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/011680.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/011680.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/011680.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/final.txt"
},
{
"id": "016033",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/016033.500e53f944a6.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/016033.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/016033.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/016033.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/016033.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/016033.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/final.txt"
},
{
"id": "020543",
"family": "flanged_rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/020543.0ad8f7e7459d.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/020543.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/020543.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/020543.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/020543.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/020543.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/final.txt"
},
{
"id": "024675",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/024675.2d1f84f175f5.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/024675.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/024675.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/024675.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/024675.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/024675.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/final.txt"
},
{
"id": "029190",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/029190.825d4001dea3.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/029190.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/029190.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/029190.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/029190.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/029190.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/final.txt"
},
{
"id": "030352",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/030352.45db9222299d.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/030352.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/030352.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/030352.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/030352.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/030352.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/final.txt"
},
{
"id": "046014",
"family": "flanged_rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/046014.4abae0d1a5a1.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/046014.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/046014.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/046014.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/046014.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/046014.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/final.txt"
},
{
"id": "047048",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/047048.b5119254bc4b.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/047048.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/047048.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/047048.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/047048.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/047048.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/final.txt"
},
{
"id": "048346",
"family": "hollow_or_stepped_shaft",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/048346.ba3e1b245d0e.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/048346.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/048346.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/048346.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/048346.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/048346.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/final.txt"
},
{
"id": "053664",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/053664.90a0635aadaf.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/053664.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/053664.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/053664.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/053664.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/053664.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/final.txt"
},
{
"id": "063487",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/063487.b524dcfc1124.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/063487.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/063487.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/063487.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/063487.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/063487.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/final.txt"
},
{
"id": "065487",
"family": "flanged_rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/065487.444ad775ec5c.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/065487.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/065487.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/065487.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/065487.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/065487.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/final.txt"
},
{
"id": "067186",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/067186.0fe747589ef9.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/067186.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/067186.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/067186.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/067186.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/067186.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/final.txt"
},
{
"id": "073706",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/073706.9727acff0f04.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/073706.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/073706.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/073706.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/073706.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/073706.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/final.txt"
},
{
"id": "077955",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/077955.499f373b1c19.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/077955.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/077955.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/077955.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/077955.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/077955.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/final.txt"
},
{
"id": "082187",
"family": "prismatic_block",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/082187.2cb2db4976cc.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/082187.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/082187.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/082187.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/082187.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/082187.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/final.txt"
},
{
"id": "089970",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/089970.fcac7c13cac8.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/089970.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/089970.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/089970.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/089970.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/089970.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/final.txt"
},
{
"id": "092092",
"family": "shaft",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/092092.878532374151.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/092092.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/092092.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/092092.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/092092.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/092092.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/final.txt"
},
{
"id": "095520",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/095520.f77225b8bb80.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/095520.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/095520.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/095520.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/095520.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/095520.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/final.txt"
},
{
"id": "102128",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/102128.5226541130cc.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/102128.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/102128.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/102128.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/102128.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/102128.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/final.txt"
},
{
"id": "104465",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/104465.367ecc4c0d1f.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/104465.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/104465.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/104465.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/104465.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/104465.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/final.txt"
},
{
"id": "110143",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/110143.d0705ec5cf9b.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/110143.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/110143.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/110143.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/110143.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/110143.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/final.txt"
},
{
"id": "112069",
"family": "prismatic_block",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/112069.527e20a67202.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/112069.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/112069.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/112069.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/112069.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/112069.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/final.txt"
},
{
"id": "116166",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/116166.daa7c0fb7095.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/116166.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/116166.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/116166.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/116166.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/116166.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/final.txt"
},
{
"id": "134828",
"family": "multi_axis_manifold",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/134828.4b43ff12062c.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/134828.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/134828.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/134828.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/134828.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/134828.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/final.txt"
},
{
"id": "136930",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/136930.84481239875b.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/136930.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/136930.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/136930.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/136930.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/136930.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/final.txt"
},
{
"id": "138022",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/138022.144778123756.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/138022.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/138022.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/138022.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/138022.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/138022.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/final.txt"
},
{
"id": "141680",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/141680.6f110692a1c2.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/141680.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/141680.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/141680.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/141680.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/141680.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/final.txt"
},
{
"id": "145574",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/145574.861af237e8e8.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/145574.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/145574.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/145574.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/145574.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/145574.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/final.txt"
},
{
"id": "150196",
"family": "prismatic_block",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/150196.d357a5b35875.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/150196.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/150196.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/150196.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/150196.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/150196.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/final.txt"
},
{
"id": "150587",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/150587.ff4fa06ecb48.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/150587.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/150587.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/150587.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/150587.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/150587.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/final.txt"
},
{
"id": "152568",
"family": "multi_axis_manifold",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/152568.92e33e9a9248.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/152568.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/152568.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/152568.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/152568.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/152568.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/final.txt"
},
{
"id": "153245",
"family": "plate_or_bracket",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/153245.0603ada945fa.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/153245.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/153245.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/153245.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/153245.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/153245.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/final.txt"
},
{
"id": "158256",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/158256.a27578bb9a47.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/158256.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/158256.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/158256.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/158256.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/158256.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/final.txt"
},
{
"id": "162558",
"family": "hollow_or_stepped_shaft",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/162558.27dadf557afe.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/162558.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/162558.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/162558.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/162558.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/162558.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/final.txt"
},
{
"id": "167962",
"family": "multi_axis_manifold",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/167962.1306cad65795.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/167962.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/167962.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/167962.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/167962.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/167962.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/final.txt"
},
{
"id": "168239",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/168239.b54b54a2287f.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/168239.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/168239.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/168239.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/168239.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/168239.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/final.txt"
},
{
"id": "174675",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/174675.25863f859f2b.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/174675.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/174675.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/174675.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/174675.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/174675.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/final.txt"
},
{
"id": "183151",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/183151.ceee2bf361bc.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/183151.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/183151.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/183151.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/183151.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/183151.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/final.txt"
},
{
"id": "189669",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/189669.66b0db2ea3d6.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/189669.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/189669.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/189669.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/189669.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/189669.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/final.txt"
},
{
"id": "194029",
"family": "rotational_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/194029.316b264a1a88.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/194029.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/194029.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/194029.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/194029.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/194029.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/final.txt"
},
{
"id": "201547",
"family": "prismatic_block",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/201547.c7cc9318deda.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/201547.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/201547.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/201547.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/201547.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/201547.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/final.txt"
},
{
"id": "204730",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/204730.1fade6b3bb74.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/204730.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/204730.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/204730.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/204730.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/204730.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/final.txt"
},
{
"id": "211326",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/211326.a4e6d3a4590a.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/211326.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/211326.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/211326.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/211326.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/211326.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/final.txt"
},
{
"id": "214404",
"family": "patterned_plate",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/214404.da6a44a7013c.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/214404.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/214404.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/214404.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/214404.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/214404.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/final.txt"
},
{
"id": "229115",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/229115.c4c1680e53b5.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/229115.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/229115.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/229115.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/229115.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/229115.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/final.txt"
},
{
"id": "234097",
"family": "hybrid_freeform_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/234097.cea424081999.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/234097.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/234097.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/234097.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/234097.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/234097.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/final.txt"
},
{
"id": "242307",
"family": "patterned_prismatic_part",
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/242307.cd1b3d043967.case.json",
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/242307.png",
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/242307.txt",
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/242307.step",
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/242307.step",
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/codex.jsonl",
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/final.txt",
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/242307.step",
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/codex.jsonl",
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/final.txt"
}
]
}
@@ -0,0 +1,101 @@
bbox_mape,edge_rel_error,error,experience_methods_applied,experience_methods_returned,face_rel_error,family,family_match,feature_f1,generated,generated_volume,id,reference_volume,similarity_score,solid_count,surface_area_rel_error,surface_chamfer_norm,surface_hausdorff95_norm,valid,variant,volume_rel_error
0.0,0.5,,0,0,0.25,general_mechanical_part,1.0,0.8571428571428571,True,125663.70614359173,000166,125663.70614359173,93.81711632834559,1,6.425513440379324e-14,0.004847580881730266,0.00977766538694562,True,baseline,0.0
0.0,0.5,,0,0,0.25,general_mechanical_part,1.0,0.8571428571428571,True,125663.70614359173,000166,125663.70614359173,93.81711632834559,1,6.425513440379324e-14,0.004847580881730266,0.00977766538694562,True,experience,0.0
0.45066264681866697,0.125,,0,0,0.1111111111111111,patterned_plate,0.0,0.8571428571428571,True,106129.48703937844,004764,36749.40939347248,54.33835930595081,1,1.0309687869495758,0.05550481239248205,0.1775660613682025,True,baseline,1.8879236099568286
0.3413730293323282,0.125,,3,18,0.1111111111111111,patterned_plate,0.0,0.8571428571428571,True,88774.08881569223,004764,36749.40939347248,59.488074232257325,1,0.7876989178243013,0.0465090322155671,0.15562347480527094,True,experience,1.4156602862700836
0.5696649029982362,0.0,,0,0,0.0,plate_or_bracket,1.0,1.0,True,19016.0,011680,898496.0,60.445468751589296,1,0.9780420050604243,0.10796723414266624,0.36481967349123434,True,baseline,0.9788357432865589
0.583774250440917,0.0,,4,10,0.0,plate_or_bracket,1.0,1.0,True,13547.999999999998,011680,898496.0,59.90070139134242,1,0.9842171382062035,0.11235674854244815,0.374964117147598,True,experience,0.9849214687655816
0.18571427761876255,0.2631578947368421,,0,0,0.26666666666666666,patterned_plate,1.0,1.0,True,23882.59512641282,016033,44618.37587532573,81.50778192907364,1,0.34116223818430896,0.018452946285241585,0.08713798240651745,True,baseline,0.4647363410728704
0.3238095238095235,0.02631578947368421,,3,10,0.06666666666666667,patterned_plate,1.0,0.9411764705882353,True,13866.369040708962,016033,44618.37587532573,75.91174289423121,1,0.5000404499582691,0.026851094204940862,0.12169100582216402,True,experience,0.6892229094251465
0.5959595959595959,0.2857142857142857,,0,0,0.28125,flanged_rotational_part,1.0,1.0,True,118756.98485692723,020543,24145.808587596228,51.56160371886595,1,1.5105685440071281,0.08451218347517844,0.22747541674825858,True,baseline,3.9183271053483395
0.23232323232323235,0.05194805194805195,,0,0,0.1875,flanged_rotational_part,1.0,0.888888888888889,True,55638.90609255368,020543,24145.808587596228,69.33575293745653,1,0.4640684949891876,0.042969081471212464,0.11104259063963073,True,experience,1.3042883774509648
0.06666666666666685,0.2,,0,0,0.19480519480519481,patterned_prismatic_part,1.0,1.0,True,569438.4483554161,024675,580617.6091315141,91.92371160771847,1,0.2739018704584683,0.011073315847120254,0.07352156562273186,True,baseline,0.019253912730652068
0.5777777777777777,0.8933333333333333,,0,0,1.4025974025974026,patterned_prismatic_part,1.0,0.9333333333333333,True,38876.03946418978,024675,580617.6091315141,56.84188202691976,1,0.8746474563374864,0.07094038930770122,0.33837892456299423,True,experience,0.9330436437807312
0.0980952380952385,0.16666666666666666,,0,0,0.14285714285714285,patterned_plate,1.0,0.9333333333333333,True,73807.58425433026,029190,131254.75510206894,83.74861954252185,1,0.1560183040026604,0.024101504873521428,0.04232654923555514,True,baseline,0.4376768735202429
0.14047619047618823,1.5277777777777777,,0,0,1.6428571428571428,patterned_plate,1.0,0.7999999999999999,True,71846.37021205797,029190,131254.75510206894,73.91053356207678,1,0.20263501345527155,0.02460078307658505,0.04143094591291666,True,experience,0.45261891535901033
0.31241830065359194,0.9285714285714286,,0,0,0.5625,patterned_plate,1.0,0.923076923076923,True,34352.594678913054,030352,60881.15044407846,73.94372815488485,1,0.48742381144539443,0.02510282770499014,0.10902462973941958,True,baseline,0.4357433388111292
0.4306604575163398,2.642857142857143,,3,10,2.0625,patterned_plate,1.0,0.7692307692307692,True,11173.345159683424,030352,60881.15044407846,59.45331217188532,1,0.6732862799472543,0.03957952772551367,0.16850566658178062,True,experience,0.8164728314398964
0.21212121212121218,0.5,,0,0,0.43243243243243246,flanged_rotational_part,1.0,0.9600000000000001,True,73578.89519968569,046014,162271.3445381513,75.92217396437314,1,0.333964798541362,0.033942930350360764,0.05126001418573268,True,baseline,0.5465687709120651
0.13636363636363644,0.5,,4,10,0.43243243243243246,flanged_rotational_part,1.0,0.9600000000000001,True,111532.3028776836,046014,162271.3445381513,81.61765078194381,1,0.24200901291799348,0.02662187229047825,0.043202965297600246,True,experience,0.31268023202050027
0.6851851851851847,0.3709677419354839,,0,0,0.34782608695652173,patterned_prismatic_part,1.0,0.823529411764706,True,167251.53322574112,047048,33461.296317388085,44.79000279500974,1,1.6260155362793598,0.11322328439048983,0.2789388077989153,True,baseline,3.9983578531842254
0.7175925925925921,0.1693548387096774,,3,18,0.2391304347826087,patterned_prismatic_part,1.0,0.823529411764706,True,169277.67589913897,047048,33461.296317388085,44.38628038601891,1,1.587008333438104,0.13259494837120114,0.321530970786483,True,experience,4.058909681606514
1.4575235605335388e-16,0.5,,0,0,0.3125,hollow_or_stepped_shaft,1.0,0.9166666666666666,True,8278002.394429736,048346,8341713.8934443,93.45850460439323,1,0.01545893719801078,0.008802226092164953,0.016975575422477428,True,baseline,0.007637698898380382
1.4575235605335388e-16,0.5,,0,0,0.3125,hollow_or_stepped_shaft,1.0,0.9166666666666666,True,8292610.800268921,048346,8341713.8934443,93.51807327218812,1,0.012238325281746575,0.008857323306102671,0.017730676062993916,True,experience,0.005886451369899948
0.02857142857142833,0.2222222222222222,,0,0,0.21212121212121213,patterned_plate,0.0,0.8571428571428571,True,315117.2806010815,053664,349435.5647059466,85.57701767939263,1,0.07576465816363272,0.012696566244914555,0.02434484580541925,True,baseline,0.0982106218459597
0.07936507936507906,0.0,,0,0,0.030303030303030304,patterned_plate,0.0,0.8571428571428571,True,282776.14120936824,053664,349435.5647059466,83.57268406993335,1,0.167398697885095,0.016320456106002173,0.05162627100789126,True,experience,0.19076313412080106
,,missing STEP,,,,patterned_prismatic_part,,,False,,063487,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,063487,,,,,,,,experience,
0.05572210294349599,0.420863309352518,,0,0,0.4396551724137931,flanged_rotational_part,1.0,0.923076923076923,True,68680.44493144708,065487,81354.16556588435,87.21779338748289,1,0.060356551334099576,0.022572421105818447,0.053770414665160235,True,baseline,0.15578453231351141
,,missing STEP,,,,flanged_rotational_part,,,False,,065487,,,,,,,,experience,
,,missing STEP,,,,patterned_plate,,,False,,067186,,,,,,,,baseline,
,,missing STEP,,,,patterned_plate,,,False,,067186,,,,,,,,experience,
,,missing STEP,,,,rotational_part,,,False,,073706,,,,,,,,baseline,
,,missing STEP,,,,rotational_part,,,False,,073706,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,077955,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,077955,,,,,,,,experience,
,,missing STEP,,,,prismatic_block,,,False,,082187,,,,,,,,baseline,
,,missing STEP,,,,prismatic_block,,,False,,082187,,,,,,,,experience,
,,missing STEP,,,,rotational_part,,,False,,089970,,,,,,,,baseline,
,,missing STEP,,,,rotational_part,,,False,,089970,,,,,,,,experience,
,,missing STEP,,,,shaft,,,False,,092092,,,,,,,,baseline,
,,missing STEP,,,,shaft,,,False,,092092,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,095520,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,095520,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,102128,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,102128,,,,,,,,experience,
,,missing STEP,,,,patterned_plate,,,False,,104465,,,,,,,,baseline,
,,missing STEP,,,,patterned_plate,,,False,,104465,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,110143,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,110143,,,,,,,,experience,
,,missing STEP,,,,prismatic_block,,,False,,112069,,,,,,,,baseline,
,,missing STEP,,,,prismatic_block,,,False,,112069,,,,,,,,experience,
0.22361111004444456,0.6538461538461539,,0,0,0.6153846153846154,rotational_part,1.0,0.761904761904762,True,67519.74039233122,116166,147536.10631008807,71.3680012563105,1,0.38925591734814796,0.03424668647245051,0.07518718288900171,True,baseline,0.5423510754010293
0.18888888915554566,0.7307692307692307,,3,16,0.6923076923076923,rotational_part,1.0,0.8571428571428572,True,69565.58402815007,116166,147536.10631008807,73.4520592182128,1,0.365981954180488,0.032489227662340435,0.07289222461786297,True,experience,0.528484343473599
0.6746686351889055,0.45217391304347826,,0,0,0.45,multi_axis_manifold,0.0,0.7692307692307692,True,34234.94531047202,134828,349556.2389887021,48.526262227273214,1,0.9239731944207157,0.10449739414590258,0.3144594900968871,True,baseline,0.9020616956815967
0.7062506154470962,0.5043478260869565,,5,14,0.5,multi_axis_manifold,0.0,0.7692307692307692,True,38190.323227729335,134828,349556.2389887021,48.60371993717729,1,0.9152269986233839,0.09836507300701838,0.29918959165673675,True,experience,0.8907462692177447
7.3809523809523805,0.5,,0,0,0.3333333333333333,rotational_part,1.0,0.9411764705882353,True,25446.900494077883,136930,38.877209088173686,25.720060532485707,1,72.6363636363658,1.666072751413829,2.6857505921882785,True,baseline,653.545454545469
6.936507936507937,0.5,,0,0,0.3333333333333333,rotational_part,1.0,0.9411764705882353,True,21205.750411731566,136930,38.877209088173686,25.725642575854934,1,67.18181818181998,1.6549008981976585,2.6698915132197145,True,experience,544.4545454545574
,,missing STEP,,,,patterned_prismatic_part,,,False,,138022,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,138022,,,,,,,,experience,
,,missing STEP,,,,patterned_plate,,,False,,141680,,,,,,,,baseline,
,,missing STEP,,,,patterned_plate,,,False,,141680,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,145574,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,145574,,,,,,,,experience,
,,missing STEP,,,,prismatic_block,,,False,,150196,,,,,,,,baseline,
,,missing STEP,,,,prismatic_block,,,False,,150196,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,150587,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,150587,,,,,,,,experience,
,,missing STEP,,,,multi_axis_manifold,,,False,,152568,,,,,,,,baseline,
,,missing STEP,,,,multi_axis_manifold,,,False,,152568,,,,,,,,experience,
,,missing STEP,,,,plate_or_bracket,,,False,,153245,,,,,,,,baseline,
,,missing STEP,,,,plate_or_bracket,,,False,,153245,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,158256,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,158256,,,,,,,,experience,
,,missing STEP,,,,hollow_or_stepped_shaft,,,False,,162558,,,,,,,,baseline,
,,missing STEP,,,,hollow_or_stepped_shaft,,,False,,162558,,,,,,,,experience,
,,missing STEP,,,,multi_axis_manifold,,,False,,167962,,,,,,,,baseline,
,,missing STEP,,,,multi_axis_manifold,,,False,,167962,,,,,,,,experience,
,,missing STEP,,,,rotational_part,,,False,,168239,,,,,,,,baseline,
,,missing STEP,,,,rotational_part,,,False,,168239,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,174675,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,174675,,,,,,,,experience,
,,missing STEP,,,,patterned_plate,,,False,,183151,,,,,,,,baseline,
,,missing STEP,,,,patterned_plate,,,False,,183151,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,189669,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,189669,,,,,,,,experience,
,,missing STEP,,,,rotational_part,,,False,,194029,,,,,,,,baseline,
,,missing STEP,,,,rotational_part,,,False,,194029,,,,,,,,experience,
,,missing STEP,,,,prismatic_block,,,False,,201547,,,,,,,,baseline,
,,missing STEP,,,,prismatic_block,,,False,,201547,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,204730,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,204730,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,211326,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,211326,,,,,,,,experience,
,,missing STEP,,,,patterned_plate,,,False,,214404,,,,,,,,baseline,
,,missing STEP,,,,patterned_plate,,,False,,214404,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,229115,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,229115,,,,,,,,experience,
,,missing STEP,,,,hybrid_freeform_part,,,False,,234097,,,,,,,,baseline,
,,missing STEP,,,,hybrid_freeform_part,,,False,,234097,,,,,,,,experience,
,,missing STEP,,,,patterned_prismatic_part,,,False,,242307,,,,,,,,baseline,
,,missing STEP,,,,patterned_prismatic_part,,,False,,242307,,,,,,,,experience,
1 bbox_mape edge_rel_error error experience_methods_applied experience_methods_returned face_rel_error family family_match feature_f1 generated generated_volume id reference_volume similarity_score solid_count surface_area_rel_error surface_chamfer_norm surface_hausdorff95_norm valid variant volume_rel_error
2 0.0 0.5 0 0 0.25 general_mechanical_part 1.0 0.8571428571428571 True 125663.70614359173 000166 125663.70614359173 93.81711632834559 1 6.425513440379324e-14 0.004847580881730266 0.00977766538694562 True baseline 0.0
3 0.0 0.5 0 0 0.25 general_mechanical_part 1.0 0.8571428571428571 True 125663.70614359173 000166 125663.70614359173 93.81711632834559 1 6.425513440379324e-14 0.004847580881730266 0.00977766538694562 True experience 0.0
4 0.45066264681866697 0.125 0 0 0.1111111111111111 patterned_plate 0.0 0.8571428571428571 True 106129.48703937844 004764 36749.40939347248 54.33835930595081 1 1.0309687869495758 0.05550481239248205 0.1775660613682025 True baseline 1.8879236099568286
5 0.3413730293323282 0.125 3 18 0.1111111111111111 patterned_plate 0.0 0.8571428571428571 True 88774.08881569223 004764 36749.40939347248 59.488074232257325 1 0.7876989178243013 0.0465090322155671 0.15562347480527094 True experience 1.4156602862700836
6 0.5696649029982362 0.0 0 0 0.0 plate_or_bracket 1.0 1.0 True 19016.0 011680 898496.0 60.445468751589296 1 0.9780420050604243 0.10796723414266624 0.36481967349123434 True baseline 0.9788357432865589
7 0.583774250440917 0.0 4 10 0.0 plate_or_bracket 1.0 1.0 True 13547.999999999998 011680 898496.0 59.90070139134242 1 0.9842171382062035 0.11235674854244815 0.374964117147598 True experience 0.9849214687655816
8 0.18571427761876255 0.2631578947368421 0 0 0.26666666666666666 patterned_plate 1.0 1.0 True 23882.59512641282 016033 44618.37587532573 81.50778192907364 1 0.34116223818430896 0.018452946285241585 0.08713798240651745 True baseline 0.4647363410728704
9 0.3238095238095235 0.02631578947368421 3 10 0.06666666666666667 patterned_plate 1.0 0.9411764705882353 True 13866.369040708962 016033 44618.37587532573 75.91174289423121 1 0.5000404499582691 0.026851094204940862 0.12169100582216402 True experience 0.6892229094251465
10 0.5959595959595959 0.2857142857142857 0 0 0.28125 flanged_rotational_part 1.0 1.0 True 118756.98485692723 020543 24145.808587596228 51.56160371886595 1 1.5105685440071281 0.08451218347517844 0.22747541674825858 True baseline 3.9183271053483395
11 0.23232323232323235 0.05194805194805195 0 0 0.1875 flanged_rotational_part 1.0 0.888888888888889 True 55638.90609255368 020543 24145.808587596228 69.33575293745653 1 0.4640684949891876 0.042969081471212464 0.11104259063963073 True experience 1.3042883774509648
12 0.06666666666666685 0.2 0 0 0.19480519480519481 patterned_prismatic_part 1.0 1.0 True 569438.4483554161 024675 580617.6091315141 91.92371160771847 1 0.2739018704584683 0.011073315847120254 0.07352156562273186 True baseline 0.019253912730652068
13 0.5777777777777777 0.8933333333333333 0 0 1.4025974025974026 patterned_prismatic_part 1.0 0.9333333333333333 True 38876.03946418978 024675 580617.6091315141 56.84188202691976 1 0.8746474563374864 0.07094038930770122 0.33837892456299423 True experience 0.9330436437807312
14 0.0980952380952385 0.16666666666666666 0 0 0.14285714285714285 patterned_plate 1.0 0.9333333333333333 True 73807.58425433026 029190 131254.75510206894 83.74861954252185 1 0.1560183040026604 0.024101504873521428 0.04232654923555514 True baseline 0.4376768735202429
15 0.14047619047618823 1.5277777777777777 0 0 1.6428571428571428 patterned_plate 1.0 0.7999999999999999 True 71846.37021205797 029190 131254.75510206894 73.91053356207678 1 0.20263501345527155 0.02460078307658505 0.04143094591291666 True experience 0.45261891535901033
16 0.31241830065359194 0.9285714285714286 0 0 0.5625 patterned_plate 1.0 0.923076923076923 True 34352.594678913054 030352 60881.15044407846 73.94372815488485 1 0.48742381144539443 0.02510282770499014 0.10902462973941958 True baseline 0.4357433388111292
17 0.4306604575163398 2.642857142857143 3 10 2.0625 patterned_plate 1.0 0.7692307692307692 True 11173.345159683424 030352 60881.15044407846 59.45331217188532 1 0.6732862799472543 0.03957952772551367 0.16850566658178062 True experience 0.8164728314398964
18 0.21212121212121218 0.5 0 0 0.43243243243243246 flanged_rotational_part 1.0 0.9600000000000001 True 73578.89519968569 046014 162271.3445381513 75.92217396437314 1 0.333964798541362 0.033942930350360764 0.05126001418573268 True baseline 0.5465687709120651
19 0.13636363636363644 0.5 4 10 0.43243243243243246 flanged_rotational_part 1.0 0.9600000000000001 True 111532.3028776836 046014 162271.3445381513 81.61765078194381 1 0.24200901291799348 0.02662187229047825 0.043202965297600246 True experience 0.31268023202050027
20 0.6851851851851847 0.3709677419354839 0 0 0.34782608695652173 patterned_prismatic_part 1.0 0.823529411764706 True 167251.53322574112 047048 33461.296317388085 44.79000279500974 1 1.6260155362793598 0.11322328439048983 0.2789388077989153 True baseline 3.9983578531842254
21 0.7175925925925921 0.1693548387096774 3 18 0.2391304347826087 patterned_prismatic_part 1.0 0.823529411764706 True 169277.67589913897 047048 33461.296317388085 44.38628038601891 1 1.587008333438104 0.13259494837120114 0.321530970786483 True experience 4.058909681606514
22 1.4575235605335388e-16 0.5 0 0 0.3125 hollow_or_stepped_shaft 1.0 0.9166666666666666 True 8278002.394429736 048346 8341713.8934443 93.45850460439323 1 0.01545893719801078 0.008802226092164953 0.016975575422477428 True baseline 0.007637698898380382
23 1.4575235605335388e-16 0.5 0 0 0.3125 hollow_or_stepped_shaft 1.0 0.9166666666666666 True 8292610.800268921 048346 8341713.8934443 93.51807327218812 1 0.012238325281746575 0.008857323306102671 0.017730676062993916 True experience 0.005886451369899948
24 0.02857142857142833 0.2222222222222222 0 0 0.21212121212121213 patterned_plate 0.0 0.8571428571428571 True 315117.2806010815 053664 349435.5647059466 85.57701767939263 1 0.07576465816363272 0.012696566244914555 0.02434484580541925 True baseline 0.0982106218459597
25 0.07936507936507906 0.0 0 0 0.030303030303030304 patterned_plate 0.0 0.8571428571428571 True 282776.14120936824 053664 349435.5647059466 83.57268406993335 1 0.167398697885095 0.016320456106002173 0.05162627100789126 True experience 0.19076313412080106
26 missing STEP patterned_prismatic_part False 063487 baseline
27 missing STEP patterned_prismatic_part False 063487 experience
28 0.05572210294349599 0.420863309352518 0 0 0.4396551724137931 flanged_rotational_part 1.0 0.923076923076923 True 68680.44493144708 065487 81354.16556588435 87.21779338748289 1 0.060356551334099576 0.022572421105818447 0.053770414665160235 True baseline 0.15578453231351141
29 missing STEP flanged_rotational_part False 065487 experience
30 missing STEP patterned_plate False 067186 baseline
31 missing STEP patterned_plate False 067186 experience
32 missing STEP rotational_part False 073706 baseline
33 missing STEP rotational_part False 073706 experience
34 missing STEP patterned_prismatic_part False 077955 baseline
35 missing STEP patterned_prismatic_part False 077955 experience
36 missing STEP prismatic_block False 082187 baseline
37 missing STEP prismatic_block False 082187 experience
38 missing STEP rotational_part False 089970 baseline
39 missing STEP rotational_part False 089970 experience
40 missing STEP shaft False 092092 baseline
41 missing STEP shaft False 092092 experience
42 missing STEP patterned_prismatic_part False 095520 baseline
43 missing STEP patterned_prismatic_part False 095520 experience
44 missing STEP patterned_prismatic_part False 102128 baseline
45 missing STEP patterned_prismatic_part False 102128 experience
46 missing STEP patterned_plate False 104465 baseline
47 missing STEP patterned_plate False 104465 experience
48 missing STEP patterned_prismatic_part False 110143 baseline
49 missing STEP patterned_prismatic_part False 110143 experience
50 missing STEP prismatic_block False 112069 baseline
51 missing STEP prismatic_block False 112069 experience
52 0.22361111004444456 0.6538461538461539 0 0 0.6153846153846154 rotational_part 1.0 0.761904761904762 True 67519.74039233122 116166 147536.10631008807 71.3680012563105 1 0.38925591734814796 0.03424668647245051 0.07518718288900171 True baseline 0.5423510754010293
53 0.18888888915554566 0.7307692307692307 3 16 0.6923076923076923 rotational_part 1.0 0.8571428571428572 True 69565.58402815007 116166 147536.10631008807 73.4520592182128 1 0.365981954180488 0.032489227662340435 0.07289222461786297 True experience 0.528484343473599
54 0.6746686351889055 0.45217391304347826 0 0 0.45 multi_axis_manifold 0.0 0.7692307692307692 True 34234.94531047202 134828 349556.2389887021 48.526262227273214 1 0.9239731944207157 0.10449739414590258 0.3144594900968871 True baseline 0.9020616956815967
55 0.7062506154470962 0.5043478260869565 5 14 0.5 multi_axis_manifold 0.0 0.7692307692307692 True 38190.323227729335 134828 349556.2389887021 48.60371993717729 1 0.9152269986233839 0.09836507300701838 0.29918959165673675 True experience 0.8907462692177447
56 7.3809523809523805 0.5 0 0 0.3333333333333333 rotational_part 1.0 0.9411764705882353 True 25446.900494077883 136930 38.877209088173686 25.720060532485707 1 72.6363636363658 1.666072751413829 2.6857505921882785 True baseline 653.545454545469
57 6.936507936507937 0.5 0 0 0.3333333333333333 rotational_part 1.0 0.9411764705882353 True 21205.750411731566 136930 38.877209088173686 25.725642575854934 1 67.18181818181998 1.6549008981976585 2.6698915132197145 True experience 544.4545454545574
58 missing STEP patterned_prismatic_part False 138022 baseline
59 missing STEP patterned_prismatic_part False 138022 experience
60 missing STEP patterned_plate False 141680 baseline
61 missing STEP patterned_plate False 141680 experience
62 missing STEP patterned_prismatic_part False 145574 baseline
63 missing STEP patterned_prismatic_part False 145574 experience
64 missing STEP prismatic_block False 150196 baseline
65 missing STEP prismatic_block False 150196 experience
66 missing STEP patterned_prismatic_part False 150587 baseline
67 missing STEP patterned_prismatic_part False 150587 experience
68 missing STEP multi_axis_manifold False 152568 baseline
69 missing STEP multi_axis_manifold False 152568 experience
70 missing STEP plate_or_bracket False 153245 baseline
71 missing STEP plate_or_bracket False 153245 experience
72 missing STEP patterned_prismatic_part False 158256 baseline
73 missing STEP patterned_prismatic_part False 158256 experience
74 missing STEP hollow_or_stepped_shaft False 162558 baseline
75 missing STEP hollow_or_stepped_shaft False 162558 experience
76 missing STEP multi_axis_manifold False 167962 baseline
77 missing STEP multi_axis_manifold False 167962 experience
78 missing STEP rotational_part False 168239 baseline
79 missing STEP rotational_part False 168239 experience
80 missing STEP patterned_prismatic_part False 174675 baseline
81 missing STEP patterned_prismatic_part False 174675 experience
82 missing STEP patterned_plate False 183151 baseline
83 missing STEP patterned_plate False 183151 experience
84 missing STEP patterned_prismatic_part False 189669 baseline
85 missing STEP patterned_prismatic_part False 189669 experience
86 missing STEP rotational_part False 194029 baseline
87 missing STEP rotational_part False 194029 experience
88 missing STEP prismatic_block False 201547 baseline
89 missing STEP prismatic_block False 201547 experience
90 missing STEP patterned_prismatic_part False 204730 baseline
91 missing STEP patterned_prismatic_part False 204730 experience
92 missing STEP patterned_prismatic_part False 211326 baseline
93 missing STEP patterned_prismatic_part False 211326 experience
94 missing STEP patterned_plate False 214404 baseline
95 missing STEP patterned_plate False 214404 experience
96 missing STEP patterned_prismatic_part False 229115 baseline
97 missing STEP patterned_prismatic_part False 229115 experience
98 missing STEP hybrid_freeform_part False 234097 baseline
99 missing STEP hybrid_freeform_part False 234097 experience
100 missing STEP patterned_prismatic_part False 242307 baseline
101 missing STEP patterned_prismatic_part False 242307 experience
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
# CAD 经验库 50 件 A/B 盲重建报告
> 运行状态:2026-07-23 因 Codex 账户用量上限中断;当前为 15/50
> 完整配对的阶段结果。系统提示可在 2026-07-29 10:24 后继续。
## 口径
- 测试集:固定分层抽取 50 件;这 50 件不进入经验库。
- 训练经验:其余 950 件生成的通用经验库。
- 两组输入完全相同:仅多视图图片和文字描述;生成过程禁止读取参考 STEP 和解析 JSON。
- Baseline:空经验库;Experience950 件经验库。
- 参考 STEP 只在生成完成后用于评分。
- “相似度”是体积、面积、包围盒、表面距离、拓扑和语义特征的综合诊断分,不代表制造级 1:1 或可互换性。
## 当前完成度
- 有效 Baseline16/50
- 有效 Experience15/50
- 完整配对:15/50
- 经验方法返回/实际采用:106/28
## 汇总
| 指标 | 无经验 | 有经验 | 差值 |
|---|---:|---:|---:|
| 综合相似度(0–100,越高越好) | 69.11 | 66.64 | -2.47 |
| 体积相对误差(越低越好) | 44.519 | 37.136 | -7.383 |
| 表面积相对误差 | 5.385 | 4.997 | -0.388 |
| 包围盒尺寸 MAPE | 0.766 | 0.760 | -0.006 |
| 归一化表面 Chamfer | 0.1537 | 0.1559 | +0.0023 |
| 特征 F1 | 0.907 | 0.878 | -0.029 |
经验组胜/平/负(±0.25 分视为平):**4/4/7**。
## 阶段解释
- 经验组的体积、表面积和包围盒平均误差较低,说明通用构造经验对尺度/主体关系有一定帮助。
- 经验组的表面距离、特征 F1 和综合分暂时较差,说明部分经验会诱导多余或错误特征,当前不能宣称整体提升。
- 每组由独立的大模型运行生成;即使经验命中为 0,两次结果也可能因模型随机性不同。因此最终应完成 50 对,并对若干样本做重复运行,才能把经验效果与随机波动分开。
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
HERE = Path(__file__).resolve().parent
def run(case_id: str, variant: str) -> dict:
completed = subprocess.run(
[sys.executable, str(HERE / "run_case.py"), case_id, variant],
capture_output=True,
text=True,
)
return {
"id": case_id,
"variant": variant,
"returncode": completed.returncode,
"output": completed.stdout.strip(),
"error": completed.stderr.strip(),
}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--workers", type=int, default=4)
parser.add_argument("--start-index", type=int, default=0)
parser.add_argument("--end-index", type=int)
args = parser.parse_args()
manifest = json.loads((HERE / "manifest.json").read_text())
cases = manifest["cases"][args.start_index : args.end_index]
jobs = [
(case["id"], variant)
for case in cases
for variant in ("baseline", "experience")
]
results_name = (
"batch-results.json"
if args.start_index == 0 and args.end_index is None
else f"batch-results-{args.start_index}-{args.end_index or 'end'}.json"
)
results_path = HERE / results_name
results = []
with ThreadPoolExecutor(max_workers=args.workers) as pool:
futures = {pool.submit(run, *job): job for job in jobs}
for future in as_completed(futures):
result = future.result()
results.append(result)
print(json.dumps(result, ensure_ascii=False), flush=True)
results_path.write_text(
json.dumps(results, indent=2, ensure_ascii=False) + "\n"
)
failed = [item for item in results if item["returncode"] != 0]
print(json.dumps({"completed": len(results), "failed": len(failed)}))
raise SystemExit(1 if failed else 0)
if __name__ == "__main__":
main()
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import subprocess
from pathlib import Path
HERE = Path(__file__).resolve().parent
WORKSPACE = HERE.parents[2]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("case_id")
parser.add_argument("variant", choices=("baseline", "experience"))
args = parser.parse_args()
manifest = json.loads((HERE / "manifest.json").read_text())
case = next(item for item in manifest["cases"] if item["id"] == args.case_id)
output = Path(case[f"{args.variant}_step"])
output.parent.mkdir(parents=True, exist_ok=True)
if output.is_file():
print(json.dumps({"status": "skipped", "output": str(output)}))
return
description = Path(case["description"]).read_text()
library = (
HERE / "empty-library.json"
if args.variant == "baseline"
else HERE / "train-library.json"
)
prompt = f"""
This is a controlled CAD reconstruction benchmark.
Use $cad-router and $cad to reconstruct the single mechanical part shown in
the attached multi-view image and described below. Generate a new parametric
STEP from the image and description only.
Hard isolation rules:
- Do not search for, inspect, import, copy, or reference any teacher STEP,
SLDPRT, parser/input, parser/output, case JSON, or SLDPRT-NET dataset file.
- The attached image and the description in this prompt are the only
part-specific evidence.
- Run cad-router with the explicit generalized experience library
{library.resolve()}; do not use any other experience library.
- Use the router-selected backend and actually apply returned generalized
methods when present.
- Do not preserve an exact STEP base: this must be a blind reconstruction.
Write the primary output exactly to:
{output.resolve()}
Keep the editable generator and model-spec.json in the same directory.
Also write the router result to route.json and record in cad-task.json the
experience methods returned and the subset actually applied. Record an empty
list when nothing matches; never claim an experience method was used unless
the router returned it.
Validate that the STEP opens and contains positive-volume geometry. For this
batch benchmark, do not open CAD Viewer or create browser tabs.
Description:
{description}
"""
command = [
"codex",
"exec",
"--ephemeral",
"--json",
"--image",
case["image"],
"--cd",
str(WORKSPACE),
"--sandbox",
"danger-full-access",
"--output-last-message",
case[f"{args.variant}_message"],
prompt,
]
with Path(case[f"{args.variant}_log"]).open("w") as stream:
completed = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT)
print(json.dumps({
"status": "ok" if completed.returncode == 0 and output.is_file() else "failed",
"returncode": completed.returncode,
"output": str(output),
}))
raise SystemExit(0 if completed.returncode == 0 and output.is_file() else 1)
if __name__ == "__main__":
main()
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import random
from collections import defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
OUTPUT = ROOT / "parser" / "output"
HERE = Path(__file__).resolve().parent
DATASET = Path("/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset")
TARGET_COUNT = 50
def main() -> None:
rows: list[dict] = []
for path in sorted(OUTPUT.glob("*.json")):
payload = json.loads(path.read_text())
stem = Path(payload["provenance"]["source_path"]).stem
image = DATASET / "image" / f"{stem}.png"
description = DATASET / "Des_Text" / f"{stem}.txt"
reference = DATASET / "model_step" / f"{stem}.step"
if image.is_file() and description.is_file() and reference.is_file():
rows.append(
{
"id": stem,
"family": payload["design_ir"]["part_family"],
"case_json": str(path.resolve()),
"image": str(image),
"description": str(description),
"reference_step": str(reference),
}
)
by_family: dict[str, list[dict]] = defaultdict(list)
for row in rows:
by_family[row["family"]].append(row)
rng = random.Random(20260723)
selected: list[dict] = []
total = len(rows)
for family, family_rows in sorted(by_family.items()):
rng.shuffle(family_rows)
quota = max(1, round(TARGET_COUNT * len(family_rows) / total))
selected.extend(family_rows[: min(quota, len(family_rows))])
selected_ids = {row["id"] for row in selected}
if len(selected) > TARGET_COUNT:
selected = selected[:TARGET_COUNT]
elif len(selected) < TARGET_COUNT:
remainder = [row for row in rows if row["id"] not in selected_ids]
rng.shuffle(remainder)
selected.extend(remainder[: TARGET_COUNT - len(selected)])
selected = sorted(selected, key=lambda row: row["id"])
selected_ids = {row["id"] for row in selected}
train_dir = HERE / "train_cases"
train_dir.mkdir(parents=True, exist_ok=True)
for path in train_dir.glob("*.json"):
path.unlink()
for row in rows:
if row["id"] in selected_ids:
continue
source = Path(row["case_json"])
(train_dir / source.name).symlink_to(source)
for row in selected:
for variant in ("baseline", "experience"):
output_dir = HERE / "generated" / variant / row["id"]
output_dir.mkdir(parents=True, exist_ok=True)
row[f"{variant}_step"] = str(output_dir / f"{row['id']}.step")
row[f"{variant}_log"] = str(output_dir / "codex.jsonl")
row[f"{variant}_message"] = str(output_dir / "final.txt")
manifest = {
"schema_version": "1.0",
"selection_seed": 20260723,
"holdout_count": len(selected),
"train_count": len(rows) - len(selected),
"cases": selected,
}
(HERE / "manifest.json").write_text(
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
)
print(json.dumps({
"holdout_count": len(selected),
"train_count": len(rows) - len(selected),
"family_counts": {
family: sum(row["family"] == family for row in selected)
for family in sorted({row["family"] for row in selected})
},
}, indent=2))
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
{
"schema_version": "1.0",
"draft_kind": "llm_generalized_experience_proposals",
"proposals": [
{
"id": "constraint.patterned_prismatic_part.repeated_radius_group",
"kind": "constraint",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["repeated_axial_hole_pattern"],
"required_relations": ["repeated_radius_group"],
"context_features": ["repeated_axial_hole_pattern"]
},
"guidance": "Preserve one shared semantic radius role across members of a repeated axial feature group.",
"semantic_rationale": "The equality relationship transfers across different overall sizes and placements.",
"check": "Confirm that repeated members intended to be equal retain the same radius role."
},
{
"id": "constraint.patterned_prismatic_part.multiple_axis_system",
"kind": "constraint",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["cylindrical_feature_network", "multi_axis_passage"],
"required_relations": ["multiple_axis_system"],
"context_features": ["cylindrical_feature_network", "multi_axis_passage"]
},
"guidance": "Represent passage directions as explicit semantic axes instead of unrelated local cuts.",
"semantic_rationale": "An explicit axis network preserves relationships when the body dimensions change.",
"check": "Confirm that every passage remains assigned to its intended axis system."
},
{
"id": "motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
"kind": "feature_motif",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["multi_level_planar_profile", "planar_dominant_body"],
"required_relations": [],
"context_features": ["multi_level_planar_profile", "planar_dominant_body"]
},
"guidance": "Treat the planar-dominant body and its multiple profile levels as one reusable structural composition.",
"semantic_rationale": "The composition describes topology without depending on an instance dimension.",
"check": "Confirm that the expected planar levels remain distinct and connected to the primary body."
},
{
"id": "motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
"kind": "feature_motif",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["cylindrical_feature_network", "multi_axis_passage"],
"required_relations": ["multiple_axis_system"],
"context_features": ["cylindrical_feature_network", "multi_axis_passage"]
},
"guidance": "Model the cylindrical feature network and multi-axis passages as a coordinated feature system.",
"semantic_rationale": "The coordinated system transfers across variants while preserving semantic axes.",
"check": "Confirm that the cylindrical features and passages preserve their intended axis memberships."
},
{
"id": "validation.patterned_prismatic_part.repeated_feature_consistency",
"kind": "validation_rule",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["repeated_axial_hole_pattern"],
"required_relations": ["repeated_radius_group"],
"context_features": ["repeated_axial_hole_pattern"]
},
"guidance": "Validate repeated feature membership, equality roles, and attachment to the intended host region.",
"semantic_rationale": "These checks remain meaningful across dimensions and pattern placements.",
"check": "Verify repeated-member count consistency, shared radius roles, and host connectivity."
}
]
}
@@ -0,0 +1,33 @@
# 015133 单件高纯度蒸馏实验
## 对照结果
| 路线 | 可用于生成的信息 | 严格相似度 | 结论 |
|---|---|---:|---|
| 纯图片生成 | 多视图图片与文字描述 | 54.71 | 主体类别可辨认,但尺寸、孔系和端部结构损失明显 |
| 正式单件通用蒸馏 | 净化后的通用特征、关系和比例角色 | 无可执行提升 | 产生 5 条候选、0 条推广经验;单件证据不能证明通用性 |
| 专属 `exact_step_base` 模板 | 原始 B-Rep、哈希、几何签名与 JSON 修改层 | 100.00 | STEP 字节一致,199 面、488 边、体积与包围盒完全一致 |
## 源几何
- 包围盒:213.5 × 78.5 × 76.0 mm
- 实体数:1
- 面数:199
- 边数:488
- 体积:1174741.621833 mm³
- 解析曲面:79 个平面、80 个圆柱面、40 个圆锥面
## 单件通用蒸馏实际保留的信息
- 7 个粗粒度特征类别
- 2 个关系
- 6 个无量纲比例角色
- 不保留孔数量、孔中心、端板槽、各圆柱面的语义身份、具体尺寸或构造顺序
这些信息适合积累跨零件经验,但不足以重建一个具有 199 个面的指定实例。
## 结论
“单件高纯度”要达到 90 分以上,必须进入**实例模板/逆向参数化**
通道,而不是通用经验通道。通用经验负责教方法;实例模板负责保存指定
零件的尺寸、特征图和精确基体。两类数据必须分库、分权限、分评价口径。
@@ -0,0 +1,231 @@
{
"schema_version": "1.0",
"batch_kind": "sanitized_cad_semantic_review",
"guardrails": {
"contains_instance_dimensions": false,
"contains_absolute_coordinates": false,
"llm_must_propose_methods_not_copy_answers": true,
"proposals_require_deterministic_evidence_verification": true
},
"corpus_summary": {
"case_count": 1,
"family_count": 1
},
"cases": [
{
"anonymous_case": "case_000001",
"family": "patterned_prismatic_part",
"features": [
"cylindrical_feature_network",
"high_topological_complexity",
"moderate_aspect_body",
"multi_axis_passage",
"multi_level_planar_profile",
"planar_dominant_body",
"repeated_axial_hole_pattern"
],
"relations": [
"multiple_axis_system",
"repeated_radius_group"
],
"candidate_rules": [],
"validation_targets": [],
"dimensionless_observation_roles": [
{
"name": "bbox_short_to_long",
"numerator_role": "short_bounding_span",
"denominator_role": "long_bounding_span"
},
{
"name": "bbox_middle_to_long",
"numerator_role": "middle_bounding_span",
"denominator_role": "long_bounding_span"
},
{
"name": "planar_area_fraction",
"numerator_role": "planar_surface_area",
"denominator_role": "total_surface_area"
},
{
"name": "cylindrical_area_fraction",
"numerator_role": "cylindrical_surface_area",
"denominator_role": "total_surface_area"
},
{
"name": "pattern_radius_to_transverse_span",
"numerator_role": "pattern_radius",
"denominator_role": "transverse_bounding_span"
},
{
"name": "repeated_feature_radius_to_transverse_span",
"numerator_role": "repeated_feature_radius",
"denominator_role": "transverse_bounding_span"
}
]
}
],
"existing_vocabulary": {
"promoted_ids": [
"constraint.flanged_rotational_part.coaxial_stack",
"constraint.flanged_rotational_part.common_pattern_radius",
"constraint.flanged_rotational_part.dominant_axis_alignment",
"constraint.flanged_rotational_part.repeated_radius_group",
"constraint.flanged_rotational_part.shared_passage_axis",
"constraint.flanged_rotational_part.transition_axis_continuity",
"constraint.hollow_or_stepped_shaft.coaxial_stack",
"constraint.hollow_or_stepped_shaft.dominant_axis_alignment",
"constraint.hollow_or_stepped_shaft.shared_passage_axis",
"constraint.hollow_or_stepped_shaft.transition_axis_continuity",
"constraint.multi_axis_manifold.multiple_axis_system",
"constraint.multi_axis_manifold.shared_passage_axis",
"constraint.patterned_plate.common_pattern_radius",
"constraint.patterned_plate.multiple_axis_system",
"constraint.patterned_plate.repeated_radius_group",
"constraint.patterned_plate.shared_passage_axis",
"constraint.patterned_plate.transition_axis_continuity",
"constraint.patterned_prismatic_part.common_pattern_radius",
"constraint.patterned_prismatic_part.multiple_axis_system",
"constraint.patterned_prismatic_part.repeated_radius_group",
"constraint.patterned_prismatic_part.shared_passage_axis",
"constraint.patterned_prismatic_part.transition_axis_continuity",
"constraint.rotational_part.coaxial_stack",
"constraint.rotational_part.dominant_axis_alignment",
"constraint.rotational_part.shared_passage_axis",
"constraint.rotational_part.transition_axis_continuity",
"constraint.shaft.coaxial_stack",
"constraint.shaft.dominant_axis_alignment",
"constraint.shaft.shared_passage_axis",
"constraint.shaft.transition_axis_continuity",
"distribution.flanged_rotational_part.bbox_middle_to_long",
"distribution.flanged_rotational_part.bbox_short_to_long",
"distribution.flanged_rotational_part.cylindrical_area_fraction",
"distribution.flanged_rotational_part.largest_coaxial_radius_to_transverse_span",
"distribution.flanged_rotational_part.pattern_radius_to_transverse_span",
"distribution.flanged_rotational_part.planar_area_fraction",
"distribution.flanged_rotational_part.repeated_feature_radius_to_transverse_span",
"distribution.flanged_rotational_part.smallest_to_largest_coaxial_radius",
"distribution.general_mechanical_part.bbox_middle_to_long",
"distribution.general_mechanical_part.bbox_short_to_long",
"distribution.general_mechanical_part.cylindrical_area_fraction",
"distribution.general_mechanical_part.planar_area_fraction",
"distribution.hollow_or_stepped_shaft.bbox_middle_to_long",
"distribution.hollow_or_stepped_shaft.bbox_short_to_long",
"distribution.hollow_or_stepped_shaft.cylindrical_area_fraction",
"distribution.hollow_or_stepped_shaft.largest_coaxial_radius_to_transverse_span",
"distribution.hollow_or_stepped_shaft.planar_area_fraction",
"distribution.hollow_or_stepped_shaft.smallest_to_largest_coaxial_radius",
"distribution.multi_axis_manifold.bbox_middle_to_long",
"distribution.multi_axis_manifold.bbox_short_to_long",
"distribution.multi_axis_manifold.cylindrical_area_fraction",
"distribution.multi_axis_manifold.pattern_radius_to_transverse_span",
"distribution.multi_axis_manifold.planar_area_fraction",
"distribution.multi_axis_manifold.repeated_feature_radius_to_transverse_span",
"distribution.patterned_plate.bbox_middle_to_long",
"distribution.patterned_plate.bbox_short_to_long",
"distribution.patterned_plate.cylindrical_area_fraction",
"distribution.patterned_plate.pattern_radius_to_transverse_span",
"distribution.patterned_plate.planar_area_fraction",
"distribution.patterned_plate.repeated_feature_radius_to_transverse_span",
"distribution.patterned_prismatic_part.bbox_middle_to_long",
"distribution.patterned_prismatic_part.bbox_short_to_long",
"distribution.patterned_prismatic_part.cylindrical_area_fraction",
"distribution.patterned_prismatic_part.largest_coaxial_radius_to_transverse_span",
"distribution.patterned_prismatic_part.pattern_radius_to_transverse_span",
"distribution.patterned_prismatic_part.planar_area_fraction",
"distribution.patterned_prismatic_part.repeated_feature_radius_to_transverse_span",
"distribution.patterned_prismatic_part.smallest_to_largest_coaxial_radius",
"distribution.plate_or_bracket.bbox_middle_to_long",
"distribution.plate_or_bracket.bbox_short_to_long",
"distribution.plate_or_bracket.cylindrical_area_fraction",
"distribution.plate_or_bracket.planar_area_fraction",
"distribution.prismatic_block.bbox_middle_to_long",
"distribution.prismatic_block.bbox_short_to_long",
"distribution.prismatic_block.cylindrical_area_fraction",
"distribution.prismatic_block.pattern_radius_to_transverse_span",
"distribution.prismatic_block.planar_area_fraction",
"distribution.prismatic_block.repeated_feature_radius_to_transverse_span",
"distribution.rotational_part.bbox_middle_to_long",
"distribution.rotational_part.bbox_short_to_long",
"distribution.rotational_part.cylindrical_area_fraction",
"distribution.rotational_part.largest_coaxial_radius_to_transverse_span",
"distribution.rotational_part.planar_area_fraction",
"distribution.rotational_part.smallest_to_largest_coaxial_radius",
"distribution.shaft.bbox_middle_to_long",
"distribution.shaft.bbox_short_to_long",
"distribution.shaft.cylindrical_area_fraction",
"distribution.shaft.largest_coaxial_radius_to_transverse_span",
"distribution.shaft.planar_area_fraction",
"distribution.shaft.smallest_to_largest_coaxial_radius",
"motif.flanged_rotational_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.flanged_rotational_part.multi_level_planar_profile_and_planar_dominant_body",
"motif.flanged_rotational_part.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
"motif.flanged_rotational_part.rotational_body_and_flange_like_rotational_body",
"motif.flanged_rotational_part.rotational_body_and_stepped_rotational_profile",
"motif.hollow_or_stepped_shaft.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.hollow_or_stepped_shaft.rotational_body_and_stepped_rotational_profile",
"motif.hollow_or_stepped_shaft.shaft_like_body_and_stepped_rotational_profile",
"motif.multi_axis_manifold.cylindrical_feature_network_and_multi_axis_passage",
"motif.multi_axis_manifold.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.multi_axis_manifold.multi_level_planar_profile_and_planar_dominant_body",
"motif.patterned_plate.cylindrical_feature_network_and_multi_axis_passage",
"motif.patterned_plate.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.patterned_plate.multi_level_planar_profile_and_planar_dominant_body",
"motif.patterned_plate.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
"motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
"motif.patterned_prismatic_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
"motif.patterned_prismatic_part.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
"motif.plate_or_bracket.multi_level_planar_profile_and_planar_dominant_body",
"motif.prismatic_block.multi_level_planar_profile_and_planar_dominant_body",
"motif.rotational_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.rotational_part.rotational_body_and_flange_like_rotational_body",
"motif.rotational_part.rotational_body_and_stepped_rotational_profile",
"motif.rotational_part.rotational_body_and_toroidal_blend_or_groove",
"motif.shaft.cylindrical_feature_network_and_stepped_cylindrical_passage",
"motif.shaft.rotational_body_and_stepped_rotational_profile",
"motif.shaft.shaft_like_body_and_stepped_rotational_profile"
],
"candidate_ids": [
"constraint.flanged_rotational_part.multiple_axis_system",
"constraint.general_mechanical_part.transition_axis_continuity",
"constraint.hollow_or_stepped_shaft.multiple_axis_system",
"constraint.multi_axis_manifold.transition_axis_continuity",
"constraint.prismatic_block.transition_axis_continuity",
"constraint.rotational_part.multiple_axis_system",
"distribution.general_mechanical_part.largest_coaxial_radius_to_transverse_span",
"distribution.general_mechanical_part.smallest_to_largest_coaxial_radius",
"distribution.patterned_plate.largest_coaxial_radius_to_transverse_span",
"distribution.patterned_plate.smallest_to_largest_coaxial_radius",
"distribution.plate_or_bracket.pattern_radius_to_transverse_span",
"distribution.plate_or_bracket.repeated_feature_radius_to_transverse_span",
"distribution.prismatic_block.largest_coaxial_radius_to_transverse_span",
"distribution.prismatic_block.smallest_to_largest_coaxial_radius",
"motif.flanged_rotational_part.cylindrical_feature_network_and_multi_axis_passage",
"motif.flanged_rotational_part.rotational_body_and_toroidal_blend_or_groove",
"motif.flanged_rotational_part.shaft_like_body_and_stepped_rotational_profile",
"motif.hollow_or_stepped_shaft.cylindrical_feature_network_and_multi_axis_passage",
"motif.hollow_or_stepped_shaft.rotational_body_and_toroidal_blend_or_groove",
"motif.rotational_part.cylindrical_feature_network_and_multi_axis_passage",
"motif.rotational_part.multi_level_planar_profile_and_planar_dominant_body"
]
},
"draft_contract": {
"draft_kind": "llm_generalized_experience_proposals",
"proposal_required_fields": [
"id",
"kind",
"scope",
"evidence_query",
"guidance",
"semantic_rationale"
],
"allowed_kinds": [
"feature_motif",
"constraint",
"design_rule",
"validation_rule",
"failure_repair",
"dimensionless_distribution"
]
}
}
@@ -0,0 +1,173 @@
{
"schema_version": "2.0",
"library_kind": "generalized_cad_experience",
"generated_at": "2026-07-23T11:21:55+00:00",
"induction_mode": "llm_proposals_with_deterministic_evidence_verification",
"status": "collecting_evidence",
"policy": {
"instance_parameters_allowed": false,
"absolute_coordinates_allowed": false,
"single_case_promotion_allowed": false,
"llm_semantic_review_required": true,
"draft_evidence_verified": true,
"router_consumable": true,
"minimum_support": 20,
"minimum_confidence": 0.8
},
"corpus_summary": {
"accepted_case_count": 1,
"duplicate_case_count": 0,
"rejected_case_count": 0,
"family_count": 1
},
"experience_summary": {
"llm_proposal_count": 5,
"promoted_experience_count": 0,
"candidate_experience_count": 5,
"by_kind": [],
"by_scope": [],
"candidate_by_kind": [
{
"kind": "constraint",
"count": 2
},
{
"kind": "feature_motif",
"count": 2
},
{
"kind": "validation_rule",
"count": 1
}
]
},
"experiences": [],
"candidate_experiences": [
{
"id": "constraint.patterned_prismatic_part.multiple_axis_system",
"kind": "constraint",
"scope": [
"patterned_prismatic_part"
],
"when": {
"features": [
"cylindrical_feature_network",
"multi_axis_passage"
],
"relations": [
"multiple_axis_system"
]
},
"guidance": "Represent passage directions as explicit semantic axes instead of unrelated local cuts.",
"semantic_rationale": "An explicit axis network preserves relationships when the body dimensions change.",
"support": 1,
"confidence": 1.0,
"check": "Confirm that every passage remains assigned to its intended axis system.",
"promotion_state": "candidate",
"required_support": 20,
"remaining_support": 19,
"required_confidence": 0.8,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
},
{
"id": "constraint.patterned_prismatic_part.repeated_radius_group",
"kind": "constraint",
"scope": [
"patterned_prismatic_part"
],
"when": {
"features": [
"repeated_axial_hole_pattern"
],
"relations": [
"repeated_radius_group"
]
},
"guidance": "Preserve one shared semantic radius role across members of a repeated axial feature group.",
"semantic_rationale": "The equality relationship transfers across different overall sizes and placements.",
"support": 1,
"confidence": 1.0,
"check": "Confirm that repeated members intended to be equal retain the same radius role.",
"promotion_state": "candidate",
"required_support": 20,
"remaining_support": 19,
"required_confidence": 0.8,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
},
{
"id": "motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
"kind": "feature_motif",
"scope": [
"patterned_prismatic_part"
],
"when": {
"features": [
"cylindrical_feature_network",
"multi_axis_passage"
],
"relations": [
"multiple_axis_system"
]
},
"guidance": "Model the cylindrical feature network and multi-axis passages as a coordinated feature system.",
"semantic_rationale": "The coordinated system transfers across variants while preserving semantic axes.",
"support": 1,
"confidence": 1.0,
"check": "Confirm that the cylindrical features and passages preserve their intended axis memberships.",
"promotion_state": "candidate",
"required_support": 20,
"remaining_support": 19,
"required_confidence": 0.8,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
},
{
"id": "motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
"kind": "feature_motif",
"scope": [
"patterned_prismatic_part"
],
"when": {
"features": [
"multi_level_planar_profile",
"planar_dominant_body"
],
"relations": []
},
"guidance": "Treat the planar-dominant body and its multiple profile levels as one reusable structural composition.",
"semantic_rationale": "The composition describes topology without depending on an instance dimension.",
"support": 1,
"confidence": 1.0,
"check": "Confirm that the expected planar levels remain distinct and connected to the primary body.",
"promotion_state": "candidate",
"required_support": 20,
"remaining_support": 19,
"required_confidence": 0.8,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
},
{
"id": "validation.patterned_prismatic_part.repeated_feature_consistency",
"kind": "validation_rule",
"scope": [
"patterned_prismatic_part"
],
"when": {
"features": [
"repeated_axial_hole_pattern"
],
"relations": [
"repeated_radius_group"
]
},
"guidance": "Validate repeated feature membership, equality roles, and attachment to the intended host region.",
"semantic_rationale": "These checks remain meaningful across dimensions and pattern placements.",
"support": 1,
"confidence": 1.0,
"check": "Verify repeated-member count consistency, shared radius roles, and host connectivity.",
"promotion_state": "candidate",
"required_support": 20,
"remaining_support": 19,
"required_confidence": 0.8,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
}
]
}
@@ -25,14 +25,18 @@ published library and require repeated support before promoting any experience.
```
4. Read `work/review/semantic-batch.json` completely. Act as the semantic
distiller: compare families, features, relations, prior vocabulary, failure
rules, and validation targets; then author
distiller: compare families, parameter roles, datum roles, canonical
reconstruction stages, feature cardinality classes, relations, prior
vocabulary, failure rules, and validation targets; then author
`work/review/experience-draft.json` using
`references/llm-review-contract.md`.
Propose methods and invariants, not raw co-occurrence pairs. Do not infer
operation order, manufacturing intent, or failure causes that final B-Rep
evidence cannot support.
Propose methods, invariants, and family-level `reconstruction_grammar`
records, not raw co-occurrence pairs. A reconstruction grammar is a
canonical backend-neutral strategy inferred from repeated geometry; it is
not the source model's recovered feature history. Do not infer
manufacturing intent or failure causes that final B-Rep evidence cannot
support.
5. Publish only after the model draft exists:
@@ -70,10 +74,15 @@ required.
## STEP extraction boundary
- The extractor reads analytic B-Rep surfaces, bounding boxes, topology counts,
axes, radii, and locations into a private evidence record.
- It derives only semantic candidates such as rotational stacks, axis
relationships, transitions, and repeated-hole motifs.
- The V2 extractor reads analytic B-Rep surfaces, bounding boxes, topology
counts, axes, radii, and locations into a private evidence record.
- It derives scale-independent body classes, part families, rotational and
prismatic motifs, passage and pattern relationships, complexity roles, and
normalized ratio observations before sanitization.
- It also derives private exact cardinality evidence plus sanitized symbolic
parameter roles, datum roles, feature-count classes, and canonical
reconstruction stages. These are the evidence used to learn how to rebuild a
family without publishing any teacher dimensions.
- Exact source paths, hashes, dimensions, coordinates, and face references are
permitted in private case JSON and forbidden in the published library.
- Keep input/output as a human-visible staging area and induction corpus. Never
@@ -86,7 +95,8 @@ required.
- One case is evidence, not experience.
- Publish generalized, dimension-free observations below threshold as visible
`candidate_experiences`, but never expose them to generation consumers.
- Require `min_support` independent cases and `min_confidence` before promotion.
- Require `min_support` independent cases and conditional `min_confidence`
within the proposal's semantic context before promotion.
- Promote feature compositions, semantic constraints, dimensionless
distributions, validation checks, and failure-repair methods.
- Reject absolute dimensions, coordinates, face references, source paths,
@@ -105,6 +115,8 @@ The library is backend-neutral. A consumer receives:
- `dimensionless_distribution`: multi-case ratio ranges;
- `validation_rule`: checks independent of one part's coordinates;
- `failure_repair`: reusable detection and repair methods.
- `reconstruction_grammar`: symbolic parameters, datums, canonical feature
stages, optional/required feature roles, and validation roles for a family.
build123d, SimpleCADAPI, or another backend translates the same queried
experience into its own operations. Experience must never contain executable
@@ -1,4 +1,4 @@
interface:
display_name: "CAD Experience Builder"
short_description: "用大模型语义复核与证据门禁蒸馏可迁移的 CAD 通用经验"
default_prompt: "Use $cad-experience-builder to prepare sanitized STEP evidence, perform model-led semantic induction, publish only evidence-verified generalized CAD experience, and report candidate versus promoted results."
short_description: "蒸馏可迁移的 CAD 重建语法、约束与验证经验"
default_prompt: "Use $cad-experience-builder to prepare sanitized STEP evidence, induce family-level symbolic reconstruction grammars and reusable methods, publish only deterministically verified experience, and report candidate versus promoted results."
@@ -18,6 +18,25 @@ Supported fields:
"part_family": "flanged_hub_adapter",
"features": [{"type": "base_flange"}, {"type": "hollow_sleeve"}],
"constraints": [{"id": "coaxial_stack", "type": "coaxial"}],
"reconstruction_evidence": {
"parameter_roles": ["overall_long_span", "coaxial_diameter_roles"],
"datum_roles": ["part_center", "primary_axis"],
"feature_roles": ["rotational_body", "coaxial_cylindrical_stack"],
"relation_roles": ["coaxial_stack"],
"canonical_stages": [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": ["part_center", "primary_axis"]
}
],
"private_cardinality_evidence": {
"feature_role_count": 5,
"feature_role_count_class": "repeated"
},
"validation_roles": ["closed_solid", "coaxial_stack"]
},
"normalized_observations": [
{
"name": "sleeve_od_to_base_od",
@@ -52,3 +71,11 @@ likely instance answers. Ratios are accepted only through
Legacy distilled JSON can be used when it contains the fields above. Exact
`parameters`, `evidence`, centers, axes, and surface references are ignored.
V3 extractor output should also provide several independent semantic signals
per case: body-aspect class, topology-complexity role, analytic feature
networks, part family, relationship candidates, and role-named dimensionless
observations. It additionally provides symbolic reconstruction roles, canonical
reconstruction stages, and private exact cardinality evidence. Sanitization
converts exact counts into qualitative classes before LLM review. Do not encode
an absolute source dimension as a feature token.
@@ -62,3 +62,23 @@ case, positions, and coordinates are forbidden.
Consumers query this library by requested family and feature vocabulary. They
must not receive the induction corpus, private case JSON, or
`candidate_experiences`. Only `experiences` is executable guidance.
## Reconstruction grammar
`reconstruction_grammar` is the executable bridge between generalized
experience and a backend-neutral design plan. It may contain only:
- symbolic `parameter_roles`;
- symbolic `datum_roles`;
- required or optional semantic feature and relation roles;
- canonical stages with tokenized operation, feature, and reference roles;
- qualitative cardinality classes such as `single`, `paired`, `repeated`, or
`dense`;
- validation roles.
It must not contain parameter values, exact member counts, absolute
coordinates, source identifiers, face references, or executable teacher
geometry. The publisher verifies that every promoted role and stage is present
in enough independent private cases. A consumer instantiates the grammar using
the user's dimensions, inferred dimensionless distributions, and backend
operations.
@@ -17,7 +17,8 @@ Write `work/review/experience-draft.json` with this shape:
"scope": ["part_family"],
"evidence_query": {
"required_features": ["rotational_body", "central_passage"],
"required_relations": ["dominant_axis_alignment"]
"required_relations": ["dominant_axis_alignment"],
"context_features": ["rotational_body", "central_passage"]
},
"guidance": "Preserve a shared axis across the participating feature roles.",
"semantic_rationale": "The relationship remains meaningful when individual dimensions change.",
@@ -28,17 +29,68 @@ Write `work/review/experience-draft.json` with this shape:
```
Allowed kinds are `feature_motif`, `constraint`, `design_rule`,
`validation_rule`, and `failure_repair`.
`validation_rule`, `failure_repair`, `dimensionless_distribution`, and
`reconstruction_grammar`.
Distribution proposals also provide `observation_name`; the publisher computes
the distribution from private evidence and never accepts values from the draft.
A `reconstruction_grammar` proposal must additionally contain symbolic,
number-free reconstruction content:
```json
{
"id": "reconstruction.patterned_prismatic_part.primary",
"kind": "reconstruction_grammar",
"scope": ["patterned_prismatic_part"],
"evidence_query": {
"required_features": ["planar_dominant_body", "cylindrical_feature_network"],
"required_relations": [],
"context_features": ["planar_dominant_body"]
},
"guidance": "Build a datum-driven envelope before adding the passage network and repeated features.",
"semantic_rationale": "The staged method transfers across dimensional variants while preserving feature relationships.",
"reconstruction_grammar": {
"parameter_roles": ["overall_long_span", "overall_middle_span", "overall_short_span"],
"datum_roles": ["part_center", "primary_axis", "primary_transverse_plane"],
"feature_roles": ["planar_dominant_body", "cylindrical_feature_network"],
"relation_roles": [],
"canonical_stages": [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": ["part_center", "primary_axis"]
},
{
"id": "construct_primary_envelope",
"operation": "extrude_profile",
"feature_roles": ["planar_dominant_body"],
"reference_roles": ["part_center", "primary_axis"]
}
],
"cardinality_classes": ["plane_dense", "cylinder_dense"],
"validation_roles": ["closed_solid", "surface_mix_consistency"]
}
}
```
Follow these rules:
- Require an evidence query composed only of semantic feature and relation
tokens present in the sanitized batch.
- Use scopes to prevent unrelated part families from supporting a proposal.
- Use `context_features` to define the conditional population used for
confidence, so a valid subtype method is not diluted by unrelated variants
in the same broad family.
- Explain why a proposal transfers across dimensions and variants.
- Avoid dimensions, coordinates, source identifiers, face references, and
numeric literals in prose.
- Do not infer build order or causal failure mechanisms from final geometry.
- Treat `canonical_stages` as a recommended reconstruction order, never as a
claim about the source CAD history.
- For reconstruction grammars, retain only roles and stages repeated by the
scoped evidence. Do not insert family knowledge that the sanitized batch
does not support.
- Prefer a small set of meaningful methods over every possible feature pair.
- Preserve earlier proposal IDs when evidence still supports the same meaning.
- Let the publisher calculate support and confidence; never claim them in the
@@ -102,6 +102,71 @@ def list_tokens(value: Any) -> list[str]:
return sorted({item for raw in value if (item := token(raw)) != "unknown"})
def normalize_stage(raw: Any) -> dict[str, Any] | None:
if not isinstance(raw, dict):
return None
stage_id = token(raw.get("id"))
operation = token(raw.get("operation"))
if stage_id == "unknown" or operation == "unknown":
return None
return {
"id": stage_id,
"operation": operation,
"feature_roles": list_tokens(raw.get("feature_roles")),
"reference_roles": list_tokens(raw.get("reference_roles")),
}
def normalize_reconstruction(raw: Any) -> dict[str, Any]:
if not isinstance(raw, dict):
return {
"parameter_roles": [],
"datum_roles": [],
"feature_roles": [],
"relation_roles": [],
"canonical_stages": [],
"cardinality_classes": [],
"validation_roles": [],
}
private_cardinality = (
raw.get("private_cardinality_evidence")
if isinstance(raw.get("private_cardinality_evidence"), dict)
else {}
)
class_values = private_cardinality.get("surface_type_classes", {})
cardinality_classes = list_tokens(raw.get("cardinality_classes"))
if isinstance(class_values, dict):
cardinality_classes.extend(
token(f"{kind}_{value}")
for kind, value in class_values.items()
)
for role in (
"feature_role_count_class",
"coaxial_radius_role_count_class",
"repeated_member_count_class",
):
value = private_cardinality.get(role)
if value:
cardinality_classes.append(token(f"{role}.{value}"))
return {
"parameter_roles": list_tokens(raw.get("parameter_roles")),
"datum_roles": list_tokens(raw.get("datum_roles")),
"feature_roles": list_tokens(raw.get("feature_roles")),
"relation_roles": list_tokens(raw.get("relation_roles")),
"canonical_stages": [
stage
for item in raw.get("canonical_stages", [])
if (stage := normalize_stage(item)) is not None
]
if isinstance(raw.get("canonical_stages"), list)
else [],
"cardinality_classes": sorted(
{item for item in cardinality_classes if item != "unknown"}
),
"validation_roles": list_tokens(raw.get("validation_roles")),
}
def percentile(values: list[float], fraction: float) -> float:
ordered = sorted(values)
if len(ordered) == 1:
@@ -232,6 +297,9 @@ def normalize_case(payload: dict[str, Any]) -> dict[str, Any]:
"rules": rules,
"validations": validations,
"normalized_observations": observations,
"reconstruction": normalize_reconstruction(
design_ir.get("reconstruction_evidence")
),
}
@@ -574,7 +642,10 @@ def extract_folder(input_dir: Path, output_dir: Path) -> dict[str, Any]:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
digest = payload.get("provenance", {}).get("source_sha256")
if isinstance(digest, str):
if (
isinstance(digest, str)
and payload.get("extractor_version") == "3.0"
):
known_hashes[digest] = path
except (OSError, json.JSONDecodeError, AttributeError):
continue
@@ -637,6 +708,7 @@ def prepare_semantic_batch(
"relations": case["relations"],
"candidate_rules": case["rules"],
"validation_targets": case["validations"],
"reconstruction_evidence": case["reconstruction"],
"dimensionless_observation_roles": [
{
"name": item["name"],
@@ -678,6 +750,8 @@ def prepare_semantic_batch(
"design_rule",
"validation_rule",
"failure_repair",
"dimensionless_distribution",
"reconstruction_grammar",
],
},
}
@@ -693,6 +767,8 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
"design_rule",
"validation_rule",
"failure_repair",
"dimensionless_distribution",
"reconstruction_grammar",
}
for index, raw in enumerate(payload.get("proposals", [])):
if not isinstance(raw, dict):
@@ -707,6 +783,10 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
)
required_features = list_tokens(evidence.get("required_features"))
required_relations = list_tokens(evidence.get("required_relations"))
context_features = list_tokens(
evidence.get("context_features") or required_features
)
observation_name = token(raw.get("observation_name"))
guidance = string_without_instance_numbers(raw.get("guidance"))
rationale = string_without_instance_numbers(raw.get("semantic_rationale"))
if proposal_id == "unknown":
@@ -715,7 +795,11 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
raise ValueError(f"proposal {proposal_id} has an unsupported kind")
if not scopes:
raise ValueError(f"proposal {proposal_id} has no scope")
if not required_features and not required_relations:
if (
not required_features
and not required_relations
and observation_name == "unknown"
):
raise ValueError(f"proposal {proposal_id} has no evidence query")
if guidance is None or rationale is None:
raise ValueError(
@@ -728,10 +812,25 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
"evidence_query": {
"required_features": required_features,
"required_relations": required_relations,
"context_features": context_features,
},
"guidance": guidance,
"semantic_rationale": rationale,
}
if observation_name != "unknown":
proposal["observation_name"] = observation_name
if kind == "reconstruction_grammar":
grammar = normalize_reconstruction(raw.get("reconstruction_grammar"))
if (
not grammar["parameter_roles"]
or not grammar["datum_roles"]
or not grammar["canonical_stages"]
or not grammar["validation_roles"]
):
raise ValueError(
f"proposal {proposal_id} has an incomplete reconstruction grammar"
)
proposal["reconstruction_grammar"] = grammar
for key in ("check", "repair", "failure_signature"):
value = string_without_instance_numbers(raw.get(key))
if value is not None:
@@ -752,6 +851,41 @@ def build_reviewed_library(
promoted: list[dict[str, Any]] = []
candidates: list[dict[str, Any]] = []
family_counts = Counter(case["family"] for case in cases)
def supports_reconstruction_grammar(
case: dict[str, Any], grammar: dict[str, Any]
) -> bool:
evidence = case.get("reconstruction", {})
for key in (
"parameter_roles",
"datum_roles",
"feature_roles",
"relation_roles",
"validation_roles",
):
required = set(grammar.get(key, []))
available = set(evidence.get(key, []))
if required and not required.issubset(available):
return False
available_stages = {
(item["id"], item["operation"]): item
for item in evidence.get("canonical_stages", [])
}
for required_stage in grammar.get("canonical_stages", []):
key = (required_stage["id"], required_stage["operation"])
available = available_stages.get(key)
if available is None:
return False
if not set(required_stage.get("feature_roles", [])).issubset(
available.get("feature_roles", [])
):
return False
if not set(required_stage.get("reference_roles", [])).issubset(
available.get("reference_roles", [])
):
return False
return True
for proposal in proposals:
scope = set(proposal["scope"])
relevant = [
@@ -765,14 +899,80 @@ def build_reviewed_library(
required_relations = set(
proposal["evidence_query"]["required_relations"]
)
supporting = [
context_features = set(
proposal["evidence_query"].get("context_features", required_features)
)
if proposal["kind"] == "dimensionless_distribution":
matched = [
observation
for case in relevant
for observation in case["normalized_observations"]
if observation["name"] == proposal.get("observation_name")
]
support = len(matched)
item: dict[str, Any] = {
"id": proposal["id"],
"kind": proposal["kind"],
"scope": proposal["scope"],
"when": {
"ratio": {
"numerator_role": (
matched[0]["numerator_role"] if matched else "unknown"
),
"denominator_role": (
matched[0]["denominator_role"] if matched else "unknown"
),
}
},
"guidance": proposal["guidance"],
"semantic_rationale": proposal["semantic_rationale"],
"support": support,
"confidence": 1.0 if support else 0.0,
}
if matched:
values = [observation["value"] for observation in matched]
item["distribution"] = {
"sample_count": support,
"min": round(min(values), 6),
"p10": round(percentile(values, 0.10), 6),
"median": round(median(values), 6),
"p90": round(percentile(values, 0.90), 6),
"max": round(max(values), 6),
}
if eligible(support, item["confidence"], min_support, min_confidence):
promoted.append(item)
else:
item.pop("distribution", None)
item.update(
{
"promotion_state": "candidate",
"required_support": min_support,
"remaining_support": max(0, min_support - support),
"required_confidence": min_confidence,
"consumer_policy": "visible_for_review_but_not_available_to_cad_router",
}
)
candidates.append(item)
continue
contextual = [
case
for case in relevant
if context_features.issubset(case["features"])
]
supporting = [
case
for case in contextual
if required_features.issubset(case["features"])
and required_relations.issubset(case["relations"])
and (
proposal["kind"] != "reconstruction_grammar"
or supports_reconstruction_grammar(
case, proposal["reconstruction_grammar"]
)
)
]
support = len(supporting)
confidence = support / len(relevant) if relevant else 0.0
confidence = support / len(contextual) if contextual else 0.0
item: dict[str, Any] = {
"id": proposal["id"],
"kind": proposal["kind"],
@@ -789,6 +989,8 @@ def build_reviewed_library(
for key in ("check", "repair", "failure_signature"):
if key in proposal:
item[key] = proposal[key]
if proposal["kind"] == "reconstruction_grammar":
item["reconstruction_grammar"] = proposal["reconstruction_grammar"]
if eligible(support, confidence, min_support, min_confidence):
promoted.append(item)
else:
@@ -11,6 +11,8 @@ from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
EXTRACTOR_VERSION = "3.0"
def _rounded(value: float) -> float:
return round(float(value), 6)
@@ -42,6 +44,32 @@ def _radial_offset(location: list[float], center: list[float], axis: str) -> flo
location[second] - center[second],
)
def _ratio(numerator: float, denominator: float) -> float:
return _rounded(numerator / denominator) if denominator > 1e-9 else 0.0
def _observation(
name: str, value: float, numerator_role: str, denominator_role: str
) -> dict[str, Any]:
return {
"name": name,
"value": _rounded(value),
"numerator_role": numerator_role,
"denominator_role": denominator_role,
}
def _cardinality_class(count: int) -> str:
if count <= 0:
return "absent"
if count == 1:
return "single"
if count == 2:
return "paired"
if count <= 6:
return "repeated"
return "dense"
def _surface_record(face: Any, index: int) -> dict[str, Any]:
from OCP.BRepAdaptor import BRepAdaptor_Surface
@@ -125,6 +153,11 @@ def _semantic_summary(
axis_votes[_canonical_axis(surface["axis"])] += max(surface["area"], 1.0)
dominant_axis = axis_votes.most_common(1)[0][0] if axis_votes else "z"
scale = max(bbox_size) if bbox_size else 1.0
sorted_spans = sorted((max(float(value), 1e-9) for value in bbox_size))
short_span, middle_span, long_span = sorted_spans
long_to_middle = _ratio(long_span, middle_span)
middle_to_short = _ratio(middle_span, short_span)
short_to_long = _ratio(short_span, long_span)
coaxial_tolerance = max(scale * 0.005, 1e-4)
parallel_cylinders: list[dict[str, Any]] = []
@@ -152,6 +185,9 @@ def _semantic_summary(
repeated_group_sizes = sorted(
(len(rows) for rows in repeated_groups.values()), reverse=True
)
largest_repeated_group = (
max(repeated_groups.values(), key=len) if repeated_groups else []
)
parallel_cones = [
item for item in cones if _canonical_axis(item["axis"]) == dominant_axis
]
@@ -160,6 +196,66 @@ def _semantic_summary(
has_multi_axis_passages = any(
_canonical_axis(item["axis"]) != dominant_axis for item in cylinders
)
planes = [item for item in surfaces if item["type"] == "plane"]
toruses = [item for item in surfaces if item["type"] == "torus"]
spheres = [item for item in surfaces if item["type"] == "sphere"]
other_surfaces = [item for item in surfaces if item["type"] == "other"]
total_area = sum(max(float(item.get("area", 0.0)), 0.0) for item in surfaces)
planar_area = sum(max(float(item.get("area", 0.0)), 0.0) for item in planes)
cylindrical_area = sum(
max(float(item.get("area", 0.0)), 0.0) for item in cylinders
)
planar_fraction = _ratio(planar_area, total_area)
cylindrical_fraction = _ratio(cylindrical_area, total_area)
elongated = long_to_middle >= 3.0
plate_like = middle_to_short >= 4.0 and long_to_middle < 3.0
compact = long_to_middle < 2.0 and middle_to_short < 2.5
transverse_axes = {
"x": (1, 2),
"y": (0, 2),
"z": (0, 1),
}[dominant_axis]
transverse_span = max(bbox_size[index] for index in transverse_axes)
dominant_span = bbox_size[{"x": 0, "y": 1, "z": 2}[dominant_axis]]
largest_coaxial_radius = max(
(surface["radius"] for surface in coaxial_cylinders), default=0.0
)
smallest_coaxial_radius = min(
(surface["radius"] for surface in coaxial_cylinders), default=0.0
)
pattern_offsets = [
_radial_offset(surface["location"], bbox_center, dominant_axis)
for surface in largest_repeated_group
]
pattern_mean_offset = (
sum(pattern_offsets) / len(pattern_offsets) if pattern_offsets else 0.0
)
pattern_spread = (
max(pattern_offsets) - min(pattern_offsets) if pattern_offsets else 0.0
)
has_circular_pattern = bool(
len(pattern_offsets) >= 3
and pattern_mean_offset > coaxial_tolerance
and pattern_spread / pattern_mean_offset <= 0.08
)
# Multiple radii sharing a transverse center are a robust final-B-Rep
# signal for stepped bores/counterbores, without claiming operation order.
transverse_groups: dict[tuple[int, int, int], set[float]] = defaultdict(set)
axis_index = {"x": 0, "y": 1, "z": 2}[dominant_axis]
for surface in parallel_cylinders:
location = surface["location"]
key = (
int(round(location[transverse_axes[0]] / max(coaxial_tolerance, 1e-6))),
int(round(location[transverse_axes[1]] / max(coaxial_tolerance, 1e-6))),
axis_index,
)
transverse_groups[key].add(round(surface["radius"], 4))
has_stepped_cylindrical_passage = any(
len(radii) >= 2 for radii in transverse_groups.values()
)
features: list[dict[str, Any]] = []
if has_rotational_stack:
@@ -177,6 +273,51 @@ def _semantic_summary(
features.append({"type": "repeated_axial_hole_pattern"})
if has_multi_axis_passages:
features.append({"type": "multi_axis_passage"})
features.append(
{
"type": (
"elongated_body"
if elongated
else "plate_like_body"
if plate_like
else "compact_body"
if compact
else "moderate_aspect_body"
)
}
)
if planar_fraction >= 0.55:
features.append({"type": "planar_dominant_body"})
if cylindrical_fraction >= 0.55:
features.append({"type": "cylindrical_dominant_body"})
if len(planes) >= 6:
features.append({"type": "multi_level_planar_profile"})
if len(cylinders) >= 2:
features.append({"type": "cylindrical_feature_network"})
if toruses:
features.append({"type": "toroidal_blend_or_groove"})
if spheres:
features.append({"type": "spherical_surface_feature"})
if other_surfaces:
features.append({"type": "freeform_surface_region"})
if has_circular_pattern:
features.append({"type": "circular_equal_radius_pattern"})
if has_stepped_cylindrical_passage:
features.append({"type": "stepped_cylindrical_passage"})
if has_rotational_stack:
features.append({"type": "stepped_rotational_profile"})
if elongated:
features.append({"type": "shaft_like_body"})
if len(distinct_coaxial_radii) >= 3 and elongated:
features.append({"type": "sleeve_or_hollow_shaft_candidate"})
if transverse_span > 0 and dominant_span / transverse_span < 0.8:
features.append({"type": "flange_like_rotational_body"})
if len(surfaces) >= 40:
features.append({"type": "high_topological_complexity"})
elif len(surfaces) >= 16:
features.append({"type": "medium_topological_complexity"})
else:
features.append({"type": "low_topological_complexity"})
constraints: list[dict[str, Any]] = []
if has_rotational_stack:
@@ -194,15 +335,104 @@ def _semantic_summary(
constraints.append(
{"id": "transition_axis_continuity", "type": "axis_alignment"}
)
if has_circular_pattern:
constraints.append(
{"id": "common_pattern_radius", "type": "radial_pattern"}
)
if has_multi_axis_passages:
constraints.append(
{"id": "multiple_axis_system", "type": "axis_network"}
)
if has_stepped_cylindrical_passage:
constraints.append(
{"id": "shared_passage_axis", "type": "coaxial"}
)
if has_rotational_stack and parallel_cones and has_repeated_axial_holes:
family = "flanged_hub_adapter_candidate"
if has_rotational_stack and has_repeated_axial_holes:
family = "flanged_rotational_part"
elif has_rotational_stack and elongated and len(distinct_coaxial_radii) >= 3:
family = "hollow_or_stepped_shaft"
elif has_rotational_stack and elongated:
family = "shaft"
elif has_rotational_stack:
family = "rotational_part_candidate"
family = "rotational_part"
elif has_repeated_axial_holes and plate_like:
family = "patterned_plate"
elif has_repeated_axial_holes:
family = "patterned_prismatic_part_candidate"
family = "patterned_prismatic_part"
elif has_multi_axis_passages and len(cylinders) >= 3:
family = "multi_axis_manifold"
elif plate_like and planes:
family = "plate_or_bracket"
elif planar_fraction >= 0.55:
family = "prismatic_block"
elif other_surfaces or toruses or spheres:
family = "hybrid_freeform_part"
else:
family = "unclassified_part"
family = "general_mechanical_part"
observations = [
_observation(
"bbox_short_to_long",
short_to_long,
"short_bounding_span",
"long_bounding_span",
),
_observation(
"bbox_middle_to_long",
_ratio(middle_span, long_span),
"middle_bounding_span",
"long_bounding_span",
),
_observation(
"planar_area_fraction",
planar_fraction,
"planar_surface_area",
"total_surface_area",
),
_observation(
"cylindrical_area_fraction",
cylindrical_fraction,
"cylindrical_surface_area",
"total_surface_area",
),
]
if largest_coaxial_radius > 0:
observations.append(
_observation(
"largest_coaxial_radius_to_transverse_span",
_ratio(largest_coaxial_radius, transverse_span),
"largest_coaxial_radius",
"transverse_bounding_span",
)
)
if smallest_coaxial_radius > 0 and largest_coaxial_radius > 0:
observations.append(
_observation(
"smallest_to_largest_coaxial_radius",
_ratio(smallest_coaxial_radius, largest_coaxial_radius),
"smallest_coaxial_radius",
"largest_coaxial_radius",
)
)
if pattern_mean_offset > 0:
observations.append(
_observation(
"pattern_radius_to_transverse_span",
_ratio(pattern_mean_offset, transverse_span),
"pattern_radius",
"transverse_bounding_span",
)
)
if largest_repeated_group:
observations.append(
_observation(
"repeated_feature_radius_to_transverse_span",
_ratio(largest_repeated_group[0]["radius"], transverse_span),
"repeated_feature_radius",
"transverse_bounding_span",
)
)
summary = {
"dominant_axis": dominant_axis,
@@ -214,10 +444,240 @@ def _semantic_summary(
else 0,
"parallel_cone_surface_count": len(parallel_cones),
"has_multi_axis_passages": has_multi_axis_passages,
"shape_class": (
"elongated" if elongated else "plate_like" if plate_like else "compact"
if compact
else "moderate_aspect"
),
"normalized_observations": observations,
}
return family, features, constraints, summary
def _reconstruction_evidence(
surfaces: list[dict[str, Any]],
features: list[dict[str, Any]],
constraints: list[dict[str, Any]],
semantic_summary: dict[str, Any],
) -> dict[str, Any]:
"""Describe a canonical reconstruction grammar without claiming CAD history."""
feature_roles = sorted(
{
str(item.get("type"))
for item in features
if isinstance(item, dict) and item.get("type")
}
)
relation_roles = sorted(
{
str(item.get("id") or item.get("type"))
for item in constraints
if isinstance(item, dict) and (item.get("id") or item.get("type"))
}
)
surface_counts = Counter(item.get("type", "other") for item in surfaces)
parameter_roles = [
"overall_long_span",
"overall_middle_span",
"overall_short_span",
"primary_axis_span",
"primary_transverse_span",
"secondary_transverse_span",
]
if "cylindrical_feature_network" in feature_roles:
parameter_roles.extend(
[
"passage_diameter_roles",
"passage_axis_offset_roles",
"passage_extent_roles",
]
)
if "coaxial_cylindrical_stack" in feature_roles:
parameter_roles.extend(
[
"coaxial_diameter_roles",
"axial_segment_length_roles",
"shoulder_position_roles",
]
)
if "repeated_axial_hole_pattern" in feature_roles:
parameter_roles.extend(
[
"pattern_member_diameter_role",
"pattern_radius_or_spacing_role",
"pattern_angular_or_linear_phase_role",
]
)
if "multi_level_planar_profile" in feature_roles:
parameter_roles.extend(
[
"planar_level_offset_roles",
"profile_width_roles",
"profile_length_roles",
]
)
if "conical_transition" in feature_roles:
parameter_roles.append("transition_slope_role")
stages: list[dict[str, Any]] = [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": [
"part_center",
"primary_axis",
"primary_transverse_plane",
"secondary_transverse_plane",
],
},
{
"id": "construct_primary_envelope",
"operation": (
"revolve_profile"
if "rotational_body" in feature_roles
else "extrude_profile"
),
"feature_roles": [
role
for role in (
"rotational_body",
"planar_dominant_body",
"elongated_body",
"plate_like_body",
"compact_body",
"moderate_aspect_body",
)
if role in feature_roles
],
"reference_roles": ["part_center", "primary_axis"],
},
]
if "multi_level_planar_profile" in feature_roles:
stages.append(
{
"id": "establish_planar_levels",
"operation": "add_or_remove_profile_levels",
"feature_roles": ["multi_level_planar_profile"],
"reference_roles": ["primary_transverse_plane"],
}
)
if "coaxial_cylindrical_stack" in feature_roles:
stages.append(
{
"id": "construct_coaxial_stack",
"operation": "add_or_cut_coaxial_profiles",
"feature_roles": ["coaxial_cylindrical_stack"],
"reference_roles": ["primary_axis"],
}
)
if "cylindrical_feature_network" in feature_roles:
stages.append(
{
"id": "construct_passage_network",
"operation": "cut_semantic_passages",
"feature_roles": [
role
for role in (
"cylindrical_feature_network",
"central_passage_candidate",
"stepped_cylindrical_passage",
"multi_axis_passage",
)
if role in feature_roles
],
"reference_roles": ["primary_axis", "part_center"],
}
)
if "repeated_axial_hole_pattern" in feature_roles:
stages.append(
{
"id": "construct_repeated_feature_pattern",
"operation": "pattern_semantic_feature",
"feature_roles": [
role
for role in (
"repeated_axial_hole_pattern",
"circular_equal_radius_pattern",
)
if role in feature_roles
],
"reference_roles": ["primary_axis", "part_center"],
}
)
transition_roles = [
role
for role in (
"conical_transition",
"toroidal_blend_or_groove",
"spherical_surface_feature",
"freeform_surface_region",
)
if role in feature_roles
]
if transition_roles:
stages.append(
{
"id": "resolve_transitions_and_finishing",
"operation": "apply_transitions_or_blends",
"feature_roles": transition_roles,
"reference_roles": ["primary_axis"],
}
)
stages.append(
{
"id": "validate_reconstructed_shape",
"operation": "validate_geometry_and_relations",
"feature_roles": feature_roles,
"reference_roles": ["part_center", "primary_axis"],
}
)
return {
"interpretation": "canonical_reconstruction_plan_not_recovered_history",
"parameter_roles": sorted(set(parameter_roles)),
"datum_roles": [
"part_center",
"primary_axis",
"primary_transverse_plane",
"secondary_transverse_plane",
],
"feature_roles": feature_roles,
"relation_roles": relation_roles,
"canonical_stages": stages,
"private_cardinality_evidence": {
"surface_type_counts": dict(sorted(surface_counts.items())),
"surface_type_classes": {
kind: _cardinality_class(count)
for kind, count in sorted(surface_counts.items())
},
"feature_role_count": len(feature_roles),
"feature_role_count_class": _cardinality_class(len(feature_roles)),
"coaxial_radius_role_count": int(
semantic_summary.get("distinct_coaxial_radius_count", 0)
),
"coaxial_radius_role_count_class": _cardinality_class(
int(semantic_summary.get("distinct_coaxial_radius_count", 0))
),
"repeated_member_count": int(
semantic_summary.get("largest_repeated_radius_group", 0)
),
"repeated_member_count_class": _cardinality_class(
int(semantic_summary.get("largest_repeated_radius_group", 0))
),
},
"validation_roles": sorted(
{
"closed_solid",
"bounding_proportion_consistency",
"surface_mix_consistency",
*relation_roles,
}
),
}
def extract_step_case(source: Path) -> dict[str, Any]:
from build123d import import_step
@@ -239,9 +699,14 @@ def extract_step_case(source: Path) -> dict[str, Any]:
family, features, constraints, semantic_summary = _semantic_summary(
surfaces, bbox_center, bbox_size
)
reconstruction_evidence = _reconstruction_evidence(
surfaces, features, constraints, semantic_summary
)
digest = hashlib.sha256(source_bytes).hexdigest()
normalized_observations = semantic_summary.pop("normalized_observations", [])
return {
"schema_version": "1.0",
"schema_version": "2.0",
"extractor_version": EXTRACTOR_VERSION,
"case_kind": "private_step_evidence",
"case_id": digest,
"provenance": {
@@ -267,8 +732,9 @@ def extract_step_case(source: Path) -> dict[str, Any]:
"part_family": family,
"features": features,
"constraints": constraints,
"normalized_observations": [],
"normalized_observations": normalized_observations,
"semantic_summary": semantic_summary,
"reconstruction_evidence": reconstruction_evidence,
},
"experience": {
"rules": [],
@@ -54,6 +54,38 @@ def make_case(index: int) -> dict:
"denominator_role": "base_outer_diameter",
}
],
"reconstruction_evidence": {
"parameter_roles": [
"overall_long_span",
"overall_middle_span",
"overall_short_span",
],
"datum_roles": ["part_center", "primary_axis"],
"feature_roles": ["base_flange", "hollow_sleeve", "counterbore"],
"relation_roles": ["coaxial_stack"],
"canonical_stages": [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": ["part_center", "primary_axis"],
},
{
"id": "construct_primary_envelope",
"operation": "revolve_profile",
"feature_roles": ["base_flange", "hollow_sleeve"],
"reference_roles": ["primary_axis"],
},
],
"private_cardinality_evidence": {
"surface_type_classes": {
"plane": "repeated",
"cylinder": "dense",
},
"feature_role_count_class": "repeated",
},
"validation_roles": ["closed_solid", "coaxial_stack"],
},
},
"experience": {
"rules": [
@@ -122,12 +154,47 @@ class CadExperienceTest(unittest.TestCase):
family, features, constraints, summary = STEP_MODULE._semantic_summary(
surfaces, [0.0, 0.0, 0.0], [40.0, 20.0, 40.0]
)
self.assertEqual("flanged_hub_adapter_candidate", family)
self.assertEqual("flanged_rotational_part", family)
self.assertIn(
"coaxial_cylindrical_stack", {item["type"] for item in features}
)
self.assertIn("coaxial_stack", {item["id"] for item in constraints})
self.assertEqual("y", summary["dominant_axis"])
self.assertTrue(summary["normalized_observations"])
reconstruction = STEP_MODULE._reconstruction_evidence(
surfaces, features, constraints, summary
)
self.assertIn("overall_long_span", reconstruction["parameter_roles"])
self.assertTrue(reconstruction["canonical_stages"])
self.assertEqual(
"canonical_reconstruction_plan_not_recovered_history",
reconstruction["interpretation"],
)
def test_conditional_feature_context_does_not_dilute_valid_method(self):
proposal = self.reviewed_proposal()
proposal["evidence_query"]["context_features"] = [
"base_flange",
"hollow_sleeve",
]
target = [MODULE.normalize_case(make_case(index)) for index in range(20)]
unrelated = []
for index in range(80):
payload = make_case(index + 100)
payload["design_ir"]["features"] = [{"type": "base_flange"}]
payload["design_ir"]["constraints"] = []
unrelated.append(MODULE.normalize_case(payload))
library = MODULE.build_reviewed_library(
target + unrelated,
[MODULE.normalize_proposals({
"draft_kind": "llm_generalized_experience_proposals",
"proposals": [proposal],
})[0]],
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual(1, len(library["experiences"]))
def test_single_case_never_promotes(self):
case = MODULE.normalize_case(make_case(1))
@@ -247,6 +314,65 @@ class CadExperienceTest(unittest.TestCase):
}
)
def test_reconstruction_grammar_promotes_without_instance_values(self):
proposal = {
"id": "reconstruction.flanged_hub_adapter.primary",
"kind": "reconstruction_grammar",
"scope": ["flanged_hub_adapter"],
"evidence_query": {
"required_features": ["base_flange", "hollow_sleeve"],
"required_relations": ["coaxial_stack"],
"context_features": ["base_flange", "hollow_sleeve"],
},
"guidance": "Establish shared datums before composing the primary envelope.",
"semantic_rationale": "The symbolic stages transfer across dimensional variants.",
"reconstruction_grammar": {
"parameter_roles": [
"overall_long_span",
"overall_middle_span",
"overall_short_span",
],
"datum_roles": ["part_center", "primary_axis"],
"feature_roles": ["base_flange", "hollow_sleeve"],
"relation_roles": ["coaxial_stack"],
"canonical_stages": [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": ["part_center", "primary_axis"],
},
{
"id": "construct_primary_envelope",
"operation": "revolve_profile",
"feature_roles": ["base_flange", "hollow_sleeve"],
"reference_roles": ["primary_axis"],
},
],
"cardinality_classes": ["plane_repeated", "cylinder_dense"],
"validation_roles": ["closed_solid", "coaxial_stack"],
},
}
normalized = MODULE.normalize_proposals(
{
"draft_kind": "llm_generalized_experience_proposals",
"proposals": [proposal],
}
)
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
library = MODULE.build_reviewed_library(
cases,
normalized,
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual([], MODULE.audit_library(library))
self.assertEqual("reconstruction_grammar", library["experiences"][0]["kind"])
rendered = json.dumps(library)
self.assertNotIn("91.0", rendered)
self.assertNotIn('"parameters"', rendered)
def test_direct_distill_without_model_draft_is_blocked(self):
with self.assertRaisesRegex(SystemExit, "statistical distillation is disabled"):
MODULE.main(["distill"])
Binary file not shown.
Binary file not shown.
+319
View File
@@ -0,0 +1,319 @@
from pathlib import Path
from docx import Document
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
from build_cnc_first_integration_checklist import (
AMBER,
BLACK,
BLUE,
GRAY,
GREEN,
LIGHT_BLUE,
PALE_BLUE,
WHITE,
add_callout,
add_text,
keep_row_together,
set_cell_margins,
set_cell_shading,
set_repeat_table_header,
set_table_fixed_width,
)
OUT = Path("/Users/jerry/linkhand/CadSet/docs/CNC机床系统对接资料与技术支持需求函.docx")
def setup_document():
doc = Document()
section = doc.sections[0]
section.page_width = Inches(8.5)
section.page_height = Inches(11)
section.top_margin = Inches(0.7)
section.bottom_margin = Inches(0.7)
section.left_margin = Inches(0.72)
section.right_margin = Inches(0.72)
section.header_distance = Inches(0.3)
section.footer_distance = Inches(0.3)
normal = doc.styles["Normal"]
normal.font.name = "Arial"
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
normal.font.size = Pt(10.5)
normal.font.color.rgb = RGBColor.from_string(BLACK)
normal.paragraph_format.space_after = Pt(5)
normal.paragraph_format.line_spacing = 1.12
for name, size, before, after in (
("Heading 1", 16, 14, 7),
("Heading 2", 12.5, 10, 5),
("Heading 3", 11, 7, 3),
):
style = doc.styles[name]
style.font.name = "Arial"
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
style.font.size = Pt(size)
style.font.bold = True
style.font.color.rgb = RGBColor.from_string(BLUE)
style.paragraph_format.space_before = Pt(before)
style.paragraph_format.space_after = Pt(after)
style.paragraph_format.keep_with_next = True
for name in ("List Bullet", "List Number"):
style = doc.styles[name]
style.font.name = "Arial"
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
style.font.size = Pt(10.5)
hp = section.header.paragraphs[0]
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
add_text(hp, "CNC 机床系统对接|资料与技术支持需求函", size=8.5, color=GRAY)
fp = section.footer.paragraphs[0]
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(fp, "请按实际设备型号填写;不适用项目请标注“不适用”", size=8, color=GRAY)
return doc
def formal_matrix(doc, headers, widths, rows, first_col_fill=None, sizes=None):
table = doc.add_table(rows=1, cols=len(headers))
table.style = "Table Grid"
set_table_fixed_width(table, widths)
for idx, text in enumerate(headers):
cell = table.rows[0].cells[idx]
set_cell_shading(cell, BLUE)
set_cell_margins(cell)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, text, size=9.2, bold=True, color=WHITE)
set_repeat_table_header(table.rows[0])
for ridx, values in enumerate(rows):
row = table.add_row()
keep_row_together(row)
for idx, value in enumerate(values):
cell = row.cells[idx]
set_cell_margins(cell, top=100, bottom=100)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
if idx == 0 and first_col_fill:
set_cell_shading(cell, first_col_fill)
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if idx == 0 else WD_ALIGN_PARAGRAPH.LEFT
size = sizes[idx] if sizes else 9.2
add_text(p, value, size=size, bold=(idx == 0))
return table
def add_form_lines(doc, pairs):
table = doc.add_table(rows=0, cols=2)
table.style = "Table Grid"
set_table_fixed_width(table, [3.25, 3.25])
for left, right in pairs:
row = table.add_row()
keep_row_together(row)
for idx, value in enumerate((left, right)):
cell = row.cells[idx]
set_cell_margins(cell, top=120, bottom=120)
add_text(cell.paragraphs[0], value, size=9.5)
return table
def build():
doc = setup_document()
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(12)
p.paragraph_format.space_after = Pt(4)
add_text(p, "CNC 机床系统对接", size=23, bold=True, color=BLUE)
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(12)
add_text(p, "资料与技术支持需求函", size=20, bold=True, color=BLUE)
doc.add_heading("一、需求目的", level=1)
p = doc.add_paragraph()
add_text(
p,
"为开展数字零件模型至 CNC 加工程序生成、程序传输、试切验证及加工状态采集等工作,"
"现请贵方协助确认实际机床和 CNC 控制系统配置,并提供本函所列技术资料、接口信息、"
"验证样例及联合调试支持。",
)
p = doc.add_paragraph()
add_text(
p,
"本函中的通信产品和接口名称均为候选项,最终以实际机床型号、CNC 系统版本、已购选件、"
"机床制造商配置及贵方书面确认为准。",
color=GRAY,
)
add_callout(
doc,
"回复方式",
"请在对应表格中填写、勾选或标注“不支持/不适用/待确认”,并随回复附相关手册、"
"安装包、许可证说明、样例文件和技术联系人信息。",
PALE_BLUE,
)
doc.add_heading("二、建议参与对接的技术人员", level=1)
role_rows = [
("机床制造商/集成商", "机械配置、轴与主轴、刀库/夹具、专用 M 代码、PLC/联锁、机床安全和现场调试。", "姓名:________ 电话:________"),
("三菱 CNC/代理商", "CNC 型号与版本、已购选件、通信软件、SDK、许可证、标准接口和版本兼容。", "姓名:________ 电话:________"),
("现场工艺/设备人员", "现用 CAM、后处理器、刀具、材料、装夹、坐标、切削参数和已验证程序。", "姓名:________ 电话:________"),
("网络/信息化人员", "工业网络、IP/端口、账号、证书、远程访问、日志和数据合规。", "姓名:________ 电话:________"),
]
formal_matrix(doc, ["参与角色", "确认范围", "联系人"], [1.45, 3.70, 1.35], role_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("三、设备与 CNC 基础信息", level=1)
equipment_rows = [
("机床身份", "制造商、完整型号、序列号、出厂年份、设备编号", "____________________________", "铭牌及整机照片"),
("CNC 控制系统", "三菱 CNC 系列、完整型号、系统软件版本", "____________________________", "系统信息页面照片"),
("已安装选件", "高速加工、五轴、刀尖点控制、宏程序、通信等选件", "____________________________", "选件清单/参数页面"),
("机床类型", "车床/铣床/加工中心/车铣复合/磨床/专机", "____________________________", "设备规格书"),
("轴配置", "轴名称、正方向、行程、零点、软限位、旋转轴结构", "____________________________", "轴配置图"),
("主轴配置", "数量、方向、最高转速、功率/扭矩、锥度", "____________________________", "主轴规格"),
("刀库/刀塔", "类型、容量、刀位规则、刀具尺寸重量限制", "____________________________", "刀库规格"),
("辅助装置", "探针、自动对刀、第四/第五轴、尾座、卡盘、托盘等", "____________________________", "配置说明"),
]
formal_matrix(
doc,
["项目", "请确认内容", "贵方填写", "建议附件"],
[1.15, 2.65, 1.55, 1.15],
equipment_rows,
first_col_fill=LIGHT_BLUE,
sizes=[9.0, 8.8, 8.8, 8.5],
)
doc.add_heading("四、请提供的技术资料", level=1)
material_rows = [
("P0", "CNC 与机床手册", "CNC 规格/编程/操作/连接设置手册;机床厂操作、维护及专用功能手册。", "□已附 □后补 □不适用"),
("P0", "代码和程序规则", "实际支持的 G/M/T/S/F 代码、自定义 M 代码、宏变量、固定循环、坐标系、刀补、换刀规则、程序格式限制。", "□已附 □后补 □不适用"),
("P0", "程序模板", "机床厂认可的程序开头、换刀段、工序切换、异常退出和结束模板。", "□已附 □后补 □不适用"),
("P0", "验证样例包", "至少 3~10 套已加工成功样例,每套对应 STEP/图纸、毛坯、材料、刀具、装夹、CAM、NC 和检测结果。", "数量:____套"),
("P0", "现用 CAM/后处理", "CAM 名称和版本、后处理器、后处理配置、程序头尾模板和典型仿真项目。", "□已附 □后补 □无"),
("P1", "刀具与刀柄", "真实刀具/刀柄清单、刀位号、刀补号、主要尺寸、寿命规则及 STEP/尺寸模型。", "□已附 □后补"),
("P1", "夹具与工作区", "工作台、卡盘、虎钳、软爪、尾座、旋转轴、探针和防护区域的模型或关键尺寸。", "□已附 □后补"),
("P1", "材料与工艺", "常用材料牌号、毛坯规格、余量、硬度/热处理、切削参数、冷却方式、公差和粗糙度。", "□已附 □后补"),
("P2", "数字机床", "机床三维模型、运动学、回转中心、碰撞体、软限位、CAM 机床仿真工程。", "□已附 □后补 □无"),
]
formal_matrix(doc, ["优先级", "资料名称", "交付内容", "回复"], [0.70, 1.45, 3.30, 1.05], material_rows, first_col_fill=GREEN)
doc.add_heading("五、程序传输与通信能力确认", level=1)
interface_rows = [
("加工程序传输", "NC Explorer", "□已安装 □可新增 □不支持 □待确认", "版本/许可证/手册/网络配置"),
("加工程序传输", "FTP、共享目录或机床厂文件服务", "□支持 □不支持 □待确认", "IP/端口/目录/账号/文件规则"),
("加工程序传输", "DNC/MDC/MES 平台", "□已有 □无 □待确认", "产品名称、版本、第三方接口"),
("加工程序传输", "RS-232C 或 USB", "□支持 □不支持", "传输设置与文件限制"),
("CNC 通信 SDK", "FCSB1224W000 开发包", "□已授权 □可采购 □不支持 □待确认", "SDK、手册、样例、兼容矩阵"),
("CNC 通信运行库", "FCSB1224W100", "□已授权 □可采购 □不支持 □待确认", "安装包、版本和部署许可"),
("只读数据采集", "MTConnect", "□已配置 □可新增 □不支持 □待确认", "适配器/Agent、数据项和端点"),
("只读数据采集", "OPC UA / NC Machine Tool Connector", "□已配置 □可新增 □不支持 □待确认", "服务器、节点表、证书、许可"),
("运行监控", "NC Monitor2 或现有监控平台", "□已有 □无 □待确认", "产品版本与开放接口"),
("PLC/设备访问", "SLMP 或机床厂专用接口", "□开放只读 □开放读写 □不开放 □待确认", "设备地址表、权限和联锁说明"),
("机床厂专用 API", "机床厂二次开发接口", "□有 □无 □待确认", "API 文档、SDK、样例和授权"),
]
formal_matrix(
doc,
["用途", "候选通道", "贵方确认", "请附资料"],
[1.15, 1.80, 1.95, 1.60],
interface_rows,
first_col_fill=LIGHT_BLUE,
sizes=[8.6, 8.6, 8.3, 8.3],
)
doc.add_heading("六、接口交付要求", level=1)
interface_delivery_rows = [
("兼容性", "适用 CNC 型号、系统版本、所需硬件/选件、安装状态、许可证数量和有效期。", "____________________________"),
("开发资料", "API/协议手册、数据字典、错误码、示例程序、SDK/运行库安装包及版本说明。", "____________________________"),
("连接参数", "协议、IP、端口、账号/证书、读写权限、超时、并发、频率和文件大小限制。", "____________________________"),
("可靠性", "断线检测、重连、续传、重复提交处理、任务 ID、校验、回执和错误处理机制。", "____________________________"),
("时间与单位", "时区、时间同步、时间戳精度、字段单位、缩放系数和枚举值说明。", "____________________________"),
("版本支持", "SDK/协议与 CNC 兼容矩阵、升级策略、补丁和技术支持期限。", "____________________________"),
("日志审计", "连接、文件传输、调用方、操作时间、结果、配置修改及远程访问日志。", "____________________________"),
]
formal_matrix(doc, ["项目", "交付要求", "贵方回复/附件"], [1.25, 3.85, 1.40], interface_delivery_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("七、状态数据项确认", level=1)
p = doc.add_paragraph()
add_text(
p,
"请针对可提供的数据项另附数据字典,并至少标明:接口来源、字段/节点地址、数据类型、单位、"
"刷新周期、时间戳、枚举含义、无效值及报警条件。",
color=GRAY,
)
data_rows = [
("设备状态", "设备 ID、型号/版本、在线/离线、采集状态、最后更新时间", "□可提供 □部分 □不支持"),
("运行状态", "自动/手动/MDI、运行/暂停/停止/报警/急停、循环启动、门和关键联锁", "□可提供 □部分 □不支持"),
("程序执行", "当前程序、子程序、行号/序列号、开始结束时间、循环时间、加工计数", "□可提供 □部分 □不支持"),
("轴与坐标", "机床/工件坐标、剩余移动、进给、倍率、轴负载、旋转轴角度", "□可提供 □部分 □不支持"),
("主轴", "实际/指令转速、倍率、负载、扭矩/功率、方向、状态、温度", "□可提供 □部分 □不支持"),
("刀具", "当前刀号、刀位、刀补、寿命、使用次数、刀具报警及刀具表更新", "□可提供 □部分 □不支持"),
("报警诊断", "报警号、等级、文本、发生/解除时间、历史、CNC 与机床 PLC 报警", "□可提供 □部分 □不支持"),
("加工质量", "完工状态、实际周期、测头/尺寸结果、报废返工原因", "□可提供 □部分 □不支持"),
("维护能耗", "运行时长、润滑/冷却、维护计数、能耗等设备已有数据", "□可提供 □部分 □不支持"),
]
formal_matrix(doc, ["数据类别", "期望数据项", "贵方确认"], [1.20, 4.05, 1.25], data_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("八、网络与安全配合", level=1)
security_rows = [
("网络拓扑", "机床网段、工业交换机、防火墙/隔离区、可否新增工控机及跨网访问方式。", "□可提供 □待确认"),
("端口白名单", "协议、源/目的 IP、TCP/UDP 端口、连接方向和网络变更流程。", "□可提供 □待确认"),
("账号与证书", "角色权限、密码策略、证书签发/更新、密钥保存和账号停用流程。", "□可提供 □待确认"),
("远程支持", "VPN/堡垒机/厂商远程服务、审批、开放时间、审计和紧急断开。", "□允许 □不允许 □待确认"),
("备份恢复", "CNC 参数、PLC、刀补、宏变量和接口配置的备份、回退及恢复负责人。", "□可配合 □待确认"),
("数据合规", "可采集范围、保存期限、图纸/NC 程序保密等级、外发和云端限制。", "□可提供 □待确认"),
]
formal_matrix(doc, ["项目", "请确认内容", "贵方回复"], [1.25, 4.00, 1.25], security_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("九、联合调试与验收支持", level=1)
acceptance_rows = [
("资料审查", "型号、手册、代码表、样例、接口和许可证资料齐全;共同确认未知项和责任人。", "□可配合"),
("离线验证", "使用验证样例生成 NC,完成 CAM/CNC 仿真和现场程序对比。", "□可配合 仿真环境:________"),
("程序传输", "验证上传、下载、覆盖保护、文件名、校验、断线重试和回执,不启动机床。", "□可配合"),
("空运行", "在机床锁定/抬刀/单段/低倍率条件下核对坐标、刀具、换刀、行程和安全位置。", "□可配合"),
("试切", "使用约定毛坯、刀具和装夹加工样件,记录周期、报警、刀具和检测结果。", "□可配合"),
("状态采集", "验证约定数据项、时间戳、连续采集、断线恢复和报警记录。", "□可配合"),
("试运行", "限定设备和零件范围运行,统计成功率、人工干预、报警和返工。", "□可配合"),
]
formal_matrix(doc, ["阶段", "配合与验收内容", "贵方确认"], [1.10, 4.20, 1.20], acceptance_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("十、首批交付成果", level=1)
deliverable_rows = [
("P0", "设备接口基线表", "每台机床的完整型号、CNC 版本、轴/主轴/刀库、选件、程序传输、可用接口、网络和联系人。", "□可提供"),
("P0", "机床编程资料包", "CNC/机床手册、G/M 代码、专用 M 代码、程序模板、坐标/刀补/换刀规则。", "□可提供"),
("P0", "验证样例包", "3~10 套 STEP/图纸、毛坯、刀具、装夹、CAM、NC 和检测结果对应的完整样例。", "□可提供"),
("P1", "通信接口包", "SDK/运行库或协议、许可、样例、数据字典、节点/地址表、错误码和兼容矩阵。", "□可提供"),
("P1", "测试验收环境", "离线仿真或测试机、账号/目录、网络窗口、备份、现场人员及验收样件。", "□可提供"),
("P2", "数字机床资料包", "机床、刀具、刀柄、夹具和毛坯模型;运动学、软限位、碰撞体和 CAM 仿真工程。", "□可提供"),
]
formal_matrix(doc, ["优先级", "成果名称", "内容", "确认"], [0.70, 1.45, 3.30, 1.05], deliverable_rows, first_col_fill=GREEN)
doc.add_heading("十一、商务授权与支持信息", level=1)
commercial_rows = [
("软件与接口授权", "SDK、运行库、OPC UA/MTConnect、仿真软件、后处理器是否收费及授权方式", "____________________________"),
("现场服务", "现场调试服务内容、人员、预计工期及费用", "____________________________"),
("培训", "操作、编程、通信接口、备份恢复和安全联调培训", "____________________________"),
("维护支持", "服务时间、响应时限、升级路径、远程/现场支持和年度维护", "____________________________"),
("交付介质", "安装包、许可证、手册、样例代码、版本号、校验值和下载期限", "____________________________"),
]
formal_matrix(doc, ["项目", "请确认内容", "贵方回复"], [1.35, 3.65, 1.50], commercial_rows, first_col_fill=LIGHT_BLUE)
doc.add_heading("附:建议资料包目录", level=1)
add_callout(
doc,
"建议压缩包结构",
"01_设备铭牌与系统照片;02_机床与CNC手册;03_GM代码与机床专用说明;04_已验证样例;"
"05_CAM与后处理;06_刀具夹具材料工艺;07_通信软件与接口文档;08_网络与测试环境;"
"09_授权商务信息;10_联系人。",
LIGHT_BLUE,
)
OUT.parent.mkdir(parents=True, exist_ok=True)
doc.save(OUT)
print(OUT)
if __name__ == "__main__":
build()
@@ -0,0 +1,567 @@
from pathlib import Path
from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor
OUT = Path("/Users/jerry/linkhand/CadSet/docs/CNC机床系统对接技术需求清单.docx")
BLUE = "1F4E78"
LIGHT_BLUE = "DCE6F1"
PALE_BLUE = "EEF4F8"
GRAY = "666666"
LIGHT_GRAY = "F2F2F2"
GREEN = "E2F0D9"
AMBER = "FFF2CC"
RED = "FCE4D6"
WHITE = "FFFFFF"
BLACK = "111111"
def set_cell_shading(cell, fill):
tc_pr = cell._tc.get_or_add_tcPr()
shd = tc_pr.find(qn("w:shd"))
if shd is None:
shd = OxmlElement("w:shd")
tc_pr.append(shd)
shd.set(qn("w:fill"), fill)
def set_cell_margins(cell, top=90, start=110, bottom=90, end=110):
tc = cell._tc
tc_pr = tc.get_or_add_tcPr()
tc_mar = tc_pr.first_child_found_in("w:tcMar")
if tc_mar is None:
tc_mar = OxmlElement("w:tcMar")
tc_pr.append(tc_mar)
for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
node = tc_mar.find(qn(f"w:{margin}"))
if node is None:
node = OxmlElement(f"w:{margin}")
tc_mar.append(node)
node.set(qn("w:w"), str(value))
node.set(qn("w:type"), "dxa")
def set_repeat_table_header(row):
tr_pr = row._tr.get_or_add_trPr()
tbl_header = OxmlElement("w:tblHeader")
tbl_header.set(qn("w:val"), "true")
tr_pr.append(tbl_header)
def keep_row_together(row):
tr_pr = row._tr.get_or_add_trPr()
cant_split = OxmlElement("w:cantSplit")
tr_pr.append(cant_split)
def set_table_fixed_width(table, widths):
table.autofit = False
table.alignment = WD_TABLE_ALIGNMENT.CENTER
tbl_pr = table._tbl.tblPr
tbl_layout = tbl_pr.find(qn("w:tblLayout"))
if tbl_layout is None:
tbl_layout = OxmlElement("w:tblLayout")
tbl_pr.append(tbl_layout)
tbl_layout.set(qn("w:type"), "fixed")
for row in table.rows:
for idx, width in enumerate(widths):
row.cells[idx].width = Inches(width)
tc_pr = row.cells[idx]._tc.get_or_add_tcPr()
tc_w = tc_pr.find(qn("w:tcW"))
if tc_w is None:
tc_w = OxmlElement("w:tcW")
tc_pr.append(tc_w)
tc_w.set(qn("w:w"), str(int(width * 1440)))
tc_w.set(qn("w:type"), "dxa")
def set_run(run, size=10.5, bold=False, color=BLACK, font="Arial"):
run.font.name = font
run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), font)
run.font.size = Pt(size)
run.bold = bold
run.font.color.rgb = RGBColor.from_string(color)
def add_text(paragraph, text, size=10.5, bold=False, color=BLACK):
run = paragraph.add_run(text)
set_run(run, size=size, bold=bold, color=color)
return run
def add_bullet(doc, text, level=0, bold_prefix=None):
p = doc.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2")
p.paragraph_format.space_after = Pt(3)
p.paragraph_format.line_spacing = 1.12
if bold_prefix and text.startswith(bold_prefix):
add_text(p, bold_prefix, bold=True)
add_text(p, text[len(bold_prefix):])
else:
add_text(p, text)
return p
def add_checkbox_line(doc, text, note=None):
p = doc.add_paragraph()
p.paragraph_format.left_indent = Inches(0.15)
p.paragraph_format.first_line_indent = Inches(-0.15)
p.paragraph_format.space_after = Pt(3)
add_text(p, "", size=11.5, bold=True, color=BLUE)
add_text(p, text, bold=True)
if note:
add_text(p, f" {note}", color=GRAY)
def add_callout(doc, label, text, fill=PALE_BLUE):
table = doc.add_table(rows=1, cols=1)
set_table_fixed_width(table, [6.5])
cell = table.cell(0, 0)
set_cell_shading(cell, fill)
set_cell_margins(cell, top=120, start=150, bottom=120, end=150)
p = cell.paragraphs[0]
p.paragraph_format.space_after = Pt(0)
add_text(p, f"{label}", bold=True, color=BLUE)
add_text(p, text)
doc.add_paragraph().paragraph_format.space_after = Pt(1)
def add_requirement_table(doc, rows):
table = doc.add_table(rows=1, cols=4)
table.style = "Table Grid"
widths = [0.72, 1.63, 2.55, 1.60]
set_table_fixed_width(table, widths)
headers = ["优先级", "向对方要什么", "具体内容", "拿到后用于什么"]
for i, text in enumerate(headers):
cell = table.rows[0].cells[i]
set_cell_shading(cell, BLUE)
set_cell_margins(cell)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, text, size=9.5, bold=True, color=WHITE)
set_repeat_table_header(table.rows[0])
for priority, ask, details, usage in rows:
row = table.add_row()
keep_row_together(row)
cells = row.cells
values = [priority, ask, details, usage]
fill = GREEN if priority == "必须" else (AMBER if priority == "建议" else WHITE)
for i, value in enumerate(values):
cell = cells[i]
set_cell_margins(cell)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
if i == 0:
set_cell_shading(cell, fill)
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i == 0 else WD_ALIGN_PARAGRAPH.LEFT
add_text(p, value, size=9.2, bold=(i in (0, 1)))
return table
def add_response_table(doc, rows):
table = doc.add_table(rows=1, cols=3)
table.style = "Table Grid"
set_table_fixed_width(table, [2.35, 2.15, 2.0])
for i, text in enumerate(["请确认", "对方填写/勾选", "附件或联系人"]):
cell = table.rows[0].cells[i]
set_cell_shading(cell, BLUE)
set_cell_margins(cell)
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, text, size=9.5, bold=True, color=WHITE)
set_repeat_table_header(table.rows[0])
for item, options in rows:
row = table.add_row()
keep_row_together(row)
cells = row.cells
for cell in cells:
set_cell_margins(cell, top=110, bottom=110)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
add_text(cells[0].paragraphs[0], item, size=9.3, bold=True)
add_text(cells[1].paragraphs[0], options, size=9.3)
add_text(cells[2].paragraphs[0], "________________", size=9.3, color=GRAY)
return table
def setup_document():
doc = Document()
section = doc.sections[0]
section.page_width = Inches(8.5)
section.page_height = Inches(11)
section.top_margin = Inches(0.65)
section.bottom_margin = Inches(0.65)
section.left_margin = Inches(0.72)
section.right_margin = Inches(0.72)
section.header_distance = Inches(0.3)
section.footer_distance = Inches(0.3)
styles = doc.styles
normal = styles["Normal"]
normal.font.name = "Arial"
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
normal.font.size = Pt(10.5)
normal.font.color.rgb = RGBColor.from_string(BLACK)
normal.paragraph_format.space_after = Pt(5)
normal.paragraph_format.line_spacing = 1.12
for name, size, before, after in (
("Heading 1", 16, 14, 7),
("Heading 2", 12.5, 10, 5),
("Heading 3", 11, 7, 3),
):
style = styles[name]
style.font.name = "Arial"
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
style.font.size = Pt(size)
style.font.bold = True
style.font.color.rgb = RGBColor.from_string(BLUE)
style.paragraph_format.space_before = Pt(before)
style.paragraph_format.space_after = Pt(after)
style.paragraph_format.keep_with_next = True
for list_name in ("List Bullet", "List Bullet 2", "List Number"):
style = styles[list_name]
style.font.name = "Arial"
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
style.font.size = Pt(10.5)
header = section.header
p = header.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
add_text(p, "CNC 机床首次系统对接|资料与接口清单", size=8.5, color=GRAY)
footer = section.footer
p = footer.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, "供机床制造商、三菱 CNC 技术人员及项目对接人共同确认", size=8, color=GRAY)
return doc
def build():
doc = setup_document()
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(8)
p.paragraph_format.space_after = Pt(3)
add_text(p, "CNC 机床系统对接技术需求清单", size=23, bold=True, color=BLUE)
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(12)
add_text(p, "目标:把数字零件模型接入实际机床,完成加工程序生成、程序传输、试切验证和加工结果回收。", size=11.5, color=GRAY)
add_callout(
doc,
"先说结论",
"第一次对接不需要把所有工业接口都拿到。最低闭环只需要四类东西:①准确的机床与 CNC 型号;"
"②机床专用编程资料;③一个可上传 NC 程序的通道;④一批已在该机床验证过的样例。"
"如果还要监控加工状态,再增加一个只读数据接口。",
GREEN,
)
doc.add_heading("1. 先找对人:三菱不一定等于机床厂", level=1)
add_bullet(doc, "机床制造商/集成商:掌握机床结构、轴配置、刀库、夹具、机床专用 M 代码、PLC 梯形图和安全联锁。")
add_bullet(doc, "三菱 CNC 或代理商:掌握控制器型号、系统版本、选件、通信软件、SDK、授权和标准接口。")
add_bullet(doc, "现场工艺/操作人员:掌握真实刀具、材料、转速进给、装夹、对刀、零点和已验证加工程序。")
add_callout(doc, "对接要求", "请甲方安排以上三类人员至少各一位。只找其中一方,通常拿不全资料。", AMBER)
doc.add_heading("2. 第一批必须拿到的材料", level=1)
required_rows = [
("必须", "设备身份资料", "机床品牌/完整型号/序列号;三菱 CNC 控制器系列和完整型号;系统软件版本;已安装选件清单。最好提供铭牌、控制柜和系统信息页面照片。", "决定能用哪些手册、接口和软件,避免按错型号开发。"),
("必须", "机床能力表", "车床/铣床/加工中心;轴数与轴名称;各轴行程;主轴数量、最高转速;最大进给;刀库类型和容量;是否有旋转轴、探针、自动对刀。", "确定模型是否可加工、工序怎么拆、刀路和后处理怎么生成。"),
("必须", "编程资料", "该型号 CNC 的编程/操作手册;机床厂专用操作手册;支持的 G/M/T/S 代码;自定义 M 代码;宏变量、坐标系、刀补和程序格式说明。", "生成该机床能真正执行的 NC 程序。"),
("必须", "已验证样例包", "至少 3~10 个已加工成功的样件,每个样件尽量包含 STEP/二维图、NC 程序、材料、毛坯、刀具表、装夹说明、坐标原点及最终检测结果。", "建立正确后处理和参数基线,比只有说明书更有效。"),
("必须", "程序传输方式", "确认机床当前如何收发加工程序:以太网软件、FTP、共享目录、串口、USB 或其他;提供网络配置方法、账号权限、目录规则和文件名限制。", "打通“生成程序→送入机床”的最小闭环。"),
("建议", "CAM/后处理资料", "现用 CAM 名称和版本;现有后处理器;后处理配置;机床仿真模型/运动学文件;程序头尾模板。", "优先复用成熟后处理,减少试切风险。"),
("建议", "刀具与工艺资料", "真实刀具/刀柄清单、刀具编号、长度直径范围;常用材料与毛坯;推荐转速进给、冷却方式、余量、公差和粗糙度。", "把几何模型转换成可执行工艺,而不是只生成外形。"),
("建议", "备份与恢复资料", "当前 CNC 参数、刀补、宏变量、PLC/梯形图及机床厂数据的备份方式;试验前完整备份;恢复负责人。", "调试异常时可恢复,避免影响生产。"),
]
add_requirement_table(doc, required_rows)
doc.add_heading("3. 接口怎么问:不用一上来就说“把 API 都给我”", level=1)
add_callout(
doc,
"正确问法",
"请对方根据实际 CNC 型号勾选已经安装、可以授权或可以新增的通道。我们的优先顺序是:"
"先解决程序文件传输,再解决只读监控,最后才考虑自动写变量或远程控制。",
PALE_BLUE,
)
doc.add_heading("3.1 上传和下载加工程序(第一优先级)", level=2)
add_checkbox_line(doc, "NC Explorer", "三菱官方工具,可通过以太网在电脑与多台 CNC 间操作加工程序文件;需确认该控制器是否支持。")
add_checkbox_line(doc, "FTP / 共享目录 / 机床厂文件服务", "如果机床已经开放,提供 IP、端口、目录、账号、字符编码和文件名规则。")
add_checkbox_line(doc, "RS-232C / USB", "可作为初期人工导入或旧机型兜底,但不适合长期自动闭环。")
add_checkbox_line(doc, "机床厂自带 DNC/MDC/MES 软件", "提供产品名称、接口文档和第三方接入方法。")
doc.add_heading("3.2 读取运行状态和加工结果(第二优先级)", level=2)
add_checkbox_line(doc, "MTConnect", "适合读取运行、报警、状态等数据;通常为只读,安全性高,适合第一阶段监控。")
add_checkbox_line(doc, "OPC UA / NC Machine Tool Connector", "适合统一采集机床数据;需确认 OPC UA 服务器、节点表、证书、许可证和实际开放变量。")
add_checkbox_line(doc, "NC Monitor2 或甲方现有监控平台", "如果只需要人工远程查看,可先使用现成工具;若要系统接入,仍需确认是否提供数据接口。")
add_checkbox_line(doc, "数据库/文件导出", "若没有实时接口,可先通过班次 CSV、报警日志或加工结果文件实现离线回传。")
doc.add_heading("3.3 读写 CNC 数据或做深度集成(第三优先级)", level=2)
add_checkbox_line(doc, "FCSB1224W000 开发包(SDK", "三菱 CNC 通信软件的开发版;需索取安装包、许可证、参考手册、头文件/库和 C#/C++ 示例。")
add_checkbox_line(doc, "FCSB1224W100 运行库", "部署自行开发的 Windows 程序时可能需要;版本应与 SDK 和 CNC 兼容。")
add_checkbox_line(doc, "SLMP / PLC 设备访问", "仅在机床厂确实开放 PLC 设备映射时考虑;必须索取设备地址表、读写权限和联锁说明。")
add_checkbox_line(doc, "机床厂专用 API", "若机床厂对三菱 CNC 做过二次开发,应优先获取其 API,而不是绕过机床安全逻辑直接写控制器。")
add_callout(
doc,
"安全边界",
"第一阶段只申请“程序上传”和“状态读取”。不要直接申请自动启动主轴、循环启动、写 PLC 或修改 CNC 参数。"
"远程启动必须经过机床厂安全评审、现场联锁和甲方书面批准。",
RED,
)
doc.add_heading("4. 每一种接口要对方同时交付什么", level=1)
api_rows = [
("必须", "兼容性确认", "适用 CNC 型号、系统版本、所需硬件/选件、是否已安装、许可证数量和有效期。", "防止拿到软件但现场不能用。"),
("必须", "开发资料", "API/协议手册、数据字典、错误码、示例程序、SDK/运行库安装包、版本说明。", "完成可维护的接口开发。"),
("必须", "连接参数", "IP、端口、协议版本、账号/证书、读写权限、超时、并发和频率限制。", "建立测试连接。"),
("必须", "测试环境", "一台可调试机床或离线仿真环境;测试账号;允许上传的测试目录;测试窗口。", "不在生产机上盲调。"),
("建议", "现场支持", "机床厂电气/PLC 工程师、三菱 CNC 工程师、操作员的联系人和联合调试时间。", "遇到权限、联锁和型号差异时及时定位。"),
]
add_requirement_table(doc, api_rows)
doc.add_heading("5. 给甲方/制造商的回填表", level=1)
p = doc.add_paragraph()
add_text(p, "请对方直接填写或勾选;不确定的项目可写“待三菱/机床厂确认”。", color=GRAY)
response_rows = [
("机床制造商、型号、序列号", "填写完整型号"),
("三菱 CNC 型号与系统版本", "填写/附系统页面照片"),
("机床类型与轴配置", "车 / 铣 / 加工中心;3/4/5 轴"),
("现用 CAM 与后处理器", "有 / 无;名称和版本"),
("现有程序传输方式", "NC Explorer / FTP / DNC / RS-232C / USB / 其他"),
("可否提供通信 SDK", "FCSB1224W000 / 其他 / 不支持"),
("可否提供只读数据接口", "MTConnect / OPC UA / 其他 / 不支持"),
("可否提供测试环境", "离线仿真 / 测试机 / 生产机测试窗口"),
("可否提供已验证样例包", "数量:____;包含 STEP+NC+刀具+材料:是 / 否"),
("机床厂技术联系人", "机械 / 电气PLC / 工艺"),
("三菱或代理商技术联系人", "CNC / 通信软件 / 授权"),
]
add_response_table(doc, response_rows)
doc.add_heading("6. 第一次会议照着问的 12 个问题", level=1)
questions = [
"这台机床的完整型号是什么?三菱 CNC 的完整型号和系统版本是什么?",
"机床是几轴?各轴行程、主轴、刀库和可加工范围是多少?",
"现在工程师用什么 CAM 和后处理器生成程序?能否提供现有后处理?",
"能否给 3~10 套已经加工成功的完整样例,而不只是 NC 文件?",
"机床现在通过什么方式接收和导出加工程序?",
"是否支持 NC Explorer?现场是否已经配置以太网?",
"这个型号是否支持 FCSB1224W000?许可证、SDK 和运行库由谁提供?",
"是否已经有 MTConnect、OPC UA、MDC、MES 或机床监控系统?",
"我们能读取哪些数据:运行状态、当前程序、坐标、刀具、报警、主轴负载、加工结果?",
"第一阶段能否提供测试机或仿真环境,并允许只上传程序、不自动启动?",
"试验前谁负责备份 CNC 参数、PLC、宏变量和刀补?出现问题谁负责恢复?",
"机床厂、三菱、现场工艺三方各由谁负责,何时可以联合调试?",
]
for i, question in enumerate(questions, 1):
p = doc.add_paragraph(style="List Number")
p.paragraph_format.space_after = Pt(3)
add_text(p, question)
doc.add_heading("7. 建议甲方首批打包提供的文件夹", level=1)
add_callout(
doc,
"一个压缩包即可",
"01_设备铭牌与系统照片;02_机床与 CNC 手册;03_GM代码和机床专用说明;"
"04_已验证样例;05_CAM与后处理;06_刀具材料工艺;07_通信软件与接口文档;"
"08_网络与测试环境;09_联系人。",
LIGHT_BLUE,
)
doc.add_heading("8. CAD、CAM 与机床数字模型资料", level=1)
add_callout(
doc,
"为什么必须补这一部分",
"STEP 只能说明零件几何,不能直接告诉机床如何装夹、选刀和运动。"
"要稳定生成 NC 程序,还需要机床、刀具、夹具、毛坯和坐标系数据。",
PALE_BLUE,
)
cadcam_rows = [
("必须", "零件数据规范", "确认接收 STEP AP203/AP214/AP242 中哪一种;长度单位;坐标方向;模型原点;实体/装配要求;允许的几何修复;二维图、尺寸公差和表面粗糙度如何提供。", "建立统一输入标准,避免比例、方向、基准和公差信息丢失。"),
("必须", "机床运动学", "各线性轴和旋转轴名称、正方向、行程、零点、回转中心、轴顺序、软限位、主轴方向;五轴机需提供 RTCP/TCP 相关配置。", "建立正确后处理和碰撞仿真。"),
("必须", "坐标与对刀规则", "机床坐标、工件坐标 G54~G59/扩展坐标、程序零点、对刀点、刀长/刀径补偿号规则、探针和自动对刀流程。", "保证程序中的坐标与现场实际一致。"),
("必须", "刀具与刀柄模型", "实际刀具、刀柄、延长杆和刀盘的清单及 STEP/尺寸;刀位号、刀补号、最大直径长度重量、相邻刀位限制。", "选刀、计算切削参数并检查刀具/刀柄碰撞。"),
("必须", "夹具与工作区模型", "工作台、卡盘、虎钳、软爪、尾座、第四/第五轴、探针及主要防护区域的 STEP 或关键尺寸。", "规划装夹、可达性和碰撞检查。"),
("必须", "毛坯和材料规则", "常用棒料、板料、锻件、铸件规格;材料牌号;毛坯余量;硬度和热处理状态;是否允许自动推荐替代材料。", "生成合理工序、刀路和切削参数。"),
("建议", "机床仿真模型", "机床厂/CAM 使用的数字机床包、运动学文件、碰撞体、控制器定义和现有仿真项目。", "缩短虚拟调试和后处理验证周期。"),
]
add_requirement_table(doc, cadcam_rows)
doc.add_heading("9. 后处理器和 NC 程序规则", level=1)
post_rows = [
("必须", "程序格式限制", "程序号格式、文件扩展名、字符编码、行号、注释规则、最大文件大小、程序存储路径、子程序路径及程序调用层级限制。", "确保文件能够被控制器识别和加载。"),
("必须", "标准代码清单", "实际支持的 G/M/T/S/F 代码、固定循环、极坐标/坐标旋转、刀补、螺纹、刚性攻丝、高速高精功能及版本差异。", "限制程序生成器只能使用现场支持的指令。"),
("必须", "机床厂专用逻辑", "换刀、开关门、夹紧松开、卡盘/尾座、冷却、排屑、探针、托盘等自定义 M 代码;执行前置条件和互锁。", "避免使用通用 M 代码猜测机床动作。"),
("必须", "程序头尾模板", "提供一份机床厂认可的开头、换刀段、工序切换、异常退出和结束模板;明确安全位置和模态复位要求。", "统一安全启动与结束逻辑。"),
("必须", "后处理验收样例", "选择典型 2.5D、孔系、曲面或车削样件;提供 CAM 刀路、输出 NC、仿真结果和现场实际程序的对照。", "验证后处理输出与现场一致。"),
("建议", "控制器高级选件", "确认高速加工、五轴联动、刀尖点控制、前瞻、样条/NURBS、宏程序等功能是否购买并启用。", "避免生成未授权或不支持的功能。"),
]
add_requirement_table(doc, post_rows)
doc.add_heading("10. 需要读取的数据点清单", level=1)
add_callout(
doc,
"让对方逐项标注",
"每个数据点请填写:是否可读、来自哪个接口、字段/节点地址、数据类型、单位、刷新周期、时间戳、"
"枚举含义、无效值和报警条件。不要只回复“支持 OPC UA”或“支持 API”。",
AMBER,
)
datapoint_rows = [
("必须", "设备与连接状态", "设备 ID、CNC 型号、系统版本、在线/离线、数据源状态、采集程序版本、最后更新时间。", "识别设备并判断数据是否可信。"),
("必须", "运行状态", "自动/手动/MDI、运行/暂停/停止/报警/急停、循环启动状态、单段、选择停止、门和关键联锁状态。", "判断设备是否正在执行及为何停机。"),
("必须", "程序执行", "当前程序号/文件名、子程序、当前行号或序列号、程序开始结束时间、循环时间、加工计数。", "把加工任务与实际执行记录关联。"),
("必须", "轴与坐标", "机床坐标、工件坐标、剩余移动量、进给速度、进给倍率、各轴负载、旋转轴角度;单位和坐标系标识。", "监控运动并分析异常。"),
("必须", "主轴数据", "实际/指令转速、倍率、负载、扭矩或功率、运行方向、主轴状态、温度(若有)。", "分析切削负载和加工状态。"),
("必须", "刀具数据", "当前刀号、刀位、刀长/刀径补偿、寿命/使用次数、刀具报警、备用刀及刀具表更新时间。", "检查程序选刀并积累刀具寿命数据。"),
("必须", "报警与诊断", "报警号、等级、文本、发生/解除时间、报警历史、控制器诊断状态;明确是否能读取机床厂 PLC 报警。", "故障追踪和自动告警。"),
("建议", "加工与质量结果", "完工状态、实际周期、零件计数、测头结果、尺寸检测、报废/返工原因、操作员确认。", "形成加工结果闭环。"),
("建议", "能耗与维护", "主轴运行时长、开机时间、润滑/冷却状态、过滤器、维护计数及能耗数据(若设备已有)。", "支持后续维护和效率分析。"),
]
add_requirement_table(doc, datapoint_rows)
doc.add_heading("11. 通信接口的工程参数", level=1)
comm_rows = [
("必须", "方向与权限", "明确每个接口是只读、文件上传下载、数据读写还是远程控制;列出允许和禁止的操作。", "划定安全边界。"),
("必须", "性能指标", "刷新周期、最小采样间隔、单次/批量读取数量、并发连接数、最大文件大小、传输速率和超时时间。", "评估能否满足监控和任务下发。"),
("必须", "可靠性机制", "断线检测、自动重连、续传、重复提交处理、任务唯一 ID、校验和、确认回执和错误码。", "避免程序重复上传、丢失或状态误判。"),
("必须", "时间与单位", "CNC 时间设置、时区、NTP/PTP 支持、时间戳精度;坐标、转速、负载等字段的单位与缩放系数。", "保证多系统数据可对齐。"),
("必须", "版本管理", "SDK/运行库/协议版本与 CNC 版本兼容矩阵;升级策略;弃用接口;补丁和技术支持期限。", "避免现场升级后接口失效。"),
("建议", "日志与审计", "连接日志、文件传输记录、调用方、操作时间、操作结果、配置修改和远程访问记录。", "故障定位和责任追踪。"),
]
add_requirement_table(doc, comm_rows)
doc.add_heading("12. 网络与信息安全要求", level=1)
security_rows = [
("必须", "网络拓扑", "机床所在网段、工业交换机、防火墙/隔离区、是否允许新增工控机、跨网访问方式和负责网络变更的人员。", "确定系统部署位置和连接路径。"),
("必须", "端口白名单", "逐项提供协议、源/目的 IP、TCP/UDP 端口、连接方向;禁止使用不受控的全网开放规则。", "完成最小权限网络配置。"),
("必须", "账号与证书", "账号类型、角色权限、密码策略、证书签发/更新、密钥保存、账号停用流程;禁止共享管理员账号。", "安全且可审计地访问设备。"),
("必须", "远程支持方案", "是否允许 VPN、堡垒机或厂商远程服务;审批流程、开放时间、录屏/审计和紧急断开方法。", "让远程调试可控。"),
("必须", "备份和变更控制", "CNC 参数、PLC、刀补、宏变量、接口配置的基线备份;变更申请、回退方案、恢复演练和负责人。", "降低调试对生产的风险。"),
("建议", "数据合规", "允许采集的数据范围、数据保存期限、技术图纸和 NC 程序的保密等级、外发和云端限制。", "明确数据能否用于分析和优化。"),
]
add_requirement_table(doc, security_rows)
doc.add_heading("13. 分阶段联调与验收标准", level=1)
acceptance_rows = [
("阶段 0", "资料审查", "型号、手册、代码表、样例、接口和许可证齐全;双方确认未知项和责任人。", "输出《设备接口基线表》。"),
("阶段 1", "离线后处理", "使用已验证样例生成 NC;与现场程序对比;通过 CAM/控制器仿真,无语法错误和明显碰撞。", "后处理器可进入现场试验。"),
("阶段 2", "程序传输", "上传、下载、覆盖保护、目录、文件名、校验、断线重试和回执测试通过;不启动机床。", "打通安全文件通道。"),
("阶段 3", "空运行验证", "机床锁定/抬刀/单段/低倍率条件下执行;核对坐标、刀具、换刀、行程和安全位置。", "证明程序与机床配置一致。"),
("阶段 4", "试切验收", "指定毛坯、刀具和装夹;加工样件;记录周期、报警、刀具和检测结果;尺寸及表面质量达到双方确认标准。", "证明可实际制造。"),
("阶段 5", "数据回传", "运行状态、程序、坐标、刀具、主轴和报警等约定数据点连续采集;断线恢复和时间戳正确。", "形成加工状态闭环。"),
("阶段 6", "试运行", "限定设备、班次和零件类型运行;统计成功率、人工干预、报警和返工;完成问题清单后转正式。", "控制上线风险。"),
]
add_requirement_table(doc, acceptance_rows)
doc.add_heading("14. 项目责任、商务和交付边界", level=1)
boundary_rows = [
("必须", "责任分工", "明确机床厂、三菱/代理商、甲方工艺/设备/网络人员和软件对接方各自负责的资料、配置、调试和验收内容。", "避免接口问题互相推诿。"),
("必须", "授权和费用", "SDK、运行库、OPC UA/MTConnect、仿真软件、后处理器、现场服务、培训和年度维护分别是否收费。", "形成真实预算。"),
("必须", "交付介质", "安装包、许可证文件、离线授权方式、手册、样例代码、源代码范围、版本号、校验值和下载有效期。", "保证后续可复现部署。"),
("必须", "支持机制", "工作时间、响应时限、问题升级路径、远程/现场支持、版本升级和保修期。", "确保问题有闭环负责人。"),
("建议", "培训与知识转移", "操作、编程、通信 SDK、数据接口、备份恢复和安全联调培训;提供录屏或培训材料。", "减少长期依赖单个厂商工程师。"),
]
add_requirement_table(doc, boundary_rows)
doc.add_heading("15. 最终要求对方交付的核心成果", level=1)
core_rows = [
("P0", "设备接口基线表", "每台机床一份:完整型号、CNC 版本、轴/主轴/刀库、选件、程序传输方式、可用接口、网络和联系人。", "没有这份表,不进入开发。"),
("P0", "机床编程资料包", "CNC 手册、机床厂手册、G/M 代码、专用 M 代码、程序模板、坐标/刀补/换刀规则。", "用于生成安全 NC 程序。"),
("P0", "验证样例包", "至少 3~10 套 STEP/图纸、毛坯、刀具、装夹、CAM、NC 和检测结果一一对应的完整样例。", "用于后处理和工艺验证。"),
("P1", "通信接口包", "SDK/运行库或协议文档、许可证、样例代码、数据字典、地址/节点表、错误码和兼容矩阵。", "用于程序传输和数据采集。"),
("P1", "测试与验收环境", "离线仿真或测试机、测试账号/目录、网络窗口、备份、现场人员和验收样件。", "用于安全联调。"),
("P2", "数字机床资料包", "机床、刀具、刀柄、夹具和毛坯模型;运动学;软限位;碰撞体;CAM 仿真工程。", "用于规模化自动编程和虚拟验证。"),
]
add_requirement_table(doc, core_rows)
doc.add_heading("附 A:不同机床类型还要追加确认什么", level=1)
machine_rows = [
("车床", "卡盘/夹头、主轴/副主轴、尾座、刀塔、动力刀具、Y/C/B 轴、棒料机、接料器和螺纹循环。"),
("三轴加工中心", "工作台尺寸、主轴锥度、刀库、换刀位置、探针、第四轴接口、固定循环和高速加工选件。"),
("四/五轴加工中心", "旋转轴结构、回转中心、轴限位、TCP/RTCP、刀尖点控制、奇异点策略、机床运动学和精确碰撞模型。"),
("车铣复合", "多通道/多系统、主副主轴同步、刀塔同步、程序等待码、工件交接、通道间变量和完整同步样例。"),
("磨床/专机", "砂轮修整、补偿、测量循环、专用 PLC 动作、机床厂自定义程序格式和专用工艺数据库。"),
]
table = doc.add_table(rows=1, cols=2)
table.style = "Table Grid"
set_table_fixed_width(table, [1.35, 5.15])
for i, text in enumerate(["机床类型", "追加资料"]):
set_cell_shading(table.rows[0].cells[i], BLUE)
set_cell_margins(table.rows[0].cells[i])
p = table.rows[0].cells[i].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, text, size=9.5, bold=True, color=WHITE)
set_repeat_table_header(table.rows[0])
for machine_type, extra in machine_rows:
row = table.add_row()
keep_row_together(row)
for cell in row.cells:
set_cell_margins(cell)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
add_text(row.cells[0].paragraphs[0], machine_type, size=9.3, bold=True)
add_text(row.cells[1].paragraphs[0], extra, size=9.3)
doc.add_heading("附 B:三菱侧可供确认的典型产品名称", level=1)
p = doc.add_paragraph()
add_text(
p,
"以下只是对接时用于询问的候选名称,并不表示每台机床都具备:NC Explorer(程序文件传输)、"
"FCSB1224W000/W100CNC 通信 SDK/运行库)、MTConnect Data Collector、"
"NC Machine Tool ConnectorOPC UA)、NC Monitor2、NC Virtual Simulator、NC Trainer2 plus。",
size=9.5,
color=GRAY,
)
p = doc.add_paragraph()
p.paragraph_format.space_before = Pt(3)
add_text(
p,
"确认原则:以机床铭牌、CNC 型号、系统版本、已购选件及机床厂书面答复为准。",
size=9.5,
bold=True,
color=BLUE,
)
doc.add_heading("附 C:三菱官方参考入口", level=1)
official_sources = [
("NC 软件工具总览", "确认 NC Explorer、NC Monitor2、NC Virtual Simulator、NC Trainer2 plus 等工具。", "https://www.mitsubishielectric.com/fa/products/software/cnc/index.html"),
("NC Explorer", "官方说明其通过以太网在 Windows 资源管理器中操作 CNC 加工程序文件。", "https://us.mitsubishielectric.com/fa/en/products/cnt/computerized-numerical-controllers/software-tools/nc-explorer/"),
("FCSB1224W000 通信软件手册", "确认 SDK、运行库、支持控制器、开发语言和 API 能力。", "https://dl.mitsubishielectric.com/dl/fa/document/manual/cnc/ib1501209/ib1501209engh.pdf"),
("MTConnect Data Collector", "确认支持的 CNC、适配器、采集器和系统要求。", "https://www.mitsubishielectric.com/fa/products/cnt/cnc/pmerit/iot/mtconnect_data_collector/"),
("NC Machine Tool Connector", "确认 OPC UA/MTConnect 数据采集、连接对象和许可要求。", "https://dl.mitsubishielectric.com/dl/fa/document/manual/cnc/ib1501634/ib1501634engd.pdf"),
("三菱 CNC 手册下载", "按实际 CNC 型号和版本下载规格、编程、操作、连接和设置手册。", "https://www.mitsubishielectric.com/fa/download/search.page?kisyu=%2Fcnc&mode=manual"),
]
source_table = doc.add_table(rows=1, cols=3)
source_table.style = "Table Grid"
set_table_fixed_width(source_table, [1.55, 2.55, 2.40])
for i, text in enumerate(["官方入口", "用于确认", "网址"]):
set_cell_shading(source_table.rows[0].cells[i], BLUE)
set_cell_margins(source_table.rows[0].cells[i])
p = source_table.rows[0].cells[i].paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
add_text(p, text, size=9.3, bold=True, color=WHITE)
set_repeat_table_header(source_table.rows[0])
for name, use, url in official_sources:
row = source_table.add_row()
keep_row_together(row)
for cell in row.cells:
set_cell_margins(cell)
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
add_text(row.cells[0].paragraphs[0], name, size=8.7, bold=True)
add_text(row.cells[1].paragraphs[0], use, size=8.7)
add_text(row.cells[2].paragraphs[0], url, size=7.8, color=BLUE)
OUT.parent.mkdir(parents=True, exist_ok=True)
doc.save(OUT)
print(OUT)
if __name__ == "__main__":
build()
@@ -0,0 +1,86 @@
{
"schema_version": "1.0",
"request": "Regenerate the current compact guided-cylinder housing using only promoted generalized CAD experience while preserving the existing parameterized geometry and enlarged central bore.",
"route": {
"selected_backend": "build123d",
"runner_skill": "cad",
"project": "text-to-cad",
"fallback_order": [
"simplecadapi",
"cadam"
],
"workflow_profiles": [
"requirement_refinement",
"visual_repair"
]
},
"source": {
"model_spec": "model-spec.json",
"path": "compact_guided_cylinder.py",
"format": "python",
"backend": "build123d"
},
"artifacts": [
{
"path": "compact_guided_cylinder.step",
"role": "primary"
},
{
"path": "compact_guided_cylinder.stl",
"role": "secondary"
}
],
"parameter_source": "model-spec.json",
"experience": {
"library": "../../../../cad-experience-library/library.json",
"context_kind": "generalized_cad_experience_query",
"query_scope": "global",
"promoted_methods_found": 0,
"methods_applied": [],
"result": "No promoted generalized methods matched; candidate experiences were intentionally excluded from generation."
},
"assumptions": [
"The image has no absolute dimensions; millimetre dimensions preserve visible proportions only.",
"The product is represented as a three-body compound: main extrusion plus two end caps.",
"The four top holes are modeled through the main body because the bottom view shows matching circles.",
"The four large circles on each end are treated as protruding tie-rod ends.",
"The central bore radius was doubled from 4.5 mm to 9 mm; the collar outer diameter was increased from 17 mm to 26 mm to retain a 4 mm radial wall.",
"This is concept geometry, not a pressure-rated or manufacturing-certified pneumatic cylinder."
],
"validation": [
{
"check": "STEP generation",
"result": "passed",
"detail": "Generated a labeled three-body assembly and STL sidecar from the parameterized Python source."
},
{
"check": "Bounding box",
"result": "passed",
"detail": "138 x 60 x 39 mm; dominant axis X."
},
{
"check": "Topology",
"result": "passed",
"detail": "61 faces and 156 edges after the side-groove repair."
},
{
"check": "Visual review",
"result": "passed",
"detail": "Reviewed regenerated isometric and top snapshots. Geometry remains visually consistent with the current parameterized source, including the enlarged central bore and four-hole pattern."
},
{
"check": "Experience isolation",
"result": "passed",
"detail": "The router reported zero generalized methods. No candidate experience, parser input, parser output, private case JSON, source dimensions, or coordinates were used."
},
{
"check": "JSON parameter rebuild",
"result": "passed",
"detail": "The STEP and STL were regenerated from the part-local model-spec.json through the native Python generator."
}
],
"viewer_links": [
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fcompact_guided_cylinder_v1&file=compact_guided_cylinder.step",
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fcompact_guided_cylinder_v1&file=compact_guided_cylinder.stl"
]
}
@@ -0,0 +1,197 @@
"""Image-inferred compact guided-cylinder housing.
The reference contains no dimensions, so the named dimensions below preserve
its visible proportions rather than claiming a production drawing.
Coordinate convention:
- X: long axis
- Y: width
- Z: height, with the lowest end-cap face at Z = 0
"""
import json
from pathlib import Path
from build123d import Align, Box, Compound, Cylinder, Pos
def _model_parameters():
spec_path = Path(__file__).with_name("model-spec.json")
payload = json.loads(spec_path.read_text(encoding="utf-8"))
return {
name: float(entry["value"])
for name, entry in payload["parameters"].items()
}
PARAMETERS = _model_parameters()
def _parameter(name: str) -> float:
return PARAMETERS[name]
# Overall proportions inferred from the reference image (millimetres).
CORE_LENGTH = _parameter("core_length")
CORE_WIDTH = _parameter("core_width")
CORE_BOTTOM_Z = _parameter("core_bottom_z")
CORE_HEIGHT = _parameter("core_height")
END_CAP_THICKNESS = _parameter("end_cap_thickness")
END_CAP_WIDTH = _parameter("end_cap_width")
END_CAP_HEIGHT = _parameter("end_cap_height")
# Two shallow longitudinal relief grooves on both long side faces.
SIDE_GROOVE_DEPTH = _parameter("side_groove_depth")
SIDE_GROOVE_HEIGHT = _parameter("side_groove_height")
SIDE_GROOVE_Z = (12.0, 24.0)
# Four inferred through mounting holes on the top/bottom pattern.
MOUNT_HOLE_DIAMETER = _parameter("mount_hole_diameter")
MOUNT_HOLE_X = _parameter("mount_hole_x")
MOUNT_HOLE_Y = _parameter("mount_hole_y")
# Central top port and raised collar.
PORT_BORE_DIAMETER = _parameter("port_bore_diameter")
PORT_COLLAR_OUTER_DIAMETER = _parameter("port_collar_outer_diameter")
PORT_COLLAR_HEIGHT = _parameter("port_collar_height")
# Four visible tie-rod ends on each end cap.
TIE_ROD_END_DIAMETER = _parameter("tie_rod_end_diameter")
TIE_ROD_END_PROTRUSION = _parameter("tie_rod_end_protrusion")
TIE_ROD_Y = _parameter("tie_rod_y")
TIE_ROD_Z = (7.0, 31.0)
# Small blind pilot/port visible on both end faces.
END_FACE_HOLE_DIAMETER = _parameter("end_face_hole_diameter")
END_FACE_HOLE_DEPTH = _parameter("end_face_hole_depth")
END_FACE_HOLE_Z = _parameter("end_face_hole_z")
END_CAP_CENTER_X = CORE_LENGTH / 2.0 + END_CAP_THICKNESS / 2.0
TOTAL_LENGTH = CORE_LENGTH + 2.0 * END_CAP_THICKNESS
VISIBLE_LENGTH = TOTAL_LENGTH + 2.0 * TIE_ROD_END_PROTRUSION
TOTAL_HEIGHT = END_CAP_HEIGHT + PORT_COLLAR_HEIGHT
def _x_cylinder(
length: float,
diameter: float,
center_x: float,
center_y: float,
center_z: float,
):
"""Create an X-axis cylinder centered at the requested location."""
return Pos(center_x, center_y, center_z) * Cylinder(
diameter / 2.0,
length,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
rotation=(0.0, 90.0, 0.0),
)
def _make_core():
"""Main extrusion with side grooves, top pattern, and central through port."""
core = Pos(0.0, 0.0, CORE_BOTTOM_Z) * Box(
CORE_LENGTH,
CORE_WIDTH,
CORE_HEIGHT,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
# Cut two crisp grooves into each long side, stopping at the end-cap seams.
for groove_z in SIDE_GROOVE_Z:
groove_center_y = CORE_WIDTH / 2.0 - SIDE_GROOVE_DEPTH / 2.0 + 0.2
front_tool = Pos(0.0, groove_center_y, groove_z) * Box(
CORE_LENGTH + 1.0,
SIDE_GROOVE_DEPTH + 0.4,
SIDE_GROOVE_HEIGHT,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
)
back_tool = Pos(0.0, -groove_center_y, groove_z) * Box(
CORE_LENGTH + 1.0,
SIDE_GROOVE_DEPTH + 0.4,
SIDE_GROOVE_HEIGHT,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
)
core = core - front_tool - back_tool
# Through mounting pattern visible from top and bottom in the reference.
for x_pos in (-MOUNT_HOLE_X, MOUNT_HOLE_X):
for y_pos in (-MOUNT_HOLE_Y, MOUNT_HOLE_Y):
hole = Pos(x_pos, y_pos, -1.0) * Cylinder(
MOUNT_HOLE_DIAMETER / 2.0,
TOTAL_HEIGHT + 2.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
core = core - hole
port_hole = Pos(0.0, 0.0, -1.0) * Cylinder(
PORT_BORE_DIAMETER / 2.0,
TOTAL_HEIGHT + 2.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
core = core - port_hole
collar = Pos(0.0, 0.0, CORE_BOTTOM_Z + CORE_HEIGHT - 0.1) * Cylinder(
PORT_COLLAR_OUTER_DIAMETER / 2.0,
PORT_COLLAR_HEIGHT + 0.1,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
collar = collar - Pos(0.0, 0.0, CORE_BOTTOM_Z + CORE_HEIGHT - 1.0) * Cylinder(
PORT_BORE_DIAMETER / 2.0,
PORT_COLLAR_HEIGHT + 2.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
core = core + collar
core.label = "extruded_cylinder_body"
return core
def _make_end_cap(side: int):
"""End plate with four protruding tie-rod ends and one blind face hole."""
center_x = side * END_CAP_CENTER_X
cap = Pos(center_x, 0.0, 0.0) * Box(
END_CAP_THICKNESS,
END_CAP_WIDTH,
END_CAP_HEIGHT,
align=(Align.CENTER, Align.CENTER, Align.MIN),
)
boss_center_x = side * (
TOTAL_LENGTH / 2.0 + TIE_ROD_END_PROTRUSION / 2.0 - 0.5
)
for y_pos in (-TIE_ROD_Y, TIE_ROD_Y):
for z_pos in TIE_ROD_Z:
cap = cap + _x_cylinder(
TIE_ROD_END_PROTRUSION + 1.0,
TIE_ROD_END_DIAMETER,
boss_center_x,
y_pos,
z_pos,
)
# Blind end-face pilot: overshoot the outside face but stop inside the cap.
hole_center_x = side * (
TOTAL_LENGTH / 2.0 - END_FACE_HOLE_DEPTH / 2.0 + 0.5
)
cap = cap - _x_cylinder(
END_FACE_HOLE_DEPTH + 1.0,
END_FACE_HOLE_DIAMETER,
hole_center_x,
0.0,
END_FACE_HOLE_Z,
)
cap.label = "right_end_cap" if side > 0 else "left_end_cap"
return cap
def gen_step():
"""Return a labeled three-body STEP-ready compound."""
core = _make_core()
left_cap = _make_end_cap(-1)
right_cap = _make_end_cap(1)
model = Compound(children=[core, left_cap, right_cap])
model.label = "compact_guided_cylinder_v1"
return model
@@ -0,0 +1,149 @@
{
"schema_version": "1.0",
"model_spec_kind": "parametric_cad_model",
"model_id": "compact_guided_cylinder",
"units": "mm",
"reconstruction": {
"mode": "native_generator",
"generator": {
"path": "compact_guided_cylinder.py",
"entrypoint": "gen_step"
}
},
"parameters": {
"core_length": {
"value": 116.0,
"unit": "mm",
"role": "main_body_length"
},
"core_width": {
"value": 56.0,
"unit": "mm",
"role": "main_body_width"
},
"core_bottom_z": {
"value": 2.0,
"unit": "mm",
"role": "main_body_base_offset"
},
"core_height": {
"value": 34.0,
"unit": "mm",
"role": "main_body_height"
},
"end_cap_thickness": {
"value": 8.0,
"unit": "mm",
"role": "end_cap_thickness"
},
"end_cap_width": {
"value": 60.0,
"unit": "mm",
"role": "end_cap_width"
},
"end_cap_height": {
"value": 38.0,
"unit": "mm",
"role": "end_cap_height"
},
"side_groove_depth": {
"value": 1.8,
"unit": "mm",
"role": "longitudinal_groove_depth"
},
"side_groove_height": {
"value": 2.0,
"unit": "mm",
"role": "longitudinal_groove_height"
},
"mount_hole_diameter": {
"value": 4.5,
"unit": "mm",
"role": "mounting_hole_diameter"
},
"mount_hole_x": {
"value": 32.0,
"unit": "mm",
"role": "mounting_pattern_half_spacing_x"
},
"mount_hole_y": {
"value": 16.0,
"unit": "mm",
"role": "mounting_pattern_half_spacing_y"
},
"port_bore_diameter": {
"value": 18.0,
"unit": "mm",
"role": "central_port_bore_diameter"
},
"port_collar_outer_diameter": {
"value": 26.0,
"unit": "mm",
"role": "central_port_collar_outer_diameter"
},
"port_collar_height": {
"value": 3.0,
"unit": "mm",
"role": "central_port_collar_height"
},
"tie_rod_end_diameter": {
"value": 12.0,
"unit": "mm",
"role": "tie_rod_end_diameter"
},
"tie_rod_end_protrusion": {
"value": 3.0,
"unit": "mm",
"role": "tie_rod_end_protrusion"
},
"tie_rod_y": {
"value": 24.0,
"unit": "mm",
"role": "tie_rod_pattern_half_spacing_y"
},
"end_face_hole_diameter": {
"value": 5.5,
"unit": "mm",
"role": "end_face_blind_hole_diameter"
},
"end_face_hole_depth": {
"value": 6.0,
"unit": "mm",
"role": "end_face_blind_hole_depth"
},
"end_face_hole_z": {
"value": 14.0,
"unit": "mm",
"role": "end_face_blind_hole_height"
}
},
"feature_groups": [
{
"id": "main_body",
"kind": "prismatic_body"
},
{
"id": "mounting_pattern",
"kind": "repeated_axial_hole_pattern",
"parameters": [
"mount_hole_diameter",
"mount_hole_x",
"mount_hole_y"
]
},
{
"id": "central_port",
"kind": "raised_collar_with_passage",
"parameters": [
"port_bore_diameter",
"port_collar_outer_diameter",
"port_collar_height"
]
}
],
"modifications": [],
"outputs": {
"step": "compact_guided_cylinder.step",
"stl": "compact_guided_cylinder.stl"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@@ -0,0 +1,37 @@
{
"input": "compact_guided_cylinder.step",
"mode": "view",
"outputs": [
{
"path": "review/iso.png",
"camera": "iso"
},
{
"path": "review/iso_opposite.png",
"camera": {
"direction": [
-1,
1,
-0.8
]
}
},
{
"path": "review/top.png",
"camera": "top"
},
{
"path": "review/front.png",
"camera": "front"
}
],
"display": {
"mode": "solid",
"projection": "orthographic"
},
"render": {
"viewLabels": true,
"padding": 0.12,
"sizeProfile": "diagnostic"
}
}
@@ -0,0 +1,149 @@
{
"schema_version": "1.0",
"request": "把明确提供的 015240.step 转换成可修改的 model-spec.json,并保留原始几何作为精确基体",
"route": {
"selected_backend": "build123d",
"runner_skill": "cad",
"project": "text-to-cad",
"fallback_order": [
"simplecadapi",
"cadam"
],
"workflow_profiles": [],
"operation": "modify",
"source_mode": "exact_step_base",
"modification_mode": "model_spec_parameter_edit"
},
"source": {
"path": "source.step",
"format": "step",
"backend": "build123d",
"sha256": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4",
"immutable_exact_base": true
},
"artifacts": [
{
"path": "model-spec.json",
"role": "editable_geometry_contract"
},
{
"path": "imported_015240.step",
"role": "primary"
},
{
"path": "imported_015240.stl",
"role": "secondary"
},
{
"path": "source.step",
"role": "immutable_exact_base"
}
],
"parameters": {},
"experience": {
"library": "../../../../cad-experience-library/library.json",
"context_kind": "generalized_cad_experience_query",
"methods_applied": []
},
"assumptions": [
"单位按 STEP 几何和导入工具约定记录为 mm。",
"原 STEP 不包含可可靠恢复的原生草图、约束或特征历史;后续修改通过 model-spec.json 的命名参数和修改特征表达。",
"“选中面厚度增加”按直接建模语义解释为源面 #f22 沿其 +Z 法向外移 1.0 mm。",
"“红圈内的孔缩小”按目标水平板通孔从约 Ø4.8 mm 缩小到 Ø3.6 mm 处理。"
],
"validation": [
{
"check": "exact_base_sha256",
"passed": true,
"value": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4"
},
{
"check": "exact_base_geometry_signature",
"passed": true,
"scope": "immutable_source.step",
"size_mm": [
26.200001497,
18.906424738,
28.2
],
"center_mm": [
13.100000748,
7.453212369,
0.0
],
"solid_count": 1,
"face_count": 31,
"edge_count": 87,
"volume_mm3": 2400.466496
},
{
"check": "model_spec_parameterized_rebuild",
"passed": true,
"modified_size_mm": [
26.200001497,
18.906424738,
29.2
],
"modified_center_mm": [
13.100000748,
7.453212369,
0.5
],
"solid_count": 1,
"volume_mm3": 2476.533646
},
{
"check": "selected_face_f22_extension",
"passed": true,
"source_plane_z_mm": 14.1,
"result_plane_z_mm": 15.1,
"extension_mm": 1.0,
"result_face": "#f27"
},
{
"check": "circled_hole_resize",
"passed": true,
"center_mm": [
5.9,
11.006425,
0.0
],
"axis": "Z",
"source_diameter_mm": 4.799996,
"result_diameter_mm": 3.6,
"result_face": "#f22"
},
{
"check": "source_to_modified_cad_diff",
"passed": true,
"geometry_changed": true,
"size_delta_mm": [
0.0,
0.0,
1.0
],
"center_delta_mm": [
0.0,
0.0,
0.5
]
},
{
"check": "cad_inspect_refs_facts_planes_positioning",
"passed": true
},
{
"check": "snapshot_review",
"passed": true,
"views": [
"snapshots/iso_20260723T085707Z.png",
"snapshots/iso_opposite_20260723T085707Z.png",
"snapshots/top_ortho_20260723T085707Z.png",
"snapshots/front_ortho_20260723T085707Z.png"
]
}
],
"viewer_links": [
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fimported_015240&file=imported_015240.step"
]
}
@@ -0,0 +1,110 @@
{
"schema_version": "1.0",
"model_spec_kind": "parametric_cad_model",
"model_id": "imported_015240",
"units": "mm",
"reconstruction": {
"mode": "exact_step_base",
"source": {
"path": "source.step",
"sha256": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4",
"geometry_signature": {
"size_mm": [
26.200001497,
18.906424738,
28.2
],
"center_mm": [
13.100000748,
7.453212369,
-0.0
],
"solid_count": 1,
"face_count": 31,
"edge_count": 87,
"volume_mm3": 2400.466496
}
},
"history_recovery": {
"status": "not_present_in_step",
"contract": "Preserve the imported B-Rep exactly as the immutable base; represent later edits as named parametric modifications."
}
},
"parameters": {
"selected_face_extension": {
"value": 1.0,
"unit": "mm",
"role": "outward_offset_of_source_face_f22",
"source_face": "#f22",
"direction": "+Z"
},
"target_hole_diameter": {
"value": 3.6,
"unit": "mm",
"role": "diameter_of_circled_horizontal_plate_hole",
"source_diameter_mm": 4.799996,
"axis": "Z"
}
},
"modifications": [
{
"id": "extend_selected_face_f22",
"operation": "add_box",
"source_face": "#f22",
"direction": "+Z",
"center": [
13.1,
-1.0,
{
"parameter": "selected_face_extension",
"scale": 0.5,
"offset": 14.1
}
],
"size": [
22.2,
2.0,
{
"parameter": "selected_face_extension"
}
],
"enabled": true
},
{
"id": "fill_original_circled_hole",
"operation": "add_cylinder",
"axis": "z",
"center": [
5.9,
11.006425,
0.0
],
"diameter": 4.799996,
"length": 4.0,
"enabled": true
},
{
"id": "cut_resized_circled_hole",
"operation": "cut_cylinder",
"axis": "z",
"center": [
5.9,
11.006425,
0.0
],
"diameter": {
"parameter": "target_hole_diameter"
},
"length": 6.0,
"enabled": true
}
],
"outputs": {
"step": "imported_015240.step",
"stl": "imported_015240.stl"
},
"validation": {
"exact_base_required": true,
"geometry_signature_tolerance_mm": 1e-07
}
}
@@ -0,0 +1,29 @@
{
"input": "imported_015240.step",
"mode": "view",
"outputs": [
{
"path": "snapshots/iso.png",
"camera": "iso"
},
{
"path": "snapshots/iso_opposite.png",
"camera": {
"direction": [-1, 1, -0.8]
}
},
{
"path": "snapshots/top_ortho.png",
"camera": "top"
},
{
"path": "snapshots/front_ortho.png",
"camera": "front"
}
],
"render": {
"viewLabels": true,
"padding": 0.12,
"sizeProfile": "diagnostic"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

@@ -0,0 +1,372 @@
{
"ok": true,
"tokens": [
{
"line": 1,
"token": "#",
"cadPath": "targeted_015133",
"stepPath": "targeted_015133.step",
"stepHash": "97f9763fe46700d0a23fa052b08c119e7352275b4e850cb8ed8c77f85e29f4c3",
"summary": {
"kind": "part",
"occurrenceCount": 1,
"leafOccurrenceCount": 1,
"shapeCount": 1,
"faceCount": 199,
"edgeCount": 508,
"vertexCount": 0,
"bounds": {
"min": [
-106.75,
-0.0,
-38.0
],
"max": [
106.75,
78.5,
38.0
]
}
},
"selections": [],
"warnings": [],
"entryFacts": {
"size": [
213.5,
78.5,
76.0
],
"extentAxis": "x",
"center": [
0.0,
39.25,
0.0
],
"diag": 239.8343178112757,
"kind": "part"
},
"planes": [
{
"axis": "y",
"coordinate": 0.0,
"normalSign": -1,
"faceCount": 1,
"totalArea": 12646.876863,
"bbox": {
"min": [
-86.75,
-0.0,
-38.0
],
"max": [
86.75,
0.0,
38.0
]
},
"selectors": [
"f85"
]
},
{
"axis": "y",
"coordinate": 76.0,
"normalSign": 1,
"faceCount": 1,
"totalArea": 12168.786533,
"bbox": {
"min": [
-86.75,
76.0,
-38.0
],
"max": [
86.75,
76.0,
38.0
]
},
"selectors": [
"f59"
]
},
{
"axis": "z",
"coordinate": -38.00000000000001,
"normalSign": -1,
"faceCount": 3,
"totalArea": 11242.8,
"bbox": {
"min": [
-86.75,
0.0,
-38.0
],
"max": [
86.75,
76.0,
-38.0
]
},
"selectors": [
"f110",
"f86",
"f98"
]
},
{
"axis": "z",
"coordinate": 38.00000000000001,
"normalSign": 1,
"faceCount": 3,
"totalArea": 11242.8,
"bbox": {
"min": [
-86.75,
0.0,
38.0
],
"max": [
86.75,
76.0,
38.0
]
},
"selectors": [
"f60",
"f72",
"f84"
]
},
{
"axis": "x",
"coordinate": -106.75000000000001,
"normalSign": -1,
"faceCount": 5,
"totalArea": 5013.75858,
"bbox": {
"min": [
-106.75,
0.35,
-37.65
],
"max": [
-106.75,
75.65,
37.65
]
},
"selectors": [
"f140",
"f166",
"f168",
"f170",
"f172"
]
},
{
"axis": "x",
"coordinate": 106.75000000000001,
"normalSign": 1,
"faceCount": 5,
"totalArea": 5013.75858,
"bbox": {
"min": [
106.75,
0.35,
-37.65
],
"max": [
106.75,
75.65,
37.65
]
},
"selectors": [
"f158",
"f160",
"f162",
"f164",
"f55"
]
}
],
"entryPositioning": {
"kind": "part",
"bbox": {
"min": [
-106.75,
-0.0,
-38.0
],
"max": [
106.75,
78.5,
38.0
]
},
"bboxFacts": {
"size": [
213.5,
78.5,
76.0
],
"extentAxis": "x",
"center": [
0.0,
39.25,
0.0
],
"diag": 239.8343178112757
},
"majorPlanes": [
{
"axis": "y",
"coordinate": 0.0,
"normalSign": -1,
"faceCount": 1,
"totalArea": 12646.876863,
"bbox": {
"min": [
-86.75,
-0.0,
-38.0
],
"max": [
86.75,
0.0,
38.0
]
},
"selectors": [
"f85"
]
},
{
"axis": "y",
"coordinate": 76.0,
"normalSign": 1,
"faceCount": 1,
"totalArea": 12168.786533,
"bbox": {
"min": [
-86.75,
76.0,
-38.0
],
"max": [
86.75,
76.0,
38.0
]
},
"selectors": [
"f59"
]
},
{
"axis": "z",
"coordinate": -38.00000000000001,
"normalSign": -1,
"faceCount": 3,
"totalArea": 11242.8,
"bbox": {
"min": [
-86.75,
0.0,
-38.0
],
"max": [
86.75,
76.0,
-38.0
]
},
"selectors": [
"f110",
"f86",
"f98"
]
},
{
"axis": "z",
"coordinate": 38.00000000000001,
"normalSign": 1,
"faceCount": 3,
"totalArea": 11242.8,
"bbox": {
"min": [
-86.75,
0.0,
38.0
],
"max": [
86.75,
76.0,
38.0
]
},
"selectors": [
"f60",
"f72",
"f84"
]
},
{
"axis": "x",
"coordinate": -106.75000000000001,
"normalSign": -1,
"faceCount": 5,
"totalArea": 5013.75858,
"bbox": {
"min": [
-106.75,
0.35,
-37.65
],
"max": [
-106.75,
75.65,
37.65
]
},
"selectors": [
"f140",
"f166",
"f168",
"f170",
"f172"
]
},
{
"axis": "x",
"coordinate": 106.75000000000001,
"normalSign": 1,
"faceCount": 5,
"totalArea": 5013.75858,
"bbox": {
"min": [
106.75,
0.35,
-37.65
],
"max": [
106.75,
75.65,
37.65
]
},
"selectors": [
"f158",
"f160",
"f162",
"f164",
"f55"
]
}
]
}
}
],
"errors": []
}
@@ -0,0 +1,43 @@
{
"schema_version": "1.0",
"model_spec_kind": "parametric_cad_model",
"model_id": "targeted_015133",
"units": "mm",
"reconstruction": {
"mode": "exact_step_base",
"source": {
"path": "source.step",
"sha256": "97f9763fe46700d0a23fa052b08c119e7352275b4e850cb8ed8c77f85e29f4c3",
"geometry_signature": {
"size_mm": [
213.5,
78.5,
76.0
],
"center_mm": [
0.0,
39.25,
0.0
],
"solid_count": 1,
"face_count": 199,
"edge_count": 488,
"volume_mm3": 1174741.621833
}
},
"history_recovery": {
"status": "not_present_in_step",
"contract": "Preserve the imported B-Rep exactly as the immutable base; represent later edits as named parametric modifications."
}
},
"parameters": {},
"modifications": [],
"outputs": {
"step": "targeted_015133.step",
"stl": "targeted_015133.stl"
},
"validation": {
"exact_base_required": true,
"geometry_signature_tolerance_mm": 1e-07
}
}
@@ -0,0 +1,29 @@
{
"input": "targeted_015133.step",
"mode": "view",
"outputs": [
{
"path": "snapshot_iso.png",
"camera": "iso"
},
{
"path": "snapshot_iso_opposite.png",
"camera": {
"direction": [-1, 1, -0.8]
}
},
{
"path": "snapshot_top.png",
"camera": "top"
},
{
"path": "snapshot_front.png",
"camera": "front"
}
],
"render": {
"viewLabels": true,
"padding": 0.12,
"sizeProfile": "diagnostic"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

+17 -4
View File
@@ -1,6 +1,6 @@
---
name: cad-router
description: Route natural-language CAD, mechanical-part, assembly, 3D-printing, and robot-description requests across text-to-cad, SimpleCADAPI, and CADAM, then enforce shared generation, validation, requirement refinement, visual repair, and CAD Viewer handoff. Use for new or modified STEP/STP, SCAD, STL, 3MF, DXF, URDF, SRDF, SDF, or G-code work when the agent must choose the best modeling backend and preserve editable sources.
description: Route natural-language CAD, mechanical-part, assembly, 3D-printing, and robot-description requests across text-to-cad, SimpleCADAPI, and CADAM; create one editable model-spec.json per part; convert supplied STEP/STP files into exact-base JSON-controlled models; and enforce generation, modification, validation, visual repair, and CAD Viewer handoff. Use for new or modified STEP/STP, SCAD, STL, 3MF, DXF, URDF, SRDF, SDF, or G-code work when the agent must choose a backend, preserve editable sources, edit JSON model parameters, or parameterize an imported STEP.
---
# CAD Router
@@ -24,7 +24,11 @@ Choose a backend before modeling, keep its source as the editable authority, and
Add explicit signals when known: `--output step`, `--output stl`, `--manufacturing machining|printing|laser-cutting`, `--edit-source path/to/source`, `--existing-model`, `--assembly`, or `--browser-controls`.
The router auto-discovers the workspace `cad-experience-library/library.json`,
infers an experience family and feature roles from the request, and emits a
backend-neutral `design_plan`. Use `--experience-library`,
backend-neutral `design_plan`. When promoted `reconstruction_grammar`
records match, instantiate their symbolic parameter roles, datum roles,
canonical stages, and validation roles in the new part's
`model-spec.json`; fill values from explicit user dimensions first and
dimensionless distributions second. Use `--experience-library`,
`--experience-family`, or repeated `--experience-feature` only to override
those defaults.
3. Follow the selected route unless a hard environmental constraint makes it unavailable. If overriding it, record the reason in the task manifest.
@@ -33,7 +37,13 @@ Choose a backend before modeling, keep its source as the editable authority, and
- `simplecadapi`: use an installed SimpleCADAPI Skill/runtime; prefer it for gears, racks, ring gears, bearings, cycloidal parts, reducers, replayable graphs, semantic tags, and FreeCAD interchange.
- `cadam`: generate editable `.scad`, then execute `node scripts/cadam_compile.mjs <source.scad> <output.stl|output.dxf>`. Set `CADAM_ROOT` or pass `--cadam-root` when the CADAM checkout is not beside text-to-cad. Prefer it for fast parametric printable models, repeated patterns, decorative CSG, and parameter controls.
5. When the route reports `requirement_refinement` or `visual_repair`, refine the brief, generate, execute, inspect multiple views, and repair no more than three visual mismatch rounds before asking the user.
6. Generate into a task-owned directory and start CAD Viewer with that task directory as `--dir`; do not expose the repository-wide fixture library for a task review. Never change backend during an edit unless conversion is explicitly requested; modify the recorded source of truth.
6. Generate into a task-owned directory and start CAD Viewer with that task directory as `--dir`; do not expose the repository-wide fixture library for a task review. Write one `model-spec.json` per part and treat it as the editable geometry contract; keep `cad-task.json` as the execution record. Read `references/model-spec.md` before creating, importing, or modifying a model specification. Never change backend during an edit unless conversion is explicitly requested; modify the recorded source of truth.
For a supplied STEP/STP without an editable generator, create an
`exact_step_base` specification with `scripts/model_spec.py import-step`.
Preserve the copied STEP as the immutable exact B-Rep base and express later
edits as named JSON parameters and modification features. Do not claim that
arbitrary STEP recovers its original sketches, constraints, or feature
history.
For modification, prefer a native Python/SCAD generator. With STEP only,
inspect the explicitly supplied model and perform the smallest local feature
rebuild possible. For major topology changes, reconstruct from that model
@@ -43,7 +53,8 @@ Choose a backend before modeling, keep its source as the editable authority, and
`--experience-feature`; do not guess across family-scoped experience.
7. Validate against the brief. STEP-first routes must run geometry inspection and snapshot review. Mesh routes must check compilation, bounds, solidity/manifold facts when available, and snapshot review.
For experience-informed generation, record which generalized methods were
applied. Validate against the user's brief and held-out targets, never against
applied, including the reconstruction grammar ID and instantiated symbolic
roles. Validate against the user's brief and held-out targets, never against
parameters exposed from the same source case.
8. Write a `cad-task.json` manifest using the contract in `references/backend-contract.md`. Hand supported artifacts to `$cad-viewer` and return its live links.
@@ -67,4 +78,6 @@ Choose a backend before modeling, keep its source as the editable authority, and
- Read `references/capabilities.md` when comparing or invoking backends.
- Read `references/backend-contract.md` before writing a task manifest or adding another backend.
- Read `references/model-spec.md` when generating per-part parameter JSON,
converting STEP to a JSON-controlled model, or modifying an existing part.
- `scripts/capabilities.json` is the machine-readable V1 registry used by `scripts/route.py`.
@@ -1,4 +1,4 @@
interface:
display_name: "CAD Router"
short_description: "Route CAD work across four complementary projects."
default_prompt: "Use $cad-router to select and run the best CAD backend, validate the result, and open only the task outputs in CAD Viewer."
short_description: "Route CAD and manage per-part JSON model specs."
default_prompt: "Use $cad-router to select the best CAD backend, create or modify the part through its model-spec.json, validate the result, and open it in CAD Viewer."
@@ -72,6 +72,14 @@ Write `cad-task.json` beside the task artifacts with this minimum shape:
```
Paths are relative to the task directory unless an external input must remain absolute.
Do not store authoritative model parameters in `cad-task.json`. Every part must
have a sibling `model-spec.json` that owns its parameter values, reconstruction
mode, parametric modification layer, and output paths. See `model-spec.md`.
For an imported STEP, use `exact_step_base`: copy the explicitly supplied file
into the task directory, record its checksum and geometry signature, and keep
the imported B-Rep unchanged when the modification list is empty. This is an
exact geometric base contract, not recovery of the original CAD feature tree.
## Backend adapter requirements
@@ -0,0 +1,111 @@
# Per-part model specification
Use one `model-spec.json` per generated or imported part. Treat it as the
editable geometry contract. Keep `cad-task.json` separate for routing,
experience use, validation results, and viewer links.
## Native generator
Use `native_generator` when the part has editable Python source:
```json
{
"schema_version": "1.0",
"model_spec_kind": "parametric_cad_model",
"model_id": "example_part",
"units": "mm",
"reconstruction": {
"mode": "native_generator",
"generator": {
"path": "example_part.py",
"entrypoint": "gen_step"
}
},
"parameters": {
"width": {
"value": 40,
"unit": "mm",
"role": "overall_width"
}
},
"modifications": [],
"outputs": {
"step": "example_part.step",
"stl": "example_part.stl"
}
}
```
The generator must read its sibling `model-spec.json`. Do not duplicate
authoritative parameter values as editable constants in both files.
## Imported STEP
Convert an explicitly supplied STEP into a task-local exact-base specification:
```bash
python scripts/model_spec.py import-step input.step \
--task-dir models/cad-router-v1/imported_part \
--model-id imported_part
```
This copies the source to `source.step`, records its SHA-256 and geometry
signature, writes `model-spec.json`, and exports regenerated STEP/STL files.
With an empty modification list, `verify` requires the imported B-Rep to retain
the same bounds, center, topology counts, solid count, and volume.
STEP normally contains final B-Rep geometry, not the original sketches,
constraints, names, or feature history. Therefore:
- `exact_step_base` guarantees an unchanged imported base, not recovered native
design history.
- Add later edits as named parametric modifications.
- Use semantic native reconstruction only when the feature family can be
reliably recognized and validate it independently against the source.
- Never claim arbitrary STEP-to-JSON conversion recovers the authors original
parameters or construction order.
## Parametric modification layer
Supported V1 operations are `add_box`, `cut_box`, `add_cylinder`, and
`cut_cylinder`. Values may be literals, direct parameter references, or affine
parameter expressions:
```json
{
"id": "central_bore",
"operation": "cut_cylinder",
"axis": "z",
"center": [0, 0, 0],
"diameter": {"parameter": "bore_diameter"},
"length": {"parameter": "bore_length"},
"enabled": true
}
```
Use `scale` and `offset` when a tool center must stay anchored while its size
changes. For example, a positive-Z face at `z = 14.1` can remain the lower face
of an added box while `extension` changes:
```json
{
"center": [
13.1,
-1.0,
{"parameter": "extension", "scale": 0.5, "offset": 14.1}
],
"size": [22.2, 2.0, {"parameter": "extension"}]
}
```
Change a named value and rebuild:
```bash
python scripts/model_spec.py set path/to/model-spec.json bore_diameter 18
python scripts/model_spec.py build path/to/model-spec.json
python scripts/model_spec.py verify path/to/model-spec.json
```
Use `apply_patch` when adding or structurally editing parameters and features so
the modification remains reviewable. After every visible rebuild, run the CAD
inspection and snapshot workflow and update `cad-task.json`.
@@ -0,0 +1,632 @@
#!/usr/bin/env python3
"""Create and execute per-part JSON model specifications.
Two reconstruction modes are supported:
* ``native_generator`` loads a task-local Python generator. The generator reads
the same model-spec.json, so named parameter edits remain the source of truth.
* ``exact_step_base`` imports an immutable task-local STEP file and applies a
parametric modification layer. With no modifications, the imported B-Rep is
preserved geometrically even though the exported STEP text may differ.
"""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import math
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Any
from build123d import (
Align,
Box,
Cylinder,
Part,
Pos,
export_step,
export_stl,
import_step,
)
SCHEMA_VERSION = "1.0"
MODEL_SPEC_KIND = "parametric_cad_model"
MODEL_ID_RE = re.compile(r"[^a-zA-Z0-9._-]+")
SUPPORTED_OPERATIONS = {
"add_box",
"cut_box",
"add_cylinder",
"cut_cylinder",
}
class ModelSpecError(ValueError):
"""Raised when a model specification is invalid or unsafe to execute."""
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ModelSpecError(f"Cannot read model spec {path}: {exc}") from exc
if not isinstance(value, dict):
raise ModelSpecError("Model spec must be a JSON object")
return value
def _write_json(path: Path, value: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
temporary.replace(path)
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _safe_model_id(value: str) -> str:
result = MODEL_ID_RE.sub("_", value.strip()).strip("._-")
if not result:
raise ModelSpecError("model_id must contain a letter or number")
return result
def _resolve_relative(spec_path: Path, value: str, field: str) -> Path:
candidate = Path(value)
if candidate.is_absolute():
raise ModelSpecError(f"{field} must be task-relative, not absolute")
return (spec_path.parent / candidate).resolve()
def _parameter_value(spec: dict[str, Any], name: str) -> float:
parameters = spec.get("parameters", {})
entry = parameters.get(name) if isinstance(parameters, dict) else None
if not isinstance(entry, dict) or "value" not in entry:
raise ModelSpecError(f"Unknown parameter reference: {name}")
value = entry["value"]
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ModelSpecError(f"Parameter {name} must contain a numeric value")
if not math.isfinite(float(value)):
raise ModelSpecError(f"Parameter {name} must be finite")
return float(value)
def resolve_number(spec: dict[str, Any], value: Any, field: str) -> float:
if isinstance(value, bool):
raise ModelSpecError(f"{field} must be numeric")
if isinstance(value, (int, float)):
result = float(value)
elif isinstance(value, dict) and "parameter" in value:
unexpected = set(value) - {"parameter", "scale", "offset"}
if unexpected:
raise ModelSpecError(
f"{field} parameter expression has unsupported keys: "
+ ", ".join(sorted(unexpected))
)
scale = value.get("scale", 1.0)
offset = value.get("offset", 0.0)
if (
isinstance(scale, bool)
or not isinstance(scale, (int, float))
or isinstance(offset, bool)
or not isinstance(offset, (int, float))
):
raise ModelSpecError(f"{field} scale and offset must be numeric")
result = (
_parameter_value(spec, str(value["parameter"])) * float(scale)
+ float(offset)
)
else:
raise ModelSpecError(
f"{field} must be a number or a parameter expression"
)
if not math.isfinite(result):
raise ModelSpecError(f"{field} must be finite")
return result
def _vector3(spec: dict[str, Any], value: Any, field: str) -> tuple[float, float, float]:
if not isinstance(value, list) or len(value) != 3:
raise ModelSpecError(f"{field} must contain three values")
return tuple(
resolve_number(spec, item, f"{field}[{index}]")
for index, item in enumerate(value)
)
def validate_model_spec(spec: dict[str, Any]) -> None:
if spec.get("schema_version") != SCHEMA_VERSION:
raise ModelSpecError(f"Unsupported model spec schema: {spec.get('schema_version')}")
if spec.get("model_spec_kind") != MODEL_SPEC_KIND:
raise ModelSpecError("Not a parametric CAD model specification")
_safe_model_id(str(spec.get("model_id", "")))
if spec.get("units") != "mm":
raise ModelSpecError("V1 model specs use millimetres")
reconstruction = spec.get("reconstruction")
if not isinstance(reconstruction, dict):
raise ModelSpecError("reconstruction must be an object")
mode = reconstruction.get("mode")
if mode not in {"native_generator", "exact_step_base"}:
raise ModelSpecError(f"Unsupported reconstruction mode: {mode}")
if mode == "native_generator":
generator = reconstruction.get("generator")
if not isinstance(generator, dict) or not generator.get("path"):
raise ModelSpecError("native_generator requires generator.path")
else:
source = reconstruction.get("source")
if (
not isinstance(source, dict)
or not source.get("path")
or not source.get("sha256")
):
raise ModelSpecError("exact_step_base requires source.path and source.sha256")
parameters = spec.get("parameters", {})
if not isinstance(parameters, dict):
raise ModelSpecError("parameters must be an object")
for name, entry in parameters.items():
if not isinstance(entry, dict) or "value" not in entry:
raise ModelSpecError(f"Parameter {name} must be an object with value")
_parameter_value(spec, name)
modifications = spec.get("modifications", [])
if not isinstance(modifications, list):
raise ModelSpecError("modifications must be an array")
seen_ids: set[str] = set()
for index, modification in enumerate(modifications):
if not isinstance(modification, dict):
raise ModelSpecError(f"modifications[{index}] must be an object")
feature_id = str(modification.get("id", "")).strip()
if not feature_id or feature_id in seen_ids:
raise ModelSpecError("Every modification requires a unique id")
seen_ids.add(feature_id)
operation = modification.get("operation")
if operation not in SUPPORTED_OPERATIONS:
raise ModelSpecError(f"Unsupported modification operation: {operation}")
outputs = spec.get("outputs")
if not isinstance(outputs, dict) or not outputs.get("step"):
raise ModelSpecError("outputs.step is required")
def geometry_facts(shape: Any) -> dict[str, Any]:
box = shape.bounding_box()
solids = list(shape.solids())
volume = sum(float(solid.volume) for solid in solids)
return {
"size_mm": [
round(float(box.size.X), 9),
round(float(box.size.Y), 9),
round(float(box.size.Z), 9),
],
"center_mm": [
round(float(box.center().X), 9),
round(float(box.center().Y), 9),
round(float(box.center().Z), 9),
],
"solid_count": len(solids),
"face_count": len(shape.faces()),
"edge_count": len(shape.edges()),
"volume_mm3": round(volume, 6),
}
def _rotation_for_axis(axis: str) -> tuple[float, float, float]:
normalized = axis.lower()
if normalized == "z":
return (0.0, 0.0, 0.0)
if normalized == "x":
return (0.0, 90.0, 0.0)
if normalized == "y":
return (-90.0, 0.0, 0.0)
raise ModelSpecError(f"Unsupported cylinder axis: {axis}")
def _make_tool(spec: dict[str, Any], modification: dict[str, Any]) -> Any:
operation = str(modification["operation"])
center = _vector3(spec, modification.get("center", [0, 0, 0]), "center")
if operation.endswith("_cylinder"):
diameter = resolve_number(spec, modification.get("diameter"), "diameter")
length = resolve_number(spec, modification.get("length"), "length")
if diameter <= 0 or length <= 0:
raise ModelSpecError("Cylinder diameter and length must be positive")
tool = Cylinder(
diameter / 2.0,
length,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
rotation=_rotation_for_axis(str(modification.get("axis", "z"))),
)
else:
size = _vector3(spec, modification.get("size"), "size")
if any(item <= 0 for item in size):
raise ModelSpecError("Box dimensions must be positive")
tool = Box(
*size,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
)
return Pos(*center) * tool
def _apply_modifications(shape: Any, spec: dict[str, Any]) -> Any:
result = shape
for modification in spec.get("modifications", []):
if modification.get("enabled", True) is False:
continue
tool = _make_tool(spec, modification)
operation = modification["operation"]
result = result + tool if operation.startswith("add_") else result - tool
return result
def _load_generator(spec_path: Path, reconstruction: dict[str, Any]) -> Any:
generator = reconstruction["generator"]
source_path = _resolve_relative(spec_path, str(generator["path"]), "generator.path")
if not source_path.is_file():
raise ModelSpecError(f"Generator does not exist: {source_path}")
module_spec = importlib.util.spec_from_file_location(
f"cad_model_{hash(source_path)}",
source_path,
)
if module_spec is None or module_spec.loader is None:
raise ModelSpecError(f"Cannot load generator: {source_path}")
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
entrypoint = str(generator.get("entrypoint", "gen_step"))
function = getattr(module, entrypoint, None)
if not callable(function):
raise ModelSpecError(f"Generator has no callable {entrypoint}: {source_path}")
return function()
def build_shape(spec_path: Path, spec: dict[str, Any] | None = None) -> Any:
resolved_spec = spec_path.expanduser().resolve()
payload = spec or _read_json(resolved_spec)
validate_model_spec(payload)
reconstruction = payload["reconstruction"]
if reconstruction["mode"] == "native_generator":
shape = _load_generator(resolved_spec, reconstruction)
else:
source = reconstruction["source"]
source_path = _resolve_relative(resolved_spec, str(source["path"]), "source.path")
if not source_path.is_file():
raise ModelSpecError(f"Exact STEP base does not exist: {source_path}")
actual_hash = _sha256(source_path)
if actual_hash != source["sha256"]:
raise ModelSpecError(
"Exact STEP base checksum changed; import it again instead of "
"silently rebuilding from a different source"
)
imported = import_step(source_path)
# build123d's STEP importer returns a topology wrapper that is readable
# and boolean-capable but is not always directly accepted by its STEP
# exporter. Normalize it to a Part while preserving the wrapped B-Rep.
shape = Part(imported.wrapped)
return _apply_modifications(shape, payload)
def build_model(spec_path: Path) -> dict[str, Any]:
resolved_spec = spec_path.expanduser().resolve()
payload = _read_json(resolved_spec)
validate_model_spec(payload)
outputs = payload["outputs"]
step_path = _resolve_relative(resolved_spec, str(outputs["step"]), "outputs.step")
step_path.parent.mkdir(parents=True, exist_ok=True)
reconstruction = payload["reconstruction"]
stl_value = outputs.get("stl")
stl_path = (
_resolve_relative(resolved_spec, str(stl_value), "outputs.stl")
if stl_value
else None
)
if reconstruction["mode"] == "native_generator":
generator_path = _resolve_relative(
resolved_spec,
str(reconstruction["generator"]["path"]),
"generator.path",
)
if generator_path.with_suffix(".step").resolve() != step_path:
raise ModelSpecError(
"V1 native generator output must use the generator basename"
)
cad_step = Path(__file__).resolve().parents[2] / "cad" / "scripts" / "step"
command = [sys.executable, str(cad_step), str(generator_path), "--force"]
if stl_path is not None:
if stl_path.parent != generator_path.parent:
raise ModelSpecError(
"V1 native generator STL must remain in the task directory"
)
command.extend(["--stl", stl_path.name])
completed = subprocess.run(
command,
cwd=resolved_spec.parent,
text=True,
capture_output=True,
)
if completed.returncode != 0:
raise ModelSpecError(
"CAD generator failed: "
+ (completed.stderr.strip() or completed.stdout.strip())
)
if not step_path.is_file():
raise ModelSpecError(f"CAD generator did not write {step_path}")
else:
shape = build_shape(resolved_spec, payload)
source_path = _resolve_relative(
resolved_spec,
str(reconstruction["source"]["path"]),
"source.path",
)
if not payload.get("modifications"):
# Empty exact-base rebuilds preserve the STEP byte-for-byte.
if source_path != step_path:
shutil.copy2(source_path, step_path)
else:
export_step(shape, step_path)
if stl_path is not None:
stl_path.parent.mkdir(parents=True, exist_ok=True)
export_stl(shape, stl_path)
exported_shape = Part(import_step(step_path).wrapped)
result: dict[str, Any] = {
"model_spec": str(resolved_spec),
"step": str(step_path),
"facts": geometry_facts(exported_shape),
}
if stl_path is not None:
result["stl"] = str(stl_path)
return result
def import_step_model(
source_step: Path,
task_dir: Path,
model_id: str | None = None,
) -> dict[str, Any]:
source = source_step.expanduser().resolve()
if not source.is_file() or source.suffix.lower() not in {".step", ".stp"}:
raise ModelSpecError(f"Input must be an existing STEP/STP file: {source}")
target_dir = task_dir.expanduser().resolve()
target_dir.mkdir(parents=True, exist_ok=True)
normalized_id = _safe_model_id(model_id or source.stem)
copied_source = target_dir / "source.step"
if copied_source.resolve() != source:
shutil.copy2(source, copied_source)
source_shape = import_step(copied_source)
source_facts = geometry_facts(source_shape)
spec_path = target_dir / "model-spec.json"
payload: dict[str, Any] = {
"schema_version": SCHEMA_VERSION,
"model_spec_kind": MODEL_SPEC_KIND,
"model_id": normalized_id,
"units": "mm",
"reconstruction": {
"mode": "exact_step_base",
"source": {
"path": "source.step",
"sha256": _sha256(copied_source),
"geometry_signature": source_facts,
},
"history_recovery": {
"status": "not_present_in_step",
"contract": (
"Preserve the imported B-Rep exactly as the immutable base; "
"represent later edits as named parametric modifications."
),
},
},
"parameters": {},
"modifications": [],
"outputs": {
"step": f"{normalized_id}.step",
"stl": f"{normalized_id}.stl",
},
"validation": {
"exact_base_required": True,
"geometry_signature_tolerance_mm": 1e-7,
},
}
_write_json(spec_path, payload)
result = build_model(spec_path)
result["source"] = str(copied_source)
result["source_sha256"] = payload["reconstruction"]["source"]["sha256"]
return result
def _numbers_close(first: Any, second: Any, tolerance: float) -> bool:
if isinstance(first, list) and isinstance(second, list):
return len(first) == len(second) and all(
_numbers_close(left, right, tolerance)
for left, right in zip(first, second)
)
if isinstance(first, (int, float)) and isinstance(second, (int, float)):
return math.isclose(
float(first),
float(second),
rel_tol=tolerance,
abs_tol=tolerance,
)
return first == second
def verify_model(spec_path: Path) -> dict[str, Any]:
resolved_spec = spec_path.expanduser().resolve()
payload = _read_json(resolved_spec)
shape = build_shape(resolved_spec, payload)
facts = geometry_facts(shape)
reconstruction = payload["reconstruction"]
result: dict[str, Any] = {
"model_spec": str(resolved_spec),
"valid": True,
"facts": facts,
"checks": [],
}
if reconstruction["mode"] == "exact_step_base" and not payload.get("modifications"):
expected = reconstruction["source"].get("geometry_signature", {})
source_path = _resolve_relative(
resolved_spec,
str(reconstruction["source"]["path"]),
"source.path",
)
output_path = _resolve_relative(
resolved_spec,
str(payload["outputs"]["step"]),
"outputs.step",
)
output_shape = (
Part(import_step(output_path).wrapped)
if output_path.is_file()
else None
)
output_facts = geometry_facts(output_shape) if output_shape is not None else {}
tolerance = float(
payload.get("validation", {}).get(
"geometry_signature_tolerance_mm",
1e-7,
)
)
signature_exact = all(
_numbers_close(expected.get(key), facts.get(key), tolerance)
for key in (
"size_mm",
"center_mm",
"solid_count",
"face_count",
"edge_count",
"volume_mm3",
)
)
exported_exact = (
output_path.is_file()
and _sha256(output_path) == _sha256(source_path)
and all(
_numbers_close(expected.get(key), output_facts.get(key), tolerance)
for key in (
"size_mm",
"center_mm",
"solid_count",
"face_count",
"edge_count",
"volume_mm3",
)
)
)
exact = signature_exact and exported_exact
result["checks"].append(
{
"check": "exact_base_geometry_signature",
"passed": exact,
"expected": expected,
"actual": facts,
"exported": output_facts,
"byte_identical_step": (
output_path.is_file()
and _sha256(output_path) == _sha256(source_path)
),
}
)
result["valid"] = exact
else:
result["checks"].append(
{
"check": "model_spec_execution",
"passed": True,
"detail": "The parameterized model spec executed successfully.",
}
)
return result
def set_parameter(spec_path: Path, name: str, value: float) -> dict[str, Any]:
resolved_spec = spec_path.expanduser().resolve()
payload = _read_json(resolved_spec)
validate_model_spec(payload)
parameters = payload["parameters"]
if name not in parameters:
raise ModelSpecError(
f"Unknown parameter {name}; add a named parameter and feature binding first"
)
parameters[name]["value"] = value
validate_model_spec(payload)
_write_json(resolved_spec, payload)
return {
"model_spec": str(resolved_spec),
"parameter": name,
"value": value,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Create, build, modify, and verify per-part model-spec.json files."
)
subparsers = parser.add_subparsers(dest="command", required=True)
import_parser = subparsers.add_parser(
"import-step",
help="Create an exact STEP-base model spec in a task-owned directory.",
)
import_parser.add_argument("source_step", type=Path)
import_parser.add_argument("--task-dir", type=Path, required=True)
import_parser.add_argument("--model-id")
build_command = subparsers.add_parser(
"build",
help="Regenerate STEP/STL from a model specification.",
)
build_command.add_argument("model_spec", type=Path)
verify_command = subparsers.add_parser(
"verify",
help="Validate a model specification and exact-base signature.",
)
verify_command.add_argument("model_spec", type=Path)
set_command = subparsers.add_parser(
"set",
help="Change one existing named parameter in a model specification.",
)
set_command.add_argument("model_spec", type=Path)
set_command.add_argument("name")
set_command.add_argument("value", type=float)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
if args.command == "import-step":
result = import_step_model(args.source_step, args.task_dir, args.model_id)
elif args.command == "build":
result = build_model(args.model_spec)
elif args.command == "verify":
result = verify_model(args.model_spec)
else:
result = set_parameter(args.model_spec, args.name, args.value)
except ModelSpecError as exc:
print(json.dumps({"error": str(exc)}, ensure_ascii=False, indent=2))
return 2
print(json.dumps(result, ensure_ascii=False, indent=2))
if args.command == "verify" and result.get("valid") is False:
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
+124 -7
View File
@@ -76,6 +76,11 @@ EXPERIENCE_FAMILY_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
("shaft", ("shaft", "轴类", "传动轴")),
("flange", ("flange", "法兰")),
("bushing", ("bushing", "sleeve", "轴套", "衬套")),
("plate", ("plate", "mounting plate", "安装板", "平板")),
("bracket", ("bracket", "mounting bracket", "支架", "托架")),
("block", ("block", "rectangular body", "方块", "块体")),
("manifold", ("manifold", "valve body", "housing", "歧管", "阀体", "壳体")),
("rotational_part", ("roller", "pulley", "cylindrical body", "滚轮", "带轮")),
)
EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
@@ -90,8 +95,90 @@ EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
("conical_transition", ("cone", "taper", "", "斜面过渡")),
("gear_teeth", ("gear", "teeth", "齿轮", "齿")),
("coaxial_cylindrical_stack", ("stepped shaft", "阶梯轴", "同轴", "轴肩")),
(
"repeated_axial_hole_pattern",
(
"mounting holes",
"corner holes",
"four holes",
"multiple holes",
"bolt holes",
"安装孔",
"四个孔",
"多个孔",
),
),
("planar_dominant_body", ("plate", "bracket", "flat body", "", "支架")),
(
"multi_level_planar_profile",
("pocket", "recess", "stepped plate", "台阶面", "凹槽", "沉台"),
),
(
"rotational_body",
("cylindrical body", "roller", "pulley", "revolved", "圆柱体", "滚轮", "回转体"),
),
(
"stepped_rotational_profile",
("stepped shaft", "multiple diameters", "shoulder", "阶梯轴", "多段直径", "轴肩"),
),
("shaft_like_body", ("shaft", "axle", "rod", "", "长杆")),
(
"flange_like_rotational_body",
("flange", "flanged", "法兰", "带法兰"),
),
(
"cylindrical_feature_network",
("bore", "passage", "drilled holes", "孔系", "流道", "钻孔"),
),
(
"multi_axis_passage",
("cross hole", "intersecting passage", "multi-axis", "交叉孔", "多轴孔"),
),
)
# The router vocabulary is request-facing (plate, bracket, flange), while the
# distilled library vocabulary is geometry-facing (patterned_plate,
# plate_or_bracket, flanged_rotational_part). Keep the query semantic by
# expanding a request family into compatible generalized scopes; this never
# uses held-out case geometry or instance parameters.
EXPERIENCE_FAMILY_SCOPE_ALIASES: dict[str, set[str]] = {
"flanged_hub_adapter": {
"flanged_hub_adapter",
"flanged_rotational_part",
"hollow_or_stepped_shaft",
"rotational_part",
},
"connecting_rod": {
"connecting_rod",
"patterned_prismatic_part",
"plate_or_bracket",
},
"gear": {"gear", "rotational_part"},
"shaft": {"shaft", "hollow_or_stepped_shaft", "rotational_part"},
"flange": {"flange", "flanged_rotational_part", "rotational_part"},
"bushing": {"bushing", "hollow_or_stepped_shaft", "rotational_part"},
"plate": {"plate", "patterned_plate", "plate_or_bracket"},
"bracket": {
"bracket",
"plate_or_bracket",
"patterned_plate",
"patterned_prismatic_part",
},
"block": {"block", "prismatic_block", "patterned_prismatic_part"},
"manifold": {
"manifold",
"multi_axis_manifold",
"prismatic_block",
"patterned_prismatic_part",
},
"rotational_part": {
"rotational_part",
"shaft",
"hollow_or_stepped_shaft",
"flanged_rotational_part",
},
}
def contains_any(text: str, terms: tuple[str, ...]) -> bool:
return any(term in text for term in terms)
@@ -263,7 +350,10 @@ def classify_edit_context(
suffix = source.suffix.lower() if source else ""
text = request.lower()
if suffix in {".py", ".scad", ".js", ".mjs"}:
if suffix == ".json" and source and source.name == "model-spec.json":
mode = "model_spec_parameter_edit"
reason = "A per-part model specification exists; update its named JSON parameters or modification features."
elif suffix in {".py", ".scad", ".js", ".mjs"}:
mode = "native_parameter_edit"
reason = "An editable generator exists; update its named parameters or feature source."
elif contains_any(text, MAJOR_EDIT_TERMS):
@@ -304,7 +394,7 @@ def classify_edit_context(
"preservation_contract": [
"Preserve the source coordinate frame unless the request explicitly changes it.",
"Preserve unmodified interfaces, datums, and feature relationships.",
"Prefer the native generator over inferred STEP reconstruction when both exist.",
"Prefer the per-part model-spec.json and native generator over inferred STEP reconstruction when they exist.",
"Use generalized experience only as method guidance, never as replacement source geometry.",
],
}
@@ -355,13 +445,20 @@ def load_experience_library(
if item.strip()
}
normalized_family = family.strip().lower().replace(" ", "_") if family else None
compatible_scopes = (
EXPERIENCE_FAMILY_SCOPE_ALIASES.get(
normalized_family, {normalized_family}
)
if normalized_family
else set()
)
selected: list[object] = []
for item in payload.get("experiences", []):
if not isinstance(item, dict):
continue
scope = set(item.get("scope", []))
if "global" not in scope:
if not normalized_family or normalized_family not in scope:
if not compatible_scopes or scope.isdisjoint(compatible_scopes):
continue
required = set(item.get("when", {}).get("features", []))
if required and not required.issubset(requested_features):
@@ -371,6 +468,7 @@ def load_experience_library(
"schema_version": "2.0",
"context_kind": "generalized_cad_experience_query",
"family": normalized_family,
"compatible_scopes": sorted(compatible_scopes),
"requested_features": sorted(requested_features),
"experiences": selected,
"policy": {
@@ -480,6 +578,12 @@ def route(args: argparse.Namespace) -> dict[str, object]:
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
backend_info = registry["backends"][selected.name]
source_of_truth = backend_info["source_of_truth"]
if (
edit_context
and edit_context.get("modification_mode") == "model_spec_parameter_edit"
):
source_of_truth = "per-part model-spec.json with a backend generator"
gap = selected.score - max((scores[name].score for name in scores if name != selected.name), default=0)
confidence = "high" if gap >= 40 else "medium" if gap >= 15 else "low"
result: dict[str, object] = {
@@ -498,7 +602,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
"workflow_profiles": profiles,
"downstream_skills": downstream,
"requested_outputs": outputs,
"source_of_truth": backend_info["source_of_truth"],
"source_of_truth": source_of_truth,
"experience_context": experience_context,
"design_plan": {
"plan_kind": "backend_neutral_parametric_design_plan",
@@ -506,7 +610,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
"part_family": experience_family or "unclassified",
"requested_feature_roles": sorted(set(experience_features)),
"selected_backend": selected.name,
"source_of_truth": backend_info["source_of_truth"],
"source_of_truth": source_of_truth,
"generalized_methods": [
{
key: item[key]
@@ -517,6 +621,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
"check",
"repair",
"confidence",
"reconstruction_grammar",
)
if key in item
}
@@ -527,6 +632,17 @@ def route(args: argparse.Namespace) -> dict[str, object]:
)
if isinstance(item, dict)
],
"reconstruction_grammars": [
item["reconstruction_grammar"]
for item in (
experience_context.get("experiences", [])
if experience_context
else []
)
if isinstance(item, dict)
and item.get("kind") == "reconstruction_grammar"
and isinstance(item.get("reconstruction_grammar"), dict)
],
"experience_status": (
"pending_source_classification"
if needs_source_classification
@@ -566,9 +682,10 @@ def route(args: argparse.Namespace) -> dict[str, object]:
if edit_context
else [
"Resolve explicit user dimensions and preserve them as authoritative parameters.",
"Instantiate matched reconstruction grammars as symbolic model-spec.json parameters, datums, and canonical feature stages; never copy teacher dimensions.",
"Translate requested feature roles and matched generalized methods into backend operations.",
"Generate the editable source of truth and STEP-first artifact when requested.",
"Validate topology, dimensions, feature relationships, and export integrity.",
"Validate topology, dimensions, feature relationships, surface mix, and export integrity against the grammar validation roles.",
]
),
},
@@ -631,7 +748,7 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument(
"--edit-source",
type=Path,
help="Existing generator, STEP/STP, or private case JSON to modify.",
help="Existing model-spec.json, generator, or explicit STEP/STP to modify; private case JSON is forbidden.",
)
parser.add_argument("--assembly", action="store_true", help="The requested result is an assembly.")
parser.add_argument("--browser-controls", action="store_true", help="Interactive browser parameter controls are required.")
@@ -0,0 +1,130 @@
from __future__ import annotations
import importlib.util
import json
import tempfile
import unittest
from pathlib import Path
from build123d import Box, Cylinder, Pos, export_step, import_step
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "model_spec.py"
MODULE_SPEC = importlib.util.spec_from_file_location("cad_router_model_spec", SCRIPT_PATH)
assert MODULE_SPEC is not None and MODULE_SPEC.loader is not None
MODEL_SPEC = importlib.util.module_from_spec(MODULE_SPEC)
MODULE_SPEC.loader.exec_module(MODEL_SPEC)
class ModelSpecTests(unittest.TestCase):
def test_affine_parameter_expression(self) -> None:
payload = {
"parameters": {
"extension": {
"value": 2.0,
}
}
}
self.assertEqual(
MODEL_SPEC.resolve_number(
payload,
{
"parameter": "extension",
"scale": 0.5,
"offset": 14.1,
},
"center[2]",
),
15.1,
)
def test_import_step_exact_base_round_trip(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "teacher.step"
export_step(Box(30, 20, 10) - Pos(0, 0, -1) * Cylinder(3, 12), source)
result = MODEL_SPEC.import_step_model(
source,
root / "task",
"round_trip",
)
verification = MODEL_SPEC.verify_model(
Path(result["model_spec"])
)
self.assertTrue(verification["valid"])
self.assertEqual(
verification["checks"][0]["check"],
"exact_base_geometry_signature",
)
self.assertTrue((root / "task" / "round_trip.step").is_file())
self.assertTrue((root / "task" / "round_trip.stl").is_file())
def test_parameterized_cut_rebuilds_from_json(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "source.step"
export_step(Box(40, 30, 12), source)
imported = MODEL_SPEC.import_step_model(
source,
root / "task",
"parameterized_cut",
)
spec_path = Path(imported["model_spec"])
payload = json.loads(spec_path.read_text(encoding="utf-8"))
payload["parameters"] = {
"bore_diameter": {
"value": 6,
"unit": "mm",
"role": "central_bore_diameter",
},
"bore_length": {
"value": 16,
"unit": "mm",
"role": "through_cut_length",
},
}
payload["modifications"] = [
{
"id": "central_bore",
"operation": "cut_cylinder",
"axis": "z",
"center": [0, 0, 0],
"diameter": {"parameter": "bore_diameter"},
"length": {"parameter": "bore_length"},
"enabled": True,
}
]
spec_path.write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
first = MODEL_SPEC.build_model(spec_path)
first_volume = first["facts"]["volume_mm3"]
MODEL_SPEC.set_parameter(spec_path, "bore_diameter", 12)
second = MODEL_SPEC.build_model(spec_path)
self.assertLess(second["facts"]["volume_mm3"], first_volume)
rebuilt = import_step(second["step"])
self.assertGreater(len(rebuilt.faces()), 0)
def test_exact_base_rejects_changed_source(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
source = root / "source.step"
export_step(Box(10, 10, 10), source)
imported = MODEL_SPEC.import_step_model(
source,
root / "task",
"checksum_guard",
)
export_step(Box(12, 10, 10), root / "task" / "source.step")
with self.assertRaises(MODEL_SPEC.ModelSpecError):
MODEL_SPEC.build_model(Path(imported["model_spec"]))
if __name__ == "__main__":
unittest.main()
@@ -227,6 +227,115 @@ class CadRouterTests(unittest.TestCase):
plan["generalized_methods"][0]["id"],
)
def test_design_plan_exposes_promoted_reconstruction_grammar(self) -> None:
payload = {
"schema_version": "2.0",
"library_kind": "generalized_cad_experience",
"policy": {
"instance_parameters_allowed": False,
"absolute_coordinates_allowed": False,
"single_case_promotion_allowed": False,
"llm_semantic_review_required": True,
"draft_evidence_verified": True,
"router_consumable": True,
},
"experiences": [
{
"id": "reconstruction.flanged_hub_adapter.primary",
"kind": "reconstruction_grammar",
"scope": ["flanged_hub_adapter"],
"when": {"features": ["base_flange", "hollow_sleeve"]},
"guidance": "Establish shared datums before composing the envelope.",
"reconstruction_grammar": {
"parameter_roles": ["overall_long_span"],
"datum_roles": ["part_center", "primary_axis"],
"feature_roles": ["base_flange", "hollow_sleeve"],
"relation_roles": [],
"canonical_stages": [
{
"id": "establish_reference_frame",
"operation": "define_datums",
"feature_roles": [],
"reference_roles": ["part_center", "primary_axis"],
}
],
"cardinality_classes": ["cylinder_dense"],
"validation_roles": ["closed_solid"],
},
"support": 30,
"confidence": 0.9,
}
],
}
with tempfile.TemporaryDirectory() as directory:
library = Path(directory) / "library.json"
library.write_text(json.dumps(payload), encoding="utf-8")
result = route_module.route(
self.parse(
"生成一个带法兰轴套",
"--output",
"step",
"--experience-library",
str(library),
)
)
grammars = result["design_plan"]["reconstruction_grammars"]
self.assertEqual(1, len(grammars))
self.assertEqual(["overall_long_span"], grammars[0]["parameter_roles"])
def test_request_family_matches_geometry_facing_distilled_scope(self) -> None:
payload = {
"schema_version": "2.0",
"library_kind": "generalized_cad_experience",
"policy": {
"instance_parameters_allowed": False,
"absolute_coordinates_allowed": False,
"single_case_promotion_allowed": False,
"llm_semantic_review_required": True,
"draft_evidence_verified": True,
"router_consumable": True,
},
"experiences": [
{
"id": "motif.patterned_plate.holes",
"kind": "feature_motif",
"scope": ["patterned_plate"],
"when": {
"features": [
"planar_dominant_body",
"repeated_axial_hole_pattern",
]
},
"guidance": "Build the plate first, then place holes from symmetry.",
"support": 40,
"confidence": 0.94,
}
],
}
with tempfile.TemporaryDirectory() as directory:
library = Path(directory) / "library.json"
library.write_text(json.dumps(payload), encoding="utf-8")
result = route_module.route(
self.parse(
"Generate a mounting plate with four corner holes",
"--output",
"step",
"--experience-library",
str(library),
)
)
context = result["experience_context"]
self.assertEqual("plate", context["family"])
self.assertIn("patterned_plate", context["compatible_scopes"])
self.assertIn("planar_dominant_body", context["requested_features"])
self.assertIn(
"repeated_axial_hole_pattern", context["requested_features"]
)
self.assertEqual(
["motif.patterned_plate.holes"],
[item["id"] for item in context["experiences"]],
)
def test_step_edit_uses_source_and_library_not_private_case(self) -> None:
result = route_module.route(
self.parse(