claude-cad 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_cad/__init__.py +5 -0
- claude_cad/mock_server.py +543 -0
- claude_cad/model_generator.py +471 -0
- claude_cad/server.py +505 -0
- claude_cad/utils.py +86 -0
- claude_cad-0.1.0.dist-info/METADATA +147 -0
- claude_cad-0.1.0.dist-info/RECORD +11 -0
- claude_cad-0.1.0.dist-info/WHEEL +5 -0
- claude_cad-0.1.0.dist-info/entry_points.txt +2 -0
- claude_cad-0.1.0.dist-info/licenses/LICENSE +201 -0
- claude_cad-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,471 @@
|
|
1
|
+
"""
|
2
|
+
Model generator functions for Claude CAD.
|
3
|
+
|
4
|
+
This module contains functions to generate CadQuery models from various inputs
|
5
|
+
such as primitive parameters, text descriptions, and custom code.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import re
|
9
|
+
from typing import Dict, Any, Tuple, Union, List, Optional
|
10
|
+
|
11
|
+
import cadquery as cq
|
12
|
+
|
13
|
+
from . import utils
|
14
|
+
|
15
|
+
|
16
|
+
def create_primitive(shape_type: str, parameters: Dict[str, Any]) -> cq.Workplane:
|
17
|
+
"""Create a primitive 3D shape based on the given parameters.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
shape_type: The type of shape to create (box, sphere, cylinder, cone).
|
21
|
+
parameters: A dictionary of parameters for the shape.
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
A CadQuery Workplane object representing the shape.
|
25
|
+
|
26
|
+
Raises:
|
27
|
+
ValueError: If the shape type is invalid or required parameters are missing.
|
28
|
+
"""
|
29
|
+
# Start with a base workplane
|
30
|
+
result = cq.Workplane("XY")
|
31
|
+
|
32
|
+
if shape_type == "box":
|
33
|
+
# Required parameters for a box
|
34
|
+
length = float(parameters.get("length", 10.0))
|
35
|
+
width = float(parameters.get("width", 10.0))
|
36
|
+
height = float(parameters.get("height", 10.0))
|
37
|
+
centered = bool(parameters.get("centered", True))
|
38
|
+
|
39
|
+
# Create the box
|
40
|
+
result = result.box(length, width, height, centered=centered)
|
41
|
+
|
42
|
+
elif shape_type == "sphere":
|
43
|
+
# Required parameters for a sphere
|
44
|
+
radius = float(parameters.get("radius", 5.0))
|
45
|
+
|
46
|
+
# Create the sphere
|
47
|
+
result = result.sphere(radius)
|
48
|
+
|
49
|
+
elif shape_type == "cylinder":
|
50
|
+
# Required parameters for a cylinder
|
51
|
+
radius = float(parameters.get("radius", 5.0))
|
52
|
+
height = float(parameters.get("height", 10.0))
|
53
|
+
centered = bool(parameters.get("centered", True))
|
54
|
+
|
55
|
+
# Create the cylinder
|
56
|
+
result = result.cylinder(height, radius, centered=centered)
|
57
|
+
|
58
|
+
elif shape_type == "cone":
|
59
|
+
# Required parameters for a cone
|
60
|
+
radius1 = float(parameters.get("radius1", 5.0))
|
61
|
+
radius2 = float(parameters.get("radius2", 0.0))
|
62
|
+
height = float(parameters.get("height", 10.0))
|
63
|
+
centered = bool(parameters.get("centered", True))
|
64
|
+
|
65
|
+
# Create the cone
|
66
|
+
result = result.cone(height, radius1, radius2, centered=centered)
|
67
|
+
|
68
|
+
else:
|
69
|
+
raise ValueError(f"Invalid shape type: {shape_type}")
|
70
|
+
|
71
|
+
return result
|
72
|
+
|
73
|
+
|
74
|
+
def create_from_text(description: str) -> Tuple[cq.Workplane, str]:
|
75
|
+
"""Create a 3D model from a text description.
|
76
|
+
|
77
|
+
This function interprets a natural language description and generates
|
78
|
+
a corresponding CadQuery model.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
description: A natural language description of the 3D model to create.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
A tuple containing:
|
85
|
+
- The generated CadQuery Workplane object
|
86
|
+
- The Python code used to generate the model
|
87
|
+
"""
|
88
|
+
# Parse the description to extract model type and parameters
|
89
|
+
model_type, parameters = _parse_description(description)
|
90
|
+
|
91
|
+
# Generate CadQuery code based on the parsed description
|
92
|
+
code = _generate_cadquery_code(model_type, parameters, description)
|
93
|
+
|
94
|
+
# Execute the generated code to create the model
|
95
|
+
model = execute_script(code)
|
96
|
+
|
97
|
+
return model, code
|
98
|
+
|
99
|
+
|
100
|
+
def execute_script(code: str) -> cq.Workplane:
|
101
|
+
"""Execute CadQuery Python code to create a 3D model.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
code: Python code using CadQuery to create a model.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
A CadQuery Workplane object representing the model.
|
108
|
+
|
109
|
+
Raises:
|
110
|
+
Exception: If there's an error executing the code or if it doesn't produce a valid model.
|
111
|
+
"""
|
112
|
+
# Define a namespace for execution
|
113
|
+
namespace = {
|
114
|
+
"cq": cq,
|
115
|
+
"cadquery": cq,
|
116
|
+
"result": None
|
117
|
+
}
|
118
|
+
|
119
|
+
# Execute the code
|
120
|
+
try:
|
121
|
+
exec(code, namespace)
|
122
|
+
except Exception as e:
|
123
|
+
raise Exception(f"Error executing CadQuery code: {str(e)}")
|
124
|
+
|
125
|
+
# Retrieve the result variable
|
126
|
+
result = namespace.get("result")
|
127
|
+
|
128
|
+
# If no result variable, try to find a variable that looks like a CadQuery object
|
129
|
+
if result is None:
|
130
|
+
for name, value in namespace.items():
|
131
|
+
if isinstance(value, cq.Workplane):
|
132
|
+
result = value
|
133
|
+
break
|
134
|
+
|
135
|
+
# Ensure we have a valid model
|
136
|
+
if not isinstance(result, cq.Workplane):
|
137
|
+
raise Exception("CadQuery code did not produce a valid model. Make sure to assign your model to a variable named 'result'.")
|
138
|
+
|
139
|
+
return result
|
140
|
+
|
141
|
+
|
142
|
+
def _parse_description(description: str) -> Tuple[str, Dict[str, Any]]:
|
143
|
+
"""Parse a natural language description to extract model type and parameters.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
description: A natural language description of the 3D model.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
A tuple containing the model type and a dictionary of parameters.
|
150
|
+
"""
|
151
|
+
# Default to a simple box if we can't parse anything specific
|
152
|
+
model_type = "custom"
|
153
|
+
parameters = {}
|
154
|
+
|
155
|
+
# Look for common shape types
|
156
|
+
if re.search(r'\b(box|cube|block|rectangular)\b', description, re.IGNORECASE):
|
157
|
+
model_type = "box"
|
158
|
+
|
159
|
+
# Try to extract dimensions
|
160
|
+
length_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:length|long)', description, re.IGNORECASE)
|
161
|
+
width_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:width|wide)', description, re.IGNORECASE)
|
162
|
+
height_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:height|tall|high)', description, re.IGNORECASE)
|
163
|
+
|
164
|
+
if length_match:
|
165
|
+
parameters["length"] = float(length_match.group(1))
|
166
|
+
if width_match:
|
167
|
+
parameters["width"] = float(width_match.group(1))
|
168
|
+
if height_match:
|
169
|
+
parameters["height"] = float(height_match.group(1))
|
170
|
+
|
171
|
+
elif re.search(r'\b(sphere|ball|round)\b', description, re.IGNORECASE):
|
172
|
+
model_type = "sphere"
|
173
|
+
|
174
|
+
# Try to extract radius
|
175
|
+
radius_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:radius)', description, re.IGNORECASE)
|
176
|
+
diameter_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:diameter)', description, re.IGNORECASE)
|
177
|
+
|
178
|
+
if radius_match:
|
179
|
+
parameters["radius"] = float(radius_match.group(1))
|
180
|
+
elif diameter_match:
|
181
|
+
parameters["radius"] = float(diameter_match.group(1)) / 2.0
|
182
|
+
|
183
|
+
elif re.search(r'\b(cylinder|tube|pipe)\b', description, re.IGNORECASE):
|
184
|
+
model_type = "cylinder"
|
185
|
+
|
186
|
+
# Try to extract dimensions
|
187
|
+
radius_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:radius)', description, re.IGNORECASE)
|
188
|
+
diameter_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:diameter)', description, re.IGNORECASE)
|
189
|
+
height_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:height|tall|high)', description, re.IGNORECASE)
|
190
|
+
|
191
|
+
if radius_match:
|
192
|
+
parameters["radius"] = float(radius_match.group(1))
|
193
|
+
elif diameter_match:
|
194
|
+
parameters["radius"] = float(diameter_match.group(1)) / 2.0
|
195
|
+
|
196
|
+
if height_match:
|
197
|
+
parameters["height"] = float(height_match.group(1))
|
198
|
+
|
199
|
+
elif re.search(r'\b(cone|conical|pyramid)\b', description, re.IGNORECASE):
|
200
|
+
model_type = "cone"
|
201
|
+
|
202
|
+
# Try to extract dimensions
|
203
|
+
radius1_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:base radius|bottom radius)', description, re.IGNORECASE)
|
204
|
+
radius2_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:top radius)', description, re.IGNORECASE)
|
205
|
+
height_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:height|tall|high)', description, re.IGNORECASE)
|
206
|
+
|
207
|
+
if radius1_match:
|
208
|
+
parameters["radius1"] = float(radius1_match.group(1))
|
209
|
+
|
210
|
+
if radius2_match:
|
211
|
+
parameters["radius2"] = float(radius2_match.group(1))
|
212
|
+
elif "cone" in description.lower():
|
213
|
+
# Default to a pointed cone if not specified
|
214
|
+
parameters["radius2"] = 0.0
|
215
|
+
|
216
|
+
if height_match:
|
217
|
+
parameters["height"] = float(height_match.group(1))
|
218
|
+
|
219
|
+
# If it's a gear or complex shape, use a custom model
|
220
|
+
elif re.search(r'\b(gear|cog|wheel|teeth)\b', description, re.IGNORECASE):
|
221
|
+
model_type = "gear"
|
222
|
+
|
223
|
+
# Extract gear parameters
|
224
|
+
num_teeth_match = re.search(r'(\d+)\s*(?:teeth|tooth)', description, re.IGNORECASE)
|
225
|
+
pitch_diameter_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:pitch diameter|diameter)', description, re.IGNORECASE)
|
226
|
+
pressure_angle_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:degree|deg)?\s*(?:pressure angle)', description, re.IGNORECASE)
|
227
|
+
|
228
|
+
if num_teeth_match:
|
229
|
+
parameters["num_teeth"] = int(num_teeth_match.group(1))
|
230
|
+
else:
|
231
|
+
parameters["num_teeth"] = 20 # Default
|
232
|
+
|
233
|
+
if pitch_diameter_match:
|
234
|
+
parameters["pitch_diameter"] = float(pitch_diameter_match.group(1))
|
235
|
+
else:
|
236
|
+
parameters["pitch_diameter"] = 50.0 # Default
|
237
|
+
|
238
|
+
if pressure_angle_match:
|
239
|
+
parameters["pressure_angle"] = float(pressure_angle_match.group(1))
|
240
|
+
else:
|
241
|
+
parameters["pressure_angle"] = 20.0 # Default
|
242
|
+
|
243
|
+
elif re.search(r'\b(screw|bolt|thread|nut)\b', description, re.IGNORECASE):
|
244
|
+
model_type = "screw"
|
245
|
+
|
246
|
+
# Extract thread parameters
|
247
|
+
diameter_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:diameter)', description, re.IGNORECASE)
|
248
|
+
length_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:length|long)', description, re.IGNORECASE)
|
249
|
+
|
250
|
+
if diameter_match:
|
251
|
+
parameters["diameter"] = float(diameter_match.group(1))
|
252
|
+
else:
|
253
|
+
parameters["diameter"] = 5.0 # Default
|
254
|
+
|
255
|
+
if length_match:
|
256
|
+
parameters["length"] = float(length_match.group(1))
|
257
|
+
else:
|
258
|
+
parameters["length"] = 20.0 # Default
|
259
|
+
|
260
|
+
# Extract common operations
|
261
|
+
if re.search(r'\b(holes?|bore|drill)\b', description, re.IGNORECASE):
|
262
|
+
parameters["has_holes"] = True
|
263
|
+
|
264
|
+
# Try to extract hole dimensions
|
265
|
+
hole_diameter_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:diameter|dia)\s*(?:hole|bore)', description, re.IGNORECASE)
|
266
|
+
if hole_diameter_match:
|
267
|
+
parameters["hole_diameter"] = float(hole_diameter_match.group(1))
|
268
|
+
else:
|
269
|
+
parameters["hole_diameter"] = 5.0 # Default
|
270
|
+
|
271
|
+
if re.search(r'\b(fillet|round|radius)\b', description, re.IGNORECASE):
|
272
|
+
parameters["has_fillets"] = True
|
273
|
+
|
274
|
+
# Try to extract fillet radius
|
275
|
+
fillet_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:fillet|round|radius)', description, re.IGNORECASE)
|
276
|
+
if fillet_match:
|
277
|
+
parameters["fillet_radius"] = float(fillet_match.group(1))
|
278
|
+
else:
|
279
|
+
parameters["fillet_radius"] = 1.0 # Default
|
280
|
+
|
281
|
+
if re.search(r'\b(chamfer|bevel)\b', description, re.IGNORECASE):
|
282
|
+
parameters["has_chamfers"] = True
|
283
|
+
|
284
|
+
# Try to extract chamfer distance
|
285
|
+
chamfer_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:chamfer|bevel)', description, re.IGNORECASE)
|
286
|
+
if chamfer_match:
|
287
|
+
parameters["chamfer_distance"] = float(chamfer_match.group(1))
|
288
|
+
else:
|
289
|
+
parameters["chamfer_distance"] = 1.0 # Default
|
290
|
+
|
291
|
+
if re.search(r'\b(shell|hollow|thin)\b', description, re.IGNORECASE):
|
292
|
+
parameters["is_shelled"] = True
|
293
|
+
|
294
|
+
# Try to extract shell thickness
|
295
|
+
shell_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m)?\s*(?:shell|wall|thick)', description, re.IGNORECASE)
|
296
|
+
if shell_match:
|
297
|
+
parameters["shell_thickness"] = float(shell_match.group(1))
|
298
|
+
else:
|
299
|
+
parameters["shell_thickness"] = 1.0 # Default
|
300
|
+
|
301
|
+
return model_type, parameters
|
302
|
+
|
303
|
+
|
304
|
+
def _generate_cadquery_code(model_type: str, parameters: Dict[str, Any], description: str) -> str:
|
305
|
+
"""Generate CadQuery Python code based on the model type and parameters.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
model_type: The type of model to generate.
|
309
|
+
parameters: A dictionary of parameters for the model.
|
310
|
+
description: The original text description.
|
311
|
+
|
312
|
+
Returns:
|
313
|
+
Python code that uses CadQuery to create the model.
|
314
|
+
"""
|
315
|
+
code_lines = [
|
316
|
+
"import cadquery as cq",
|
317
|
+
"",
|
318
|
+
f"# Model generated from description: {description}",
|
319
|
+
""
|
320
|
+
]
|
321
|
+
|
322
|
+
if model_type == "box":
|
323
|
+
length = parameters.get("length", 10.0)
|
324
|
+
width = parameters.get("width", 10.0)
|
325
|
+
height = parameters.get("height", 10.0)
|
326
|
+
|
327
|
+
code_lines.extend([
|
328
|
+
f"# Create a box with dimensions: {length} x {width} x {height}",
|
329
|
+
"result = cq.Workplane(\"XY\")",
|
330
|
+
f"result = result.box({length}, {width}, {height}, centered=True)"
|
331
|
+
])
|
332
|
+
|
333
|
+
elif model_type == "sphere":
|
334
|
+
radius = parameters.get("radius", 5.0)
|
335
|
+
|
336
|
+
code_lines.extend([
|
337
|
+
f"# Create a sphere with radius: {radius}",
|
338
|
+
"result = cq.Workplane(\"XY\")",
|
339
|
+
f"result = result.sphere({radius})"
|
340
|
+
])
|
341
|
+
|
342
|
+
elif model_type == "cylinder":
|
343
|
+
radius = parameters.get("radius", 5.0)
|
344
|
+
height = parameters.get("height", 10.0)
|
345
|
+
|
346
|
+
code_lines.extend([
|
347
|
+
f"# Create a cylinder with radius: {radius} and height: {height}",
|
348
|
+
"result = cq.Workplane(\"XY\")",
|
349
|
+
f"result = result.cylinder({height}, {radius}, centered=True)"
|
350
|
+
])
|
351
|
+
|
352
|
+
elif model_type == "cone":
|
353
|
+
radius1 = parameters.get("radius1", 5.0)
|
354
|
+
radius2 = parameters.get("radius2", 0.0)
|
355
|
+
height = parameters.get("height", 10.0)
|
356
|
+
|
357
|
+
code_lines.extend([
|
358
|
+
f"# Create a cone with base radius: {radius1}, top radius: {radius2}, and height: {height}",
|
359
|
+
"result = cq.Workplane(\"XY\")",
|
360
|
+
f"result = result.cone({height}, {radius1}, {radius2}, centered=True)"
|
361
|
+
])
|
362
|
+
|
363
|
+
elif model_type == "gear":
|
364
|
+
num_teeth = parameters.get("num_teeth", 20)
|
365
|
+
pitch_diameter = parameters.get("pitch_diameter", 50.0)
|
366
|
+
pressure_angle = parameters.get("pressure_angle", 20.0)
|
367
|
+
thickness = parameters.get("height", 5.0)
|
368
|
+
|
369
|
+
code_lines.extend([
|
370
|
+
f"# Create a gear with {num_teeth} teeth, pitch diameter: {pitch_diameter}, and pressure angle: {pressure_angle}",
|
371
|
+
"import math",
|
372
|
+
"",
|
373
|
+
f"num_teeth = {num_teeth}",
|
374
|
+
f"pitch_diameter = {pitch_diameter}",
|
375
|
+
f"pressure_angle = {pressure_angle}",
|
376
|
+
f"thickness = {thickness}",
|
377
|
+
"",
|
378
|
+
"# Calculate gear parameters",
|
379
|
+
"module_val = pitch_diameter / num_teeth",
|
380
|
+
"addendum = module_val",
|
381
|
+
"dedendum = 1.25 * module_val",
|
382
|
+
"outer_diameter = pitch_diameter + 2 * addendum",
|
383
|
+
"root_diameter = pitch_diameter - 2 * dedendum",
|
384
|
+
"base_diameter = pitch_diameter * math.cos(math.radians(pressure_angle))",
|
385
|
+
"tooth_angle = 360 / num_teeth",
|
386
|
+
"",
|
387
|
+
"# Create the gear",
|
388
|
+
"result = cq.Workplane(\"XY\")",
|
389
|
+
"",
|
390
|
+
"# Create the base cylinder",
|
391
|
+
f"result = result.circle(pitch_diameter / 2).extrude({thickness})",
|
392
|
+
"",
|
393
|
+
"# Add hub/mounting hole (optional)",
|
394
|
+
"hub_diameter = pitch_diameter * 0.3",
|
395
|
+
"result = result.faces(\">Z\").workplane().circle(hub_diameter / 2).extrude(thickness * 0.5)",
|
396
|
+
"",
|
397
|
+
"# Create a mounting hole",
|
398
|
+
"hole_diameter = pitch_diameter * 0.2",
|
399
|
+
"result = result.faces(\">Z\").workplane().circle(hole_diameter / 2).cutThruAll()"
|
400
|
+
])
|
401
|
+
|
402
|
+
elif model_type == "screw":
|
403
|
+
diameter = parameters.get("diameter", 5.0)
|
404
|
+
length = parameters.get("length", 20.0)
|
405
|
+
head_diameter = diameter * 1.8
|
406
|
+
head_height = diameter * 0.6
|
407
|
+
|
408
|
+
code_lines.extend([
|
409
|
+
f"# Create a screw with diameter: {diameter} and length: {length}",
|
410
|
+
f"shaft_diameter = {diameter}",
|
411
|
+
f"shaft_length = {length}",
|
412
|
+
f"head_diameter = {head_diameter}",
|
413
|
+
f"head_height = {head_height}",
|
414
|
+
"",
|
415
|
+
"# Create the shaft",
|
416
|
+
"result = cq.Workplane(\"XY\")",
|
417
|
+
"result = result.circle(shaft_diameter / 2).extrude(shaft_length)",
|
418
|
+
"",
|
419
|
+
"# Create the head",
|
420
|
+
"result = result.faces(\">Z\").workplane().circle(head_diameter / 2).extrude(head_height)",
|
421
|
+
"",
|
422
|
+
"# Add a slot to the head",
|
423
|
+
"slot_width = head_diameter * 0.2",
|
424
|
+
"slot_depth = head_height * 0.5",
|
425
|
+
"result = result.faces(\">Z\").workplane()",
|
426
|
+
"result = result.slot2D(slot_width, head_diameter - 1, 0).cutBlind(-slot_depth)"
|
427
|
+
])
|
428
|
+
|
429
|
+
else:
|
430
|
+
# Default to a simple box if model type is not recognized
|
431
|
+
code_lines.extend([
|
432
|
+
"# Create a default box",
|
433
|
+
"result = cq.Workplane(\"XY\")",
|
434
|
+
"result = result.box(10, 10, 10, centered=True)"
|
435
|
+
])
|
436
|
+
|
437
|
+
# Add optional features based on parameters
|
438
|
+
if parameters.get("has_holes", False):
|
439
|
+
hole_diameter = parameters.get("hole_diameter", 5.0)
|
440
|
+
code_lines.extend([
|
441
|
+
"",
|
442
|
+
f"# Add a through hole with diameter: {hole_diameter}",
|
443
|
+
f"result = result.faces(\">Z\").workplane().circle({hole_diameter / 2}).cutThruAll()"
|
444
|
+
])
|
445
|
+
|
446
|
+
if parameters.get("has_fillets", False):
|
447
|
+
fillet_radius = parameters.get("fillet_radius", 1.0)
|
448
|
+
code_lines.extend([
|
449
|
+
"",
|
450
|
+
f"# Add fillets with radius: {fillet_radius}",
|
451
|
+
f"result = result.edges().fillet({fillet_radius})"
|
452
|
+
])
|
453
|
+
|
454
|
+
if parameters.get("has_chamfers", False):
|
455
|
+
chamfer_distance = parameters.get("chamfer_distance", 1.0)
|
456
|
+
code_lines.extend([
|
457
|
+
"",
|
458
|
+
f"# Add chamfers with distance: {chamfer_distance}",
|
459
|
+
f"result = result.edges().chamfer({chamfer_distance})"
|
460
|
+
])
|
461
|
+
|
462
|
+
if parameters.get("is_shelled", False):
|
463
|
+
shell_thickness = parameters.get("shell_thickness", 1.0)
|
464
|
+
code_lines.extend([
|
465
|
+
"",
|
466
|
+
f"# Create a shell with thickness: {shell_thickness}",
|
467
|
+
f"result = result.shell(-{shell_thickness})"
|
468
|
+
])
|
469
|
+
|
470
|
+
# Return the generated code
|
471
|
+
return "\n".join(code_lines)
|