This commit is contained in:
2026-07-27 16:01:32 +08:00
parent 9e74a5254a
commit 0adaac2b2a
25 changed files with 34875 additions and 9871 deletions
Binary file not shown.
+58 -34
View File
@@ -1,14 +1,17 @@
{
"schema_version": "1.0",
"request": "生成中心距120mm、大头孔36mm、小头孔20mm、厚14mm的发动机连杆,导出STEPSTL,后续可以修改参数",
"request": "Modify the existing build123d connecting rod from the CAD Viewer selection: add a 2 mm wide blind slot along the selected segment on face f18, regenerate STEP, STL, and CAD Viewer mesh/topology assets.",
"route": {
"selected_backend": "build123d",
"runner_skill": "cad",
"project": "text-to-cad",
"confidence": "high",
"fallback_order": [
"simplecadapi",
"cadam"
],
"operation": "modify",
"modification_strategy": "native_parameter_editing",
"decision_path": "route-decision.json"
},
"source": {
@@ -26,7 +29,11 @@
"role": "secondary"
},
{
"path": "connecting_rod_20260722T071141Z.png",
"path": ".connecting_rod.step.glb",
"role": "viewer_mesh_topology"
},
{
"path": "connecting_rod_slot_review_20260723T100007Z.png",
"role": "verification_snapshot"
}
],
@@ -38,57 +45,74 @@
"small_end_outer_diameter_mm": 36.0,
"small_end_bore_diameter_mm": 20.0,
"web_width_mm": 24.0,
"pocket_depth_each_side_mm": 3.0
"pocket_depth_each_side_mm": 3.0,
"viewer_slot_start_mm": [
-28.73179347578467,
-0.3844822039559189
],
"viewer_slot_end_mm": [
24.36043904920138,
-0.24367243243227724
],
"viewer_slot_surface_z_mm": 11.0,
"viewer_slot_width_mm": 2.0,
"viewer_slot_blind_depth_mm": 2.0,
"viewer_slot_bottom_z_mm": 9.0
},
"experience": {
"library": "../../../cad-experience-library/library.json",
"context_kind": "generalized_cad_experience_query",
"methods_applied": []
},
"assumptions": [
"The selected face f18 is the existing top-pocket floor at z=11 mm, so the blind slot cuts inward into the material from z=11 mm down to z=9 mm.",
"The CAD edit intent marks depth as blind but does not provide a numeric depth, so a conservative 2 mm blind depth was used.",
"This V1 sample demonstrates routing and editability rather than a production-rated engine component.",
"Material, fatigue loads, bearing fits, lubrication, fasteners, tolerances, and split big-end construction are intentionally out of scope."
],
"validation": [
{
"check": "closed solid count",
"result": 1,
"check": "STEP generation from editable source",
"result": "scripts/cad step regenerated connecting_rod.step, connecting_rod.stl, and hidden CAD Viewer GLB/topology assets from connecting_rod.py.",
"status": "pass"
},
{
"check": "bounding box millimeters",
"result": [
166.0,
56.0,
14.0
],
"status": "pass"
},
{
"check": "volume cubic millimeters",
"result": 50729.813,
"status": "pass"
},
{
"check": "bore radii millimeters",
"result": [
10.0,
18.0
],
"status": "pass"
},
{
"check": "STEP topology",
"check": "closed solid topology",
"result": {
"kind": "part",
"shape_count": 1,
"face_count": 18,
"edge_count": 42,
"warnings": []
"face_count": 23,
"edge_count": 54,
"bounds_mm": [
165.978199,
55.977382,
14.0
]
},
"status": "pass"
},
{
"check": "isometric snapshot review",
"result": "Both bores, the connected web, and the top pocket are visibly present; no disconnected body is visible.",
"check": "selected-face slot placement",
"result": "Original selected face f18 remains the top-pocket floor at z=11 mm; new slot bottom face f23 is centered at [-2.185677, -0.314077, 9.0].",
"status": "pass"
},
{
"check": "blind slot depth",
"result": "Slot side faces span z=9.0 mm to z=11.0 mm.",
"status": "pass"
},
{
"check": "slot width",
"result": "Slot end faces are cylinders with radius 1.0 mm, matching the requested 2.0 mm slot width.",
"status": "pass"
},
{
"check": "snapshot review",
"result": "Isometric snapshot shows the connecting rod, existing top pocket, and the new narrow blind slot without visible breakage or disconnected geometry.",
"status": "pass"
}
],
"viewer_links": [
"http://127.0.0.1:4178/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1&file=connecting_rod.step"
"http://127.0.0.1:5175/?dir=%2FUsers%2Flk%2FProjects%2FcadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1&file=connecting_rod.step"
]
}
@@ -1,6 +1,21 @@
"""Parametric V1 connecting-rod sample routed to the build123d backend."""
from build123d import Align, Box, BuildPart, BuildSketch, Cylinder, Location, Locations, Mode, Plane, SlotOverall, extrude
from math import atan2, degrees, hypot
from build123d import (
Align,
Box,
BuildPart,
BuildSketch,
Cylinder,
Location,
Locations,
Mode,
Plane,
SlotOverall,
extrude,
fillet,
)
CENTER_DISTANCE = 120.0
@@ -16,6 +31,61 @@ POCKET_LENGTH = 72.0
POCKET_WIDTH = 12.0
POCKET_DEPTH = 3.0
VIEWER_SLOT_START = (-28.73179347578467, -0.3844822039559189)
VIEWER_SLOT_END = (24.36043904920138, -0.24367243243227724)
VIEWER_SLOT_SURFACE_Z = THICKNESS - POCKET_DEPTH
VIEWER_SLOT_WIDTH = 2.0
VIEWER_SLOT_BLIND_DEPTH = 2.0
VIEWER_EDGE_E14_FILLET_RADIUS = 1.0
VIEWER_HOLE_CENTER = (-22.122781376227962, -9.435525387895417)
VIEWER_HOLE_DIAMETER = 6.0
VIEWER_HOLE_BLIND_DEPTH = 6.0
VIEWER_HOLE_SURFACE_Z = THICKNESS
VIEWER_HOLE_BOOLEAN_OVERTRAVEL = 0.2
VIEWER_SLOT_CENTER = (
(VIEWER_SLOT_START[0] + VIEWER_SLOT_END[0]) / 2.0,
(VIEWER_SLOT_START[1] + VIEWER_SLOT_END[1]) / 2.0,
)
VIEWER_SLOT_LENGTH = hypot(
VIEWER_SLOT_END[0] - VIEWER_SLOT_START[0],
VIEWER_SLOT_END[1] - VIEWER_SLOT_START[1],
)
VIEWER_SLOT_ROTATION = degrees(
atan2(
VIEWER_SLOT_END[1] - VIEWER_SLOT_START[1],
VIEWER_SLOT_END[0] - VIEWER_SLOT_START[0],
)
)
def _viewer_edge_e14(part):
"""Select the big-end top outer edge selected in CAD Viewer as e14."""
selected_edges = []
for edge in part.edges():
try:
edge_radius = edge.radius
edge_center = edge.arc_center
edge_normal = edge.normal()
except Exception:
continue
if abs(edge_radius - BIG_END_OUTER_DIAMETER / 2.0) > 0.05:
continue
if abs(edge_center.X + CENTER_DISTANCE / 2.0) > 0.05:
continue
if abs(edge_center.Y) > 0.05:
continue
if abs(edge_center.Z - THICKNESS) > 0.05:
continue
if abs(edge_normal.Z) < 1.0 - 1e-4:
continue
selected_edges.append(edge)
if len(selected_edges) != 1:
raise RuntimeError(f"Expected one viewer-selected e14 edge, found {len(selected_edges)}")
return selected_edges
def gen_step():
"""Return a closed, STEP-ready connecting-rod solid in millimeters."""
@@ -67,6 +137,32 @@ def gen_step():
SlotOverall(width=POCKET_LENGTH, height=POCKET_WIDTH)
extrude(amount=POCKET_DEPTH, mode=Mode.SUBTRACT)
with BuildSketch(Plane.XY.offset(VIEWER_SLOT_SURFACE_Z - VIEWER_SLOT_BLIND_DEPTH)):
with Locations(Location((VIEWER_SLOT_CENTER[0], VIEWER_SLOT_CENTER[1], 0.0))):
SlotOverall(
width=VIEWER_SLOT_LENGTH,
height=VIEWER_SLOT_WIDTH,
rotation=VIEWER_SLOT_ROTATION,
)
extrude(amount=VIEWER_SLOT_BLIND_DEPTH, mode=Mode.SUBTRACT)
with Locations(
Location(
(
VIEWER_HOLE_CENTER[0],
VIEWER_HOLE_CENTER[1],
VIEWER_HOLE_SURFACE_Z - VIEWER_HOLE_BLIND_DEPTH,
)
)
):
Cylinder(
radius=VIEWER_HOLE_DIAMETER / 2.0,
height=VIEWER_HOLE_BLIND_DEPTH + VIEWER_HOLE_BOOLEAN_OVERTRAVEL,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
result = rod.part
result = fillet(_viewer_edge_e14(result), radius=VIEWER_EDGE_E14_FILLET_RADIUS)
result.label = "cad_router_v1_connecting_rod"
return result
Binary file not shown.
Binary file not shown.
@@ -1,21 +1,13 @@
{
"schema_version": "1.0",
"request": "参考图片生成一个8孔圆法兰,中心孔直径20毫米,外径50毫米,厚度4毫米,螺丝孔使用M3",
"request": "参考图片生成一个8孔圆法兰,中心孔直径20毫米,外径50毫米,厚度4毫米,螺丝孔使用M3;根据 viewer 选中的几何位置添加2x2、直径2毫米、8毫米间距的贯穿孔阵列、一个3毫米贯穿沉头孔,以及一个10x6x1毫米矩形口袋",
"route": {
"selected_backend": "build123d",
"runner_skill": "cad",
"project": "text-to-cad",
"fallback_order": [
"simplecadapi",
"cadam"
],
"workflow_profiles": [
"requirement_refinement",
"visual_repair"
],
"downstream_skills": [
"cad-viewer"
]
"fallback_order": ["simplecadapi", "cadam"],
"workflow_profiles": ["requirement_refinement", "visual_repair", "source_modification"],
"downstream_skills": ["cad-viewer"]
},
"source": {
"path": "flange_8x_m3.py",
@@ -30,6 +22,10 @@
{
"path": "flange_8x_m3.stl",
"role": "secondary"
},
{
"path": ".flange_8x_m3.step.glb",
"role": "viewer_mesh_topology"
}
],
"parameters": {
@@ -39,12 +35,37 @@
"bolt_hole_count": 8,
"bolt_hole_diameter_mm": 3.4,
"bolt_circle_diameter_mm": 40.0,
"angular_spacing_deg": 45.0
"angular_spacing_deg": 45.0,
"viewer_pattern_center_mm": [8.077088362294017, 15.744119598589815],
"viewer_pattern_hole_diameter_mm": 2.0,
"viewer_pattern_rows": 2,
"viewer_pattern_columns": 2,
"viewer_pattern_pitch_x_mm": 8.0,
"viewer_pattern_pitch_y_mm": 8.0,
"viewer_pattern_depth": "through",
"viewer_pattern_hole_centers_mm": [
[4.077088362294017, 11.744119598589815],
[12.077088362294017, 11.744119598589815],
[4.077088362294017, 19.744119598589815],
[12.077088362294017, 19.744119598589815]
],
"viewer_countersink_center_mm": [-14.6034432889167, -4.110394299469192],
"viewer_countersink_hole_diameter_mm": 3.0,
"viewer_countersink_diameter_mm": 6.0,
"viewer_countersink_angle_deg": 90.0,
"viewer_countersink_depth": "through",
"viewer_pocket_center_mm": [6.197199513379157, -14.374353062530322],
"viewer_pocket_shape": "rectangle",
"viewer_pocket_width_mm": 10.0,
"viewer_pocket_height_mm": 6.0,
"viewer_pocket_depth_mm": 1.0,
"viewer_pocket_bottom_z_mm": 3.0
},
"assumptions": [
"M3螺丝孔按普通间隙通孔处理,孔径采用3.4毫米,不是M3攻丝底孔。",
"未指定分度圆,采用40毫米分度圆,以保持孔到外圆约3.3毫米的最小径向边距。",
"参考图中的车削纹理作为外观参考,不生成削弱4毫米薄法兰的深槽。"
"参考图中的车削纹理作为外观参考,不生成削弱4毫米薄法兰的深槽。",
"矩形口袋按 surface_xy 对齐全局 XY,沿选中面法向进入材料,深度1毫米。"
],
"validation": [
{
@@ -54,15 +75,32 @@
"step_valid": true,
"solid_count": 1,
"exact_size_mm": [50.0, 50.0, 4.0],
"topology_faces": 24,
"topology_edges": 62,
"m3_clearance_hole_count": 8,
"m3_clearance_hole_diameter_mm": 3.4,
"bolt_circle_diameter_mm": 40.0,
"angular_spacing_deg": 45.0,
"visual_review": "passed_iso_opposite_top_front"
"viewer_pattern_hole_count": 4,
"viewer_pattern_hole_diameter_mm": 2.0,
"viewer_countersink_hole_diameter_mm": 3.0,
"viewer_countersink_diameter_mm": 6.0,
"viewer_countersink_angle_deg": 90.0,
"viewer_pocket_bottom_center_mm": [6.197199513379157, -14.374353062530322, 3.0],
"viewer_pocket_bottom_size_mm": [10.0, 6.0],
"viewer_pocket_depth_mm": 1.0,
"viewer_mesh_topology": ".flange_8x_m3.step.glb",
"visual_review": "passed_viewer_pocket_top"
}
},
{
"path": "review/viewer_pocket_top_20260724T031341Z.png",
"status": "passed",
"checks": {
"snapshot_camera": "top",
"pocket_visible": true
}
}
],
"viewer_links": [
"http://127.0.0.1:4178/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fflange_8x_m3&file=flange_8x_m3.step"
"http://127.0.0.1:5174/?dir=%2FUsers%2Flk%2FProjects%2FcadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fflange_8x_m3&file=flange_8x_m3.step"
]
}
@@ -1,8 +1,8 @@
"""Parametric eight-hole circular flange generated with build123d."""
from math import cos, radians, sin
from math import cos, radians, sin, tan
from build123d import Align, BuildPart, Cylinder, Location, Locations, Mode
from build123d import Align, Box, BuildPart, Cone, Cylinder, Location, Locations, Mode
OUTER_DIAMETER_MM = 50.0
@@ -11,6 +11,32 @@ THICKNESS_MM = 4.0
BOLT_HOLE_COUNT = 8
M3_CLEARANCE_DIAMETER_MM = 3.4
BOLT_CIRCLE_DIAMETER_MM = 40.0
M4_THREAD_SPEC = "M4"
M4_TAP_DRILL_DIAMETER_MM = 3.3
M4_THREAD_DEPTH_MM = 6.0
M4_THREAD_HOLE_CENTER_MM = (-6.104359972902419, -13.137483558259564)
M4_THREAD_HOLE_FACE_Z_MM = THICKNESS_MM
M4_THREAD_HOLE_DIRECTION = (0.0, 0.0, -1.0)
VIEWER_PATTERN_CENTER_MM = (8.077088362294017, 15.744119598589815)
VIEWER_PATTERN_HOLE_DIAMETER_MM = 2.0
VIEWER_PATTERN_ROWS = 2
VIEWER_PATTERN_COLUMNS = 2
VIEWER_PATTERN_PITCH_X_MM = 8.0
VIEWER_PATTERN_PITCH_Y_MM = 8.0
VIEWER_PATTERN_FACE_Z_MM = THICKNESS_MM
VIEWER_PATTERN_DIRECTION = (0.0, 0.0, -1.0)
VIEWER_COUNTERSINK_CENTER_MM = (-14.6034432889167, -4.110394299469192)
VIEWER_COUNTERSINK_HOLE_DIAMETER_MM = 3.0
VIEWER_COUNTERSINK_DIAMETER_MM = 6.0
VIEWER_COUNTERSINK_ANGLE_DEG = 90.0
VIEWER_COUNTERSINK_FACE_Z_MM = THICKNESS_MM
VIEWER_COUNTERSINK_DIRECTION = (0.0, 0.0, -1.0)
VIEWER_POCKET_CENTER_MM = (6.197199513379157, -14.374353062530322)
VIEWER_POCKET_WIDTH_MM = 10.0
VIEWER_POCKET_HEIGHT_MM = 6.0
VIEWER_POCKET_DEPTH_MM = 1.0
VIEWER_POCKET_FACE_Z_MM = THICKNESS_MM
VIEWER_POCKET_DIRECTION = (0.0, 0.0, -1.0)
def polar_point(radius: float, angle_degrees: float) -> tuple[float, float]:
@@ -46,6 +72,65 @@ def gen_step():
mode=Mode.SUBTRACT,
)
m4_x_pos, m4_y_pos = M4_THREAD_HOLE_CENTER_MM
m4_cut_depth = min(M4_THREAD_DEPTH_MM, THICKNESS_MM)
with Locations(Location((m4_x_pos, m4_y_pos, M4_THREAD_HOLE_FACE_Z_MM - m4_cut_depth))):
Cylinder(
radius=M4_TAP_DRILL_DIAMETER_MM / 2.0,
height=m4_cut_depth + 1.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
pattern_center_x, pattern_center_y = VIEWER_PATTERN_CENTER_MM
pattern_start_x = pattern_center_x - (VIEWER_PATTERN_COLUMNS - 1) * VIEWER_PATTERN_PITCH_X_MM / 2.0
pattern_start_y = pattern_center_y - (VIEWER_PATTERN_ROWS - 1) * VIEWER_PATTERN_PITCH_Y_MM / 2.0
for row_index in range(VIEWER_PATTERN_ROWS):
for column_index in range(VIEWER_PATTERN_COLUMNS):
hole_x = pattern_start_x + column_index * VIEWER_PATTERN_PITCH_X_MM
hole_y = pattern_start_y + row_index * VIEWER_PATTERN_PITCH_Y_MM
with Locations(Location((hole_x, hole_y, -1.0))):
Cylinder(
radius=VIEWER_PATTERN_HOLE_DIAMETER_MM / 2.0,
height=THICKNESS_MM + 2.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
countersink_x, countersink_y = VIEWER_COUNTERSINK_CENTER_MM
countersink_radius = VIEWER_COUNTERSINK_DIAMETER_MM / 2.0
countersink_through_radius = VIEWER_COUNTERSINK_HOLE_DIAMETER_MM / 2.0
countersink_depth = (
countersink_radius - countersink_through_radius
) / tan(radians(VIEWER_COUNTERSINK_ANGLE_DEG / 2.0))
with Locations(Location((countersink_x, countersink_y, -1.0))):
Cylinder(
radius=countersink_through_radius,
height=THICKNESS_MM + 2.0,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
with Locations(
Location((countersink_x, countersink_y, VIEWER_COUNTERSINK_FACE_Z_MM - countersink_depth))
):
Cone(
countersink_through_radius,
countersink_radius,
countersink_depth + 0.05,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
pocket_x, pocket_y = VIEWER_POCKET_CENTER_MM
with Locations(Location((pocket_x, pocket_y, VIEWER_POCKET_FACE_Z_MM - VIEWER_POCKET_DEPTH_MM))):
Box(
VIEWER_POCKET_WIDTH_MM,
VIEWER_POCKET_HEIGHT_MM,
VIEWER_POCKET_DEPTH_MM + 0.05,
align=(Align.CENTER, Align.CENTER, Align.MIN),
mode=Mode.SUBTRACT,
)
part = flange.part
part.label = "flange_8x_m3_clearance"
part.label = "flange_8x_m3_clearance_with_m4_tapped_hole_viewer_pattern_countersink_and_pocket"
return part
@@ -2,11 +2,67 @@
"step_valid": true,
"solid_count": 1,
"exact_size_mm": [50.0, 50.0, 4.0],
"topology_counts": {
"faces": 24,
"edges": 62
},
"outer_radius_mm": 25.0,
"inner_radius_mm": 10.0,
"m3_clearance_hole_radius_mm": 1.7,
"m3_clearance_hole_count": 8,
"bolt_circle_radius_mm": 20.0,
"angular_spacing_deg": [45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0, 45.0],
"visual_review": "passed_iso_opposite_top_front"
"viewer_edits": {
"hole_pattern": {
"operation": "add_hole_pattern",
"selection_point_model_mm": [8.077088362294017, 15.744119598589815, 4.0],
"hole_diameter_mm": 2.0,
"rows": 2,
"columns": 2,
"pitch_x_mm": 8.0,
"pitch_y_mm": 8.0,
"depth": "through",
"hole_centers_mm": [
[4.077088362294017, 11.744119598589815, 2.0],
[12.077088362294017, 11.744119598589815, 2.0],
[4.077088362294017, 19.744119598589815, 2.0],
[12.077088362294017, 19.744119598589815, 2.0]
],
"verified_hole_count": 4,
"verified_hole_diameter_mm": 2.0,
"verified_through_height_mm": 4.0,
"max_center_error_mm": 0.0
},
"countersink": {
"operation": "add_countersink",
"selection_point_model_mm": [-14.6034432889167, -4.110394299469192, 4.0],
"hole_diameter_mm": 3.0,
"countersink_diameter_mm": 6.0,
"countersink_angle_deg": 90.0,
"countersink_depth_mm": 1.5,
"depth": "through"
},
"pocket": {
"operation": "add_pocket",
"selection_point_model_mm": [6.197199513379157, -14.374353062530322, 4.0],
"shape": "rectangle",
"width_mm": 10.0,
"height_mm": 6.0,
"depth_mm": 1.0,
"bottom_z_mm": 3.0,
"verified_bottom_center_mm": [6.197199513379157, -14.374353062530322, 3.0],
"verified_bottom_size_mm": [10.0, 6.0],
"verified_bottom_face_count": 1
}
},
"viewer_mesh_topology": {
"path": ".flange_8x_m3.step.glb",
"regenerated": true
},
"snapshot_review": {
"status": "passed",
"path": "review/viewer_pocket_top_20260724T031341Z.png",
"camera": "top"
},
"visual_review": "passed_viewer_pocket_top"
}
@@ -1,6 +1,6 @@
{
"schema_version": "1.0",
"request": "生成一个28齿、厚度4毫米的齿轮",
"request": "生成一个28齿、厚度4毫米的齿轮,并在 viewer 选中的两个面点位置各添加一个2.0毫米贯穿孔;随后根据 viewer 选中的顶面点 f5 添加一个10x6x1毫米矩形口袋",
"route": {
"selected_backend": "simplecadapi",
"runner_skill": "simplecadapi",
@@ -31,6 +31,10 @@
{
"path": "gear_28t_4mm.stl",
"role": "secondary"
},
{
"path": ".gear_28t_4mm.step.glb",
"role": "viewer_mesh_topology"
}
],
"parameters": {
@@ -41,13 +45,65 @@
"bore_diameter_mm": 5.0,
"addendum_factor": 1.0,
"clearance_factor": 0.25,
"backlash_mm": 0.0
"backlash_mm": 0.0,
"edit_holes": [
{
"diameter_mm": 2.0,
"center_mm": [
-2.9189440262073063,
5.47686606164935,
4.0
]
},
{
"diameter_mm": 2.0,
"center_mm": [
7.5974463825483305,
-0.8433357012066764,
4.0
]
}
],
"edit_hole_count": 2,
"edit_hole_axis": [
0.0,
0.0,
1.0
],
"edit_pockets": [
{
"shape": "rectangle",
"width_mm": 10.0,
"height_mm": 6.0,
"depth_mm": 1.0,
"center_mm": [
-8.717129036056448,
-0.9462437095981322,
4.0
],
"direction": "face_normal",
"orientation": "surface_xy",
"selected_face": {
"reference_id": "f5",
"selector": "f5",
"face_index": 5428,
"row_index": 4,
"normal": [
0.0,
0.0,
1.0
]
}
}
],
"edit_pocket_count": 1
},
"assumptions": [
"未指定模数,V1采用1.0毫米。",
"未指定压力角,采用标准20度。",
"未指定中心孔,采用5.0毫米通孔。",
"未指定齿轮类型,采用直齿渐开线外齿轮。"
"未指定齿轮类型,采用直齿渐开线外齿轮。",
"viewer 编辑请求未指定孔径,采用2.0毫米贯穿孔作为可编辑默认值。"
],
"validation": [
{
@@ -62,12 +118,55 @@
4.0
],
"pitch_diameter_mm": 28.0,
"edit_holes": [
{
"diameter_mm": 2.0,
"center_mm": [
-2.9189440262073063,
5.47686606164935,
4.0
]
},
{
"diameter_mm": 2.0,
"center_mm": [
7.5974463825483305,
-0.8433357012066764,
4.0
]
}
],
"edit_hole_count": 2,
"edit_pockets": [
{
"shape": "rectangle",
"width_mm": 10.0,
"height_mm": 6.0,
"depth_mm": 1.0,
"center_mm": [
-8.717129036056448,
-0.9462437095981322,
4.0
]
}
],
"edit_pocket_count": 1,
"faces": 182,
"edges": 545,
"outside_diameter_mm": 30.0,
"visual_review": "passed_iso_top_front"
"volume_mm3": 2167.600632,
"viewer_mesh_topology": {
"path": ".gear_28t_4mm.step.glb",
"status": "regenerated",
"face_count": 182,
"edge_count": 545,
"step_hash": "d07ba4428ce372d7dc65ea31c2c8bf636399f38e40e2bd705029789832825267"
},
"visual_review": "not_run"
}
}
],
"viewer_links": [
"http://127.0.0.1:4178/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fgear_28t_4mm&file=gear_28t_4mm.step"
"http://127.0.0.1:5174/?dir=%2FUsers%2Flk%2FProjects%2FcadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fgear_28t_4mm&file=gear_28t_4mm.step"
]
}
File diff suppressed because one or more lines are too long
@@ -1,8 +1,14 @@
"""Parametric 28-tooth spur gear generated with SimpleCADAPI."""
import json
import sys
from pathlib import Path
_PROJECT_ROOT = Path(__file__).resolve().parents[4]
_SIMPLECADAPI_SRC = _PROJECT_ROOT / "SimpleCADAPI" / "src"
if str(_SIMPLECADAPI_SRC) not in sys.path:
sys.path.insert(0, str(_SIMPLECADAPI_SRC))
import simplecadapi as scad
@@ -14,6 +20,76 @@ BORE_DIAMETER_MM = 5.0
ADDENDUM_FACTOR = 1.0
CLEARANCE_FACTOR = 0.25
BACKLASH_MM = 0.0
VIEWER_SELECTED_EDGE = {
"reference_id": "e344",
"row_index": 343,
"description": "top circular edge of the center bore",
}
BORE_TOP_CHAMFER_DISTANCE_MM = 3.0
EDIT_HOLE_AXIS = (0.0, 0.0, 1.0)
EDIT_HOLES = [
{
"diameter_mm": 2.0,
"center_mm": (-2.9189440262073063, 5.47686606164935, THICKNESS_MM),
},
{
"diameter_mm": 2.0,
"center_mm": (7.5974463825483305, -0.8433357012066764, THICKNESS_MM),
},
]
EDIT_HOLE_PATTERNS = [
{
"diameter_mm": 2.0,
"rows": 2,
"columns": 2,
"pitch_x_mm": 8.0,
"pitch_y_mm": 8.0,
"depth": "through",
"direction": "face_normal",
"orientation": "surface_xy",
"center_mm": (0.24688766006678975, -8.367405492677905, THICKNESS_MM),
"selected_face": {
"reference_id": "f5",
"selector": "f5",
"face_index": 2785,
"row_index": 4,
"normal": (0.0, 0.0, 1.0),
},
},
]
EDIT_POCKETS = [
{
"shape": "rectangle",
"width_mm": 10.0,
"height_mm": 6.0,
"depth_mm": 1.0,
"center_mm": (-8.717129036056448, -0.9462437095981322, THICKNESS_MM),
"direction": "face_normal",
"orientation": "surface_xy",
"selected_face": {
"reference_id": "f5",
"selector": "f5",
"face_index": 5428,
"row_index": 4,
"normal": (0.0, 0.0, 1.0),
},
},
]
def hole_pattern_centers(pattern):
x_center, y_center, z_center = pattern["center_mm"]
x_start = x_center - (pattern["columns"] - 1) * pattern["pitch_x_mm"] / 2.0
y_start = y_center - (pattern["rows"] - 1) * pattern["pitch_y_mm"] / 2.0
return [
(
x_start + column * pattern["pitch_x_mm"],
y_start + row * pattern["pitch_y_mm"],
z_center,
)
for row in range(pattern["rows"])
for column in range(pattern["columns"])
]
def generate() -> None:
@@ -32,14 +108,75 @@ def generate() -> None:
blank_faces = scad.ql.select(items=gear_blank.get_faces()).all()
print("gear_blank_faces", len(blank_faces))
bore_radius = BORE_DIAMETER_MM / 2.0
bore = scad.make_cylinder_rsolid(
radius=BORE_DIAMETER_MM / 2.0,
radius=bore_radius,
height=THICKNESS_MM + 2.0,
bottom_face_center=(0.0, 0.0, -1.0),
axis=(0.0, 0.0, 1.0),
)
gear = scad.cut_rsolid(gear_blank, bore, skip_non_intersecting=False)
bore_top_chamfer = scad.make_cone_rsolid(
bottom_radius=bore_radius,
height=BORE_TOP_CHAMFER_DISTANCE_MM,
top_radius=bore_radius + BORE_TOP_CHAMFER_DISTANCE_MM,
bottom_face_center=(
0.0,
0.0,
THICKNESS_MM - BORE_TOP_CHAMFER_DISTANCE_MM,
),
axis=(0.0, 0.0, 1.0),
)
edit_hole_cutters = [
scad.make_cylinder_rsolid(
radius=hole["diameter_mm"] / 2.0,
height=THICKNESS_MM + 2.0,
bottom_face_center=(hole["center_mm"][0], hole["center_mm"][1], -1.0),
axis=EDIT_HOLE_AXIS,
)
for hole in EDIT_HOLES
]
pattern_hole_cutters = [
scad.make_cylinder_rsolid(
radius=pattern["diameter_mm"] / 2.0,
height=THICKNESS_MM + 2.0,
bottom_face_center=(center[0], center[1], -1.0),
axis=EDIT_HOLE_AXIS,
)
for pattern in EDIT_HOLE_PATTERNS
for center in hole_pattern_centers(pattern)
]
pocket_cutters = [
scad.make_box_rsolid(
width=pocket["width_mm"],
height=pocket["height_mm"],
depth=pocket["depth_mm"] + 0.02,
bottom_face_center=(
pocket["center_mm"][0],
pocket["center_mm"][1],
THICKNESS_MM - pocket["depth_mm"],
),
)
for pocket in EDIT_POCKETS
]
print("viewer_edit_holes", len(edit_hole_cutters))
print("viewer_edit_pattern_holes", len(pattern_hole_cutters))
print("viewer_edit_pockets", len(pocket_cutters))
gear = scad.cut_rsolid(
gear_blank,
[
bore,
bore_top_chamfer,
*edit_hole_cutters,
*pattern_hole_cutters,
*pocket_cutters,
],
skip_non_intersecting=False,
)
gear = scad.apply_tag(shape=gear, tag="role.power_transmission.gear")
gear = scad.apply_tag(shape=gear, tag="feature.viewer_edit_point_hole")
gear = scad.apply_tag(shape=gear, tag="feature.viewer_edit_hole_pattern")
gear = scad.apply_tag(shape=gear, tag="feature.viewer_edit_rectangular_pocket")
gear = scad.apply_tag(shape=gear, tag="feature.viewer_selected_edge_chamfer")
final_faces = scad.ql.select(items=gear.get_faces()).all()
final_edges = scad.ql.select(items=gear.get_edges()).all()
print("final_topology", {"faces": len(final_faces), "edges": len(final_edges)})
@@ -61,6 +198,61 @@ def generate() -> None:
"root_diameter_mm": root_diameter_mm,
"thickness_mm": THICKNESS_MM,
"bore_diameter_mm": BORE_DIAMETER_MM,
"viewer_selected_edge": VIEWER_SELECTED_EDGE,
"bore_top_chamfer_distance_mm": BORE_TOP_CHAMFER_DISTANCE_MM,
"bore_top_chamfer_top_diameter_mm": BORE_DIAMETER_MM
+ 2.0 * BORE_TOP_CHAMFER_DISTANCE_MM,
"edit_holes": [
{
"diameter_mm": hole["diameter_mm"],
"center_mm": list(hole["center_mm"]),
}
for hole in EDIT_HOLES
],
"edit_hole_count": len(EDIT_HOLES),
"edit_hole_axis": list(EDIT_HOLE_AXIS),
"edit_hole_patterns": [
{
"diameter_mm": pattern["diameter_mm"],
"rows": pattern["rows"],
"columns": pattern["columns"],
"pitch_x_mm": pattern["pitch_x_mm"],
"pitch_y_mm": pattern["pitch_y_mm"],
"depth": pattern["depth"],
"direction": pattern["direction"],
"orientation": pattern["orientation"],
"center_mm": list(pattern["center_mm"]),
"hole_centers_mm": [
list(center) for center in hole_pattern_centers(pattern)
],
"selected_face": {
**pattern["selected_face"],
"normal": list(pattern["selected_face"]["normal"]),
},
}
for pattern in EDIT_HOLE_PATTERNS
],
"edit_hole_pattern_count": len(EDIT_HOLE_PATTERNS),
"edit_hole_pattern_hole_count": sum(
pattern["rows"] * pattern["columns"] for pattern in EDIT_HOLE_PATTERNS
),
"edit_pockets": [
{
"shape": pocket["shape"],
"width_mm": pocket["width_mm"],
"height_mm": pocket["height_mm"],
"depth_mm": pocket["depth_mm"],
"center_mm": list(pocket["center_mm"]),
"direction": pocket["direction"],
"orientation": pocket["orientation"],
"selected_face": {
**pocket["selected_face"],
"normal": list(pocket["selected_face"]["normal"]),
},
}
for pocket in EDIT_POCKETS
],
"edit_pocket_count": len(EDIT_POCKETS),
"volume_mm3": round(gear.get_volume(), 6),
"faces": len(final_faces),
"edges": len(final_edges),
@@ -5,15 +5,129 @@
"root_diameter_mm": 25.5,
"thickness_mm": 4.0,
"bore_diameter_mm": 5.0,
"volume_mm3": 2343.649578,
"faces": 171,
"edges": 507,
"viewer_selected_edge": {
"reference_id": "e344",
"row_index": 343,
"description": "top circular edge of the center bore"
},
"bore_top_chamfer_distance_mm": 3.0,
"bore_top_chamfer_top_diameter_mm": 11.0,
"edit_holes": [
{
"diameter_mm": 2.0,
"center_mm": [
-2.9189440262073063,
5.47686606164935,
4.0
]
},
{
"diameter_mm": 2.0,
"center_mm": [
7.5974463825483305,
-0.8433357012066764,
4.0
]
}
],
"edit_hole_count": 2,
"edit_hole_axis": [
0.0,
0.0,
1.0
],
"edit_hole_patterns": [
{
"diameter_mm": 2.0,
"rows": 2,
"columns": 2,
"pitch_x_mm": 8.0,
"pitch_y_mm": 8.0,
"depth": "through",
"direction": "face_normal",
"orientation": "surface_xy",
"center_mm": [
0.24688766006678975,
-8.367405492677905,
4.0
],
"hole_centers_mm": [
[
-3.7531123399332103,
-12.367405492677905,
4.0
],
[
4.24688766006679,
-12.367405492677905,
4.0
],
[
-3.7531123399332103,
-4.367405492677905,
4.0
],
[
4.24688766006679,
-4.367405492677905,
4.0
]
],
"selected_face": {
"reference_id": "f5",
"selector": "f5",
"face_index": 2785,
"row_index": 4,
"normal": [
0.0,
0.0,
1.0
]
}
}
],
"edit_hole_pattern_count": 1,
"edit_hole_pattern_hole_count": 4,
"edit_pockets": [
{
"shape": "rectangle",
"width_mm": 10.0,
"height_mm": 6.0,
"depth_mm": 1.0,
"center_mm": [
-8.717129036056448,
-0.9462437095981322,
4.0
],
"direction": "face_normal",
"orientation": "surface_xy",
"selected_face": {
"reference_id": "f5",
"selector": "f5",
"face_index": 5428,
"row_index": 4,
"normal": [
0.0,
0.0,
1.0
]
}
}
],
"edit_pocket_count": 1,
"volume_mm3": 2128.275635,
"faces": 184,
"edges": 557,
"tags": [
"feature.viewer_edit_hole_pattern",
"feature.viewer_edit_point_hole",
"feature.viewer_edit_rectangular_pocket",
"feature.viewer_selected_edge_chamfer",
"role.power_transmission.gear",
"sketch.gear_28t_m1.id_0",
"sketch_profile.arc_root_0",
"solid.boolean.cut",
"solid.extrusion"
],
"graph_nodes": 351
"graph_nodes": 359
}
@@ -1,5 +1,9 @@
"""Approximate long cylindrical pin with a shallow axial end recess."""
from math import atan2, degrees, hypot, sqrt
from build123d import Box, Cylinder, Location
from shaft_common import cut_end_bore, export_horizontal, stepped_body
@@ -8,8 +12,115 @@ LENGTH = 120.0
RECESS_DIAMETER = 6.0
RECESS_DEPTH = 5.0
VIEWER_POCKET_CENTER = (50.72611906534796, 1.4055875192234604, 8.886497233823093)
VIEWER_POCKET_WIDTH = 2.0
VIEWER_POCKET_HEIGHT = 2.0
VIEWER_POCKET_DEPTH = 1.0
VIEWER_POCKET_OVERSHOOT = 0.2
CIRCULAR_POCKET_CENTER = (68.62703575804954, 0.06569140145571417, 8.989927422862593)
CIRCULAR_POCKET_NORMAL = (0.0, -0.156229, -0.987721)
CIRCULAR_POCKET_DIAMETER = 8.0
CIRCULAR_POCKET_DEPTH = 1.0
CIRCULAR_POCKET_OVERSHOOT = 0.2
COUNTERBORE_CENTER = (40.48928144002571, -7.833112445247494, 4.424155112072327)
COUNTERBORE_NORMAL = (0.000061, -0.039505, -0.999219)
COUNTERBORE_HOLE_DIAMETER = 3.0
COUNTERBORE_DIAMETER = 6.0
COUNTERBORE_DEPTH = 2.0
COUNTERBORE_FACE_SELECTOR = {
"reference_id": "f1",
"face_index": 164,
"row_index": 0,
}
COUNTERBORE_OVERSHOOT = 0.2
COUNTERBORE_THROUGH_OVERSHOOT = 2.0
def cut_viewer_rectangular_pocket(body):
x_pos, y_pos, z_pos = VIEWER_POCKET_CENTER
radial_length = hypot(y_pos, z_pos)
normal_y = y_pos / radial_length
normal_z = z_pos / radial_length
rotation_x = degrees(atan2(-normal_y, normal_z))
tool_depth = VIEWER_POCKET_DEPTH + VIEWER_POCKET_OVERSHOOT
tool_center_offset = (VIEWER_POCKET_OVERSHOOT - VIEWER_POCKET_DEPTH) / 2.0
tool_center = (
x_pos,
y_pos + normal_y * tool_center_offset,
z_pos + normal_z * tool_center_offset,
)
tool = Location(tool_center, (rotation_x, 0.0, 0.0)) * Box(
VIEWER_POCKET_WIDTH,
VIEWER_POCKET_HEIGHT,
tool_depth,
)
return body - tool
def cut_viewer_circular_pocket(body):
x_pos, y_pos, z_pos = CIRCULAR_POCKET_CENTER
normal_x, normal_y, normal_z = CIRCULAR_POCKET_NORMAL
normal_length = sqrt(normal_x**2 + normal_y**2 + normal_z**2)
normal_y /= normal_length
normal_z /= normal_length
rotation_x = degrees(atan2(-normal_y, normal_z))
tool_depth = CIRCULAR_POCKET_DEPTH + CIRCULAR_POCKET_OVERSHOOT
tool_center_offset = (CIRCULAR_POCKET_DEPTH - CIRCULAR_POCKET_OVERSHOOT) / 2.0
tool_center = (
x_pos,
y_pos + normal_y * tool_center_offset,
z_pos + normal_z * tool_center_offset,
)
tool = Location(tool_center, (rotation_x, 0.0, 0.0)) * Cylinder(
CIRCULAR_POCKET_DIAMETER / 2.0,
tool_depth,
)
return body - tool
def cut_viewer_counterbore(body):
x_pos, y_pos, z_pos = COUNTERBORE_CENTER
normal_x, normal_y, normal_z = COUNTERBORE_NORMAL
normal_length = sqrt(normal_x**2 + normal_y**2 + normal_z**2)
normal_y /= normal_length
normal_z /= normal_length
rotation_x = degrees(atan2(-normal_y, normal_z))
through_depth = OUTER_DIAMETER + 4.0
through_tool_depth = through_depth + COUNTERBORE_THROUGH_OVERSHOOT
through_center_offset = (through_depth - COUNTERBORE_THROUGH_OVERSHOOT) / 2.0
through_center = (
x_pos,
y_pos + normal_y * through_center_offset,
z_pos + normal_z * through_center_offset,
)
through_tool = Location(through_center, (rotation_x, 0.0, 0.0)) * Cylinder(
COUNTERBORE_HOLE_DIAMETER / 2.0,
through_tool_depth,
)
counterbore_tool_depth = COUNTERBORE_DEPTH + COUNTERBORE_OVERSHOOT
counterbore_center_offset = (COUNTERBORE_DEPTH - COUNTERBORE_OVERSHOOT) / 2.0
counterbore_center = (
x_pos,
y_pos + normal_y * counterbore_center_offset,
z_pos + normal_z * counterbore_center_offset,
)
counterbore_tool = Location(counterbore_center, (rotation_x, 0.0, 0.0)) * Cylinder(
COUNTERBORE_DIAMETER / 2.0,
counterbore_tool_depth,
)
return body - through_tool - counterbore_tool
def gen_step():
body, total = stepped_body([(OUTER_DIAMETER, LENGTH)])
body = cut_end_bore(body, total, RECESS_DIAMETER, RECESS_DEPTH)
return export_horizontal(body, "pin_004910_like")
result = export_horizontal(body, "pin_004910_like")
result = cut_viewer_rectangular_pocket(result)
result = cut_viewer_circular_pocket(result)
result = cut_viewer_counterbore(result)
result.label = "pin_004910_like"
return result
@@ -4,7 +4,9 @@ The image does not define a thread standard. Rings represent the visible
thread rhythm while keeping the concept model lightweight and editable.
"""
from build123d import BuildPart, BuildSketch, Cylinder, Mode, Plane, Pos, RegularPolygon, extrude
from math import atan2, degrees
from build123d import Align, Axis, BuildPart, BuildSketch, Cylinder, Mode, Plane, Pos, RegularPolygon, extrude
from shaft_common import add_thread_rings, cut_end_bore, export_horizontal, stepped_body
@@ -17,10 +19,36 @@ def _hex_prism(z_start, across_corners, length):
return prism.part
def _radial_through_hole(body, axial_position, surface_y, surface_z, diameter, stock_diameter):
# The source is authored along +Z and export_horizontal rotates it onto +X.
source_x = -surface_z
source_y = surface_y
angle = degrees(atan2(source_y, source_x))
tool = (
Pos(0, 0, axial_position)
* Cylinder(
diameter / 2.0,
stock_diameter + 4.0,
align=(Align.CENTER, Align.CENTER, Align.CENTER),
)
.rotate(Axis.Y, 90)
.rotate(Axis.Z, angle)
)
return body - tool
def gen_step():
# Minor thread stem, neck, roller, and short nut-supporting neck.
body, total = stepped_body([(8.2, 40.0), (9.5, 7.0), (28.0, 25.0), (10.0, 4.0)])
body = add_thread_rings(body, z_start=0.5, length=38.5, major_diameter=10.0, pitch=2.0)
body = _radial_through_hole(
body,
axial_position=59.77433249254057,
surface_y=0.8860768186618869,
surface_z=13.963760796195984,
diameter=4.0,
stock_diameter=28.0,
)
hex_body = _hex_prism(total - 0.15, across_corners=22.0, length=11.0)
body = body + hex_body
total += 10.85