cyclecad 0.2.2 → 0.2.3
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.
- package/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +168 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/test-agent.html +1801 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
cycleCAD API Client Example
|
|
4
|
+
|
|
5
|
+
Simple Python client for the cycleCAD REST API.
|
|
6
|
+
Demonstrates how to create a simple part using the API.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 api-client-example.py
|
|
10
|
+
python3 api-client-example.py --host 0.0.0.0 --port 3000 --api-key YOUR_KEY
|
|
11
|
+
|
|
12
|
+
Requirements:
|
|
13
|
+
pip install requests
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
import json
|
|
18
|
+
import argparse
|
|
19
|
+
from typing import Dict, Any, List, Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CycleCADClient:
|
|
23
|
+
"""Python client for cycleCAD API Server"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, host: str = 'localhost', port: int = 3000, api_key: Optional[str] = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the client.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
host: Server hostname (default: localhost)
|
|
31
|
+
port: Server port (default: 3000)
|
|
32
|
+
api_key: Optional API key for authentication
|
|
33
|
+
"""
|
|
34
|
+
self.base_url = f'http://{host}:{port}'
|
|
35
|
+
self.api_key = api_key
|
|
36
|
+
self.session = requests.Session()
|
|
37
|
+
self.session.headers.update({
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
})
|
|
40
|
+
if api_key:
|
|
41
|
+
self.session.headers.update({'X-API-Key': api_key})
|
|
42
|
+
|
|
43
|
+
def _request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
|
|
44
|
+
"""Make HTTP request to API."""
|
|
45
|
+
url = f'{self.base_url}{endpoint}'
|
|
46
|
+
|
|
47
|
+
if method == 'GET':
|
|
48
|
+
response = self.session.get(url)
|
|
49
|
+
elif method == 'POST':
|
|
50
|
+
response = self.session.post(url, json=data)
|
|
51
|
+
elif method == 'DELETE':
|
|
52
|
+
response = self.session.delete(url)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f'Unsupported method: {method}')
|
|
55
|
+
|
|
56
|
+
if response.status_code >= 400:
|
|
57
|
+
try:
|
|
58
|
+
error = response.json()
|
|
59
|
+
raise Exception(f'API Error ({response.status_code}): {error.get("error", response.text)}')
|
|
60
|
+
except:
|
|
61
|
+
raise Exception(f'API Error ({response.status_code}): {response.text}')
|
|
62
|
+
|
|
63
|
+
return response.json()
|
|
64
|
+
|
|
65
|
+
def execute(self, method: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
66
|
+
"""Execute a single API command."""
|
|
67
|
+
return self._request('POST', '/api/execute', {
|
|
68
|
+
'method': method,
|
|
69
|
+
'params': params or {}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
def batch(self, commands: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
73
|
+
"""Execute multiple commands in a batch."""
|
|
74
|
+
return self._request('POST', '/api/batch', {
|
|
75
|
+
'commands': commands
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
def get_schema(self) -> Dict[str, Any]:
|
|
79
|
+
"""Get API schema."""
|
|
80
|
+
return self._request('GET', '/api/schema')
|
|
81
|
+
|
|
82
|
+
def get_health(self) -> Dict[str, Any]:
|
|
83
|
+
"""Check server health."""
|
|
84
|
+
return self._request('GET', '/api/health')
|
|
85
|
+
|
|
86
|
+
def get_history(self, count: int = 20) -> Dict[str, Any]:
|
|
87
|
+
"""Get command history."""
|
|
88
|
+
return self._request('GET', f'/api/history?count={count}')
|
|
89
|
+
|
|
90
|
+
def get_models(self) -> Dict[str, Any]:
|
|
91
|
+
"""List all models."""
|
|
92
|
+
return self._request('GET', '/api/models')
|
|
93
|
+
|
|
94
|
+
def get_model(self, model_id: str) -> Dict[str, Any]:
|
|
95
|
+
"""Get specific model."""
|
|
96
|
+
return self._request('GET', f'/api/models/{model_id}')
|
|
97
|
+
|
|
98
|
+
def delete_model(self, model_id: str) -> Dict[str, Any]:
|
|
99
|
+
"""Delete a model."""
|
|
100
|
+
return self._request('DELETE', f'/api/models/{model_id}')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def example_simple_part(client: CycleCADClient):
|
|
104
|
+
"""Example: Create a simple cylindrical part."""
|
|
105
|
+
print("\n" + "="*60)
|
|
106
|
+
print("EXAMPLE 1: Simple Cylindrical Part")
|
|
107
|
+
print("="*60 + "\n")
|
|
108
|
+
|
|
109
|
+
# Start sketch
|
|
110
|
+
print("1. Starting sketch on XY plane...")
|
|
111
|
+
r = client.execute('sketch.start', {'plane': 'XY'})
|
|
112
|
+
print(f" ✓ {r['result']['message']}")
|
|
113
|
+
|
|
114
|
+
# Draw circle
|
|
115
|
+
print("2. Drawing circle (radius 25mm)...")
|
|
116
|
+
r = client.execute('sketch.circle', {
|
|
117
|
+
'cx': 0,
|
|
118
|
+
'cy': 0,
|
|
119
|
+
'radius': 25
|
|
120
|
+
})
|
|
121
|
+
print(f" ✓ Circle created: {r['result']['entityId']}")
|
|
122
|
+
print(f" Area: {r['result']['area']:.2f} mm²")
|
|
123
|
+
|
|
124
|
+
# End sketch and extrude
|
|
125
|
+
print("3. Ending sketch...")
|
|
126
|
+
r = client.execute('sketch.end', {})
|
|
127
|
+
print(f" ✓ {r['result']['message']}")
|
|
128
|
+
|
|
129
|
+
print("4. Extruding to 50mm height...")
|
|
130
|
+
r = client.execute('ops.extrude', {
|
|
131
|
+
'height': 50,
|
|
132
|
+
'symmetric': False,
|
|
133
|
+
'material': 'steel'
|
|
134
|
+
})
|
|
135
|
+
print(f" ✓ Extrusion created: {r['result']['featureId']}")
|
|
136
|
+
print(f" Volume: {r['result']['volume']} mm³")
|
|
137
|
+
print(f" Material: {r['result']['material']}")
|
|
138
|
+
|
|
139
|
+
return r['result']['featureId']
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def example_batch_operations(client: CycleCADClient):
|
|
143
|
+
"""Example: Create a rectangular part using batch commands."""
|
|
144
|
+
print("\n" + "="*60)
|
|
145
|
+
print("EXAMPLE 2: Batch Operations - Rectangular Part")
|
|
146
|
+
print("="*60 + "\n")
|
|
147
|
+
|
|
148
|
+
commands = [
|
|
149
|
+
{
|
|
150
|
+
'method': 'sketch.start',
|
|
151
|
+
'params': {'plane': 'XY'}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
'method': 'sketch.rect',
|
|
155
|
+
'params': {'x': 0, 'y': 0, 'width': 60, 'height': 40}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
'method': 'sketch.end',
|
|
159
|
+
'params': {}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
'method': 'ops.extrude',
|
|
163
|
+
'params': {'height': 30, 'material': 'aluminum'}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
print("Executing batch of 4 commands...")
|
|
168
|
+
r = client.batch(commands)
|
|
169
|
+
|
|
170
|
+
if r['ok']:
|
|
171
|
+
print(f"✓ All {r['executed']} commands succeeded in {r['elapsed']}ms")
|
|
172
|
+
for i, result in enumerate(r['results']):
|
|
173
|
+
cmd = commands[i]['method']
|
|
174
|
+
print(f" [{i+1}] {cmd}: {result.get('elapsed', 0)}ms")
|
|
175
|
+
else:
|
|
176
|
+
print(f"✗ Batch failed with {len(r['errors'])} errors")
|
|
177
|
+
for error in r['errors']:
|
|
178
|
+
print(f" [{error['index']}] {error['method']}: {error['error']}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def example_query_and_validation(client: CycleCADClient):
|
|
182
|
+
"""Example: Query model data and validate."""
|
|
183
|
+
print("\n" + "="*60)
|
|
184
|
+
print("EXAMPLE 3: Query and Validation")
|
|
185
|
+
print("="*60 + "\n")
|
|
186
|
+
|
|
187
|
+
# Get available materials
|
|
188
|
+
print("1. Available materials:")
|
|
189
|
+
r = client.execute('query.materials', {})
|
|
190
|
+
for mat in r['result']['materials']:
|
|
191
|
+
print(f" • {mat}")
|
|
192
|
+
|
|
193
|
+
# Get feature list
|
|
194
|
+
print("\n2. Current features:")
|
|
195
|
+
r = client.execute('query.features', {})
|
|
196
|
+
print(f" Total: {r['result']['count']} features")
|
|
197
|
+
|
|
198
|
+
# Validate mass
|
|
199
|
+
print("\n3. Calculating mass for steel part (extrude_...):")
|
|
200
|
+
r = client.execute('validate.mass', {
|
|
201
|
+
'target': 'extrude_1234567890000',
|
|
202
|
+
'material': 'steel'
|
|
203
|
+
})
|
|
204
|
+
print(f" Mass: {r['result']['mass']} kg")
|
|
205
|
+
|
|
206
|
+
# Estimate cost
|
|
207
|
+
print("\n4. Estimating manufacturing cost (FDM):")
|
|
208
|
+
r = client.execute('validate.cost', {
|
|
209
|
+
'target': 'extrude_1234567890000',
|
|
210
|
+
'process': 'FDM',
|
|
211
|
+
'material': 'PLA'
|
|
212
|
+
})
|
|
213
|
+
print(f" Estimated cost: ${r['result']['estimatedCost']} USD")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def example_view_operations(client: CycleCADClient):
|
|
217
|
+
"""Example: View control commands."""
|
|
218
|
+
print("\n" + "="*60)
|
|
219
|
+
print("EXAMPLE 4: View Operations")
|
|
220
|
+
print("="*60 + "\n")
|
|
221
|
+
|
|
222
|
+
views = ['isometric', 'top', 'front', 'right']
|
|
223
|
+
|
|
224
|
+
for view in views:
|
|
225
|
+
print(f"Setting view to: {view}")
|
|
226
|
+
r = client.execute('view.set', {'view': view})
|
|
227
|
+
print(f" ✓ {r['result']['message']}\n")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def example_export(client: CycleCADClient):
|
|
231
|
+
"""Example: Export operations."""
|
|
232
|
+
print("\n" + "="*60)
|
|
233
|
+
print("EXAMPLE 5: Export Operations")
|
|
234
|
+
print("="*60 + "\n")
|
|
235
|
+
|
|
236
|
+
formats = [
|
|
237
|
+
('stl', {'filename': 'part.stl', 'binary': True}),
|
|
238
|
+
('obj', {'filename': 'part.obj'}),
|
|
239
|
+
('gltf', {'filename': 'part.gltf'})
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
for fmt, params in formats:
|
|
243
|
+
print(f"Exporting to {fmt.upper()}:")
|
|
244
|
+
r = client.execute(f'export.{fmt}', params)
|
|
245
|
+
if r['ok']:
|
|
246
|
+
print(f" ✓ {r['result']['message']}")
|
|
247
|
+
print(f" Filename: {r['result']['filename']}\n")
|
|
248
|
+
else:
|
|
249
|
+
print(f" ✗ Error: {r['error']}\n")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def example_server_info(client: CycleCADClient):
|
|
253
|
+
"""Example: Get server information."""
|
|
254
|
+
print("\n" + "="*60)
|
|
255
|
+
print("EXAMPLE 6: Server Information")
|
|
256
|
+
print("="*60 + "\n")
|
|
257
|
+
|
|
258
|
+
print("1. Health check:")
|
|
259
|
+
health = client.get_health()
|
|
260
|
+
print(f" Status: {health['status']}")
|
|
261
|
+
print(f" Version: {health['version']}")
|
|
262
|
+
print(f" Uptime: {health['uptime']} seconds")
|
|
263
|
+
print(f" Commands available: {health['commands']}")
|
|
264
|
+
print(f" Commands executed: {health['commandsExecuted']}")
|
|
265
|
+
print(f" Session ID: {health['sessionId']}")
|
|
266
|
+
|
|
267
|
+
print("\n2. API Schema (first 5 commands):")
|
|
268
|
+
schema = client.get_schema()
|
|
269
|
+
count = 0
|
|
270
|
+
for namespace, data in schema['namespaces'].items():
|
|
271
|
+
print(f"\n Namespace: {namespace}")
|
|
272
|
+
print(f" {data.get('description', 'N/A')}")
|
|
273
|
+
for cmd_name in list(data['commands'].keys())[:3]:
|
|
274
|
+
cmd = data['commands'][cmd_name]
|
|
275
|
+
print(f" • {cmd['method']}")
|
|
276
|
+
count += 1
|
|
277
|
+
if count >= 5:
|
|
278
|
+
break
|
|
279
|
+
if count >= 5:
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
print(f"\n ... and {schema['totalCommands'] - count} more commands")
|
|
283
|
+
|
|
284
|
+
print("\n3. Command history (last 5 commands):")
|
|
285
|
+
history = client.get_history(count=5)
|
|
286
|
+
print(f" Total executed: {history['total']}")
|
|
287
|
+
for entry in history['recent']:
|
|
288
|
+
status = '✓' if entry['ok'] else '✗'
|
|
289
|
+
print(f" [{status}] {entry['method']} ({entry['elapsed']}ms)")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main():
|
|
293
|
+
parser = argparse.ArgumentParser(
|
|
294
|
+
description='cycleCAD API Client Example'
|
|
295
|
+
)
|
|
296
|
+
parser.add_argument('--host', default='localhost', help='Server host (default: localhost)')
|
|
297
|
+
parser.add_argument('--port', type=int, default=3000, help='Server port (default: 3000)')
|
|
298
|
+
parser.add_argument('--api-key', help='API key for authentication (optional)')
|
|
299
|
+
parser.add_argument('--example', choices=[
|
|
300
|
+
'all', 'simple', 'batch', 'query', 'view', 'export', 'info'
|
|
301
|
+
], default='all', help='Which example to run')
|
|
302
|
+
|
|
303
|
+
args = parser.parse_args()
|
|
304
|
+
|
|
305
|
+
print("\n" + "█"*60)
|
|
306
|
+
print("█ cycleCAD API Client — Example Usage")
|
|
307
|
+
print("█"*60)
|
|
308
|
+
print(f"\nConnecting to {args.host}:{args.port}...")
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
client = CycleCADClient(
|
|
312
|
+
host=args.host,
|
|
313
|
+
port=args.port,
|
|
314
|
+
api_key=args.api_key
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Test connection
|
|
318
|
+
health = client.get_health()
|
|
319
|
+
print(f"✓ Connected to cycleCAD API v{health['version']}\n")
|
|
320
|
+
|
|
321
|
+
# Run examples
|
|
322
|
+
examples = {
|
|
323
|
+
'all': [
|
|
324
|
+
example_server_info,
|
|
325
|
+
example_simple_part,
|
|
326
|
+
example_batch_operations,
|
|
327
|
+
example_query_and_validation,
|
|
328
|
+
example_view_operations,
|
|
329
|
+
example_export
|
|
330
|
+
],
|
|
331
|
+
'simple': [example_simple_part],
|
|
332
|
+
'batch': [example_batch_operations],
|
|
333
|
+
'query': [example_query_and_validation],
|
|
334
|
+
'view': [example_view_operations],
|
|
335
|
+
'export': [example_export],
|
|
336
|
+
'info': [example_server_info]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for example_fn in examples[args.example]:
|
|
340
|
+
try:
|
|
341
|
+
example_fn(client)
|
|
342
|
+
except Exception as e:
|
|
343
|
+
print(f"\n✗ Example failed: {e}")
|
|
344
|
+
|
|
345
|
+
print("\n" + "█"*60)
|
|
346
|
+
print("█ Examples completed!")
|
|
347
|
+
print("█"*60 + "\n")
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
print(f"\n✗ Connection failed: {e}")
|
|
351
|
+
print("\nMake sure the API server is running:")
|
|
352
|
+
print(f" npm run server (from {args.host}:{args.port})")
|
|
353
|
+
return 1
|
|
354
|
+
|
|
355
|
+
return 0
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == '__main__':
|
|
359
|
+
exit(main())
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Manufacturing analysis workflow
|
|
2
|
+
# Run with: ./bin/cyclecad-cli.js --batch examples/batch-manufacturing.txt
|
|
3
|
+
|
|
4
|
+
# Create a bracket shape
|
|
5
|
+
sketch.start --plane XY
|
|
6
|
+
sketch.rect --x 0 --y 0 --width 80 --height 40
|
|
7
|
+
sketch.end
|
|
8
|
+
|
|
9
|
+
# Extrude to create a base
|
|
10
|
+
feature.extrude --height 15
|
|
11
|
+
|
|
12
|
+
# Add a fillet for manufacturing
|
|
13
|
+
feature.fillet --radius 2 --edges all
|
|
14
|
+
|
|
15
|
+
# Check if it's manufacturable
|
|
16
|
+
validate.printability --target extrude_1 --process CNC
|
|
17
|
+
|
|
18
|
+
# Estimate material cost
|
|
19
|
+
validate.cost --target extrude_1 --process CNC --material aluminum
|
|
20
|
+
|
|
21
|
+
# Estimate weight
|
|
22
|
+
validate.mass --target extrude_1 --material aluminum
|
|
23
|
+
|
|
24
|
+
# Design review
|
|
25
|
+
validate.designReview --target extrude_1
|
|
26
|
+
|
|
27
|
+
# Export for manufacturing
|
|
28
|
+
export.stl --filename bracket-cad.stl --binary true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Simple batch script for cycleCAD CLI
|
|
2
|
+
# Run with: ./bin/cyclecad-cli.js --batch examples/batch-simple.txt
|
|
3
|
+
|
|
4
|
+
# Create a cylinder
|
|
5
|
+
shape.cylinder --radius 25 --height 80
|
|
6
|
+
|
|
7
|
+
# Create a box
|
|
8
|
+
shape.box --width 50 --height 40 --depth 30
|
|
9
|
+
|
|
10
|
+
# Start a sketch
|
|
11
|
+
sketch.start --plane XY
|
|
12
|
+
|
|
13
|
+
# Add a circle to the sketch
|
|
14
|
+
sketch.circle --cx 0 --cy 0 --radius 15
|
|
15
|
+
|
|
16
|
+
# End the sketch
|
|
17
|
+
sketch.end
|
|
18
|
+
|
|
19
|
+
# Extrude the sketch
|
|
20
|
+
feature.extrude --height 10
|
|
21
|
+
|
|
22
|
+
# Check dimensions
|
|
23
|
+
validate.dimensions --target extrude_1
|
|
24
|
+
|
|
25
|
+
# Export to STL
|
|
26
|
+
export.stl --filename my-part.stl
|