feat: strengthen CAD experience distillation and routing
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 78 KiB |
@@ -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.
|
||||
@@ -49,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.
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ constraints, names, or feature history. Therefore:
|
||||
## Parametric modification layer
|
||||
|
||||
Supported V1 operations are `add_box`, `cut_box`, `add_cylinder`, and
|
||||
`cut_cylinder`. Values may be literals or parameter references:
|
||||
`cut_cylinder`. Values may be literals, direct parameter references, or affine
|
||||
parameter expressions:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -82,6 +83,21 @@ Supported V1 operations are `add_box`, `cut_box`, `add_cylinder`, and
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -111,11 +111,29 @@ def resolve_number(spec: dict[str, Any], value: Any, field: str) -> float:
|
||||
raise ModelSpecError(f"{field} must be numeric")
|
||||
if isinstance(value, (int, float)):
|
||||
result = float(value)
|
||||
elif isinstance(value, dict) and set(value) == {"parameter"}:
|
||||
result = _parameter_value(spec, str(value["parameter"]))
|
||||
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 {{\"parameter\": \"name\"}}"
|
||||
f"{field} must be a number or a parameter expression"
|
||||
)
|
||||
if not math.isfinite(result):
|
||||
raise ModelSpecError(f"{field} must be finite")
|
||||
|
||||
@@ -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)
|
||||
@@ -358,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):
|
||||
@@ -374,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": {
|
||||
@@ -526,6 +621,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
"check",
|
||||
"repair",
|
||||
"confidence",
|
||||
"reconstruction_grammar",
|
||||
)
|
||||
if key in item
|
||||
}
|
||||
@@ -536,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
|
||||
@@ -575,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.",
|
||||
]
|
||||
),
|
||||
},
|
||||
|
||||
@@ -17,6 +17,27 @@ 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)
|
||||
|
||||
@@ -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(
|
||||
|
||||