feat: add per-part CAD model specs

This commit is contained in:
Jerry
2026-07-23 15:12:28 +08:00
parent 304911a69f
commit a2f3ba6a90
35 changed files with 1351 additions and 85 deletions
@@ -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"
}
}