feat: add per-part CAD model specs
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 431 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
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"
|
||||
}
|
||||
}
|
||||