dsh-cad 0.13.2 → 0.14.0
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/LICENSE +201 -201
- package/README.md +3 -3
- package/README.zh-CN.md +1 -1
- package/lib/cad_connector/freecad-executor.js +209 -209
- package/lib/cad_connector/fusion360-executor.js +244 -244
- package/lib/client.js +172 -172
- package/lib/convert/step-worker.mjs +61 -61
- package/lib/demo-bracket.brep +667 -667
- package/lib/demo-flange.brep +708 -708
- package/lib/demo-shaft.brep +515 -515
- package/lib/modeling/modeling-worker.cjs +2 -2
- package/lib/modeling/occt-adapter.cjs +90 -3
- package/lib/modeling/occtts-adapter.cjs +80 -7
- package/lib/routes.js +5 -0
- package/lib/tools/cad-model.js +16 -5
- package/lib/types/modeling/client.d.ts +19 -1
- package/package.json +1 -1
|
@@ -88,215 +88,215 @@ export function freecadAvailable() {
|
|
|
88
88
|
return findFreeCad() !== null;
|
|
89
89
|
}
|
|
90
90
|
/** The Python bridge executed inside FreeCAD's console binary. */
|
|
91
|
-
const BRIDGE = String.raw `
|
|
92
|
-
import json, math, os, struct, traceback
|
|
93
|
-
|
|
94
|
-
import FreeCAD
|
|
95
|
-
import Part
|
|
96
|
-
|
|
97
|
-
BASE = os.path.dirname(os.path.abspath(__file__))
|
|
98
|
-
OPS_PATH = os.path.join(BASE, 'ops.json')
|
|
99
|
-
RESULT_PATH = os.path.join(BASE, 'result.json')
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def write(result):
|
|
103
|
-
with open(RESULT_PATH, 'w') as handle:
|
|
104
|
-
json.dump(result, handle)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def vec(value, default=(0.0, 0.0, 0.0)):
|
|
108
|
-
if not value:
|
|
109
|
-
return FreeCAD.Vector(*default)
|
|
110
|
-
return FreeCAD.Vector(float(value[0]), float(value[1]), float(value[2]))
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def orient(shape, at, axis):
|
|
114
|
-
if at:
|
|
115
|
-
shape.translate(vec(at))
|
|
116
|
-
if axis:
|
|
117
|
-
target = vec(axis, (0.0, 0.0, 1.0))
|
|
118
|
-
if target.Length < 1e-9:
|
|
119
|
-
return shape
|
|
120
|
-
target.normalize()
|
|
121
|
-
if abs(target.z - 1.0) > 1e-9:
|
|
122
|
-
rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), target)
|
|
123
|
-
shape.transformShape(rotation.toMatrix(), True)
|
|
124
|
-
return shape
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def make_prim(prim, params):
|
|
128
|
-
params = params or {}
|
|
129
|
-
if prim == 'box':
|
|
130
|
-
shape = Part.makeBox(float(params.get('dx', 10)), float(params.get('dy', 10)), float(params.get('dz', 10)))
|
|
131
|
-
elif prim == 'cylinder':
|
|
132
|
-
shape = Part.makeCylinder(float(params.get('radius', 5)), float(params.get('height', 10)))
|
|
133
|
-
elif prim == 'sphere':
|
|
134
|
-
shape = Part.makeSphere(float(params.get('radius', 5)))
|
|
135
|
-
elif prim == 'cone':
|
|
136
|
-
shape = Part.makeCone(float(params.get('radius1', 5)), float(params.get('radius2', 0)), float(params.get('height', 10)))
|
|
137
|
-
elif prim == 'torus':
|
|
138
|
-
shape = Part.makeTorus(float(params.get('majorRadius', 10)), float(params.get('minorRadius', 2)))
|
|
139
|
-
else:
|
|
140
|
-
raise ValueError('unknown primitive: %s' % prim)
|
|
141
|
-
return orient(shape, params.get('at'), params.get('axis'))
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def extrude_profile(points, height, base):
|
|
145
|
-
pts = []
|
|
146
|
-
for i in range(0, len(points) - 1, 2):
|
|
147
|
-
pts.append(FreeCAD.Vector(float(points[i]), float(points[i + 1]), float(base or 0.0)))
|
|
148
|
-
if len(pts) < 3:
|
|
149
|
-
raise ValueError('profile needs at least 3 points')
|
|
150
|
-
if (pts[-1] - pts[0]).Length > 1e-9:
|
|
151
|
-
pts.append(pts[0])
|
|
152
|
-
return Part.Face(Part.makePolygon(pts)).extrude(FreeCAD.Vector(0, 0, float(height or 10)))
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def mesh_to_shape(path):
|
|
156
|
-
import Mesh, MeshPart
|
|
157
|
-
return MeshPart.meshToShape(Mesh.Mesh(str(path)))
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def flat_normal(a, b, c):
|
|
161
|
-
n = (b - a).cross(c - a)
|
|
162
|
-
length = n.Length
|
|
163
|
-
if length > 1e-12:
|
|
164
|
-
n = FreeCAD.Vector(n.x / length, n.y / length, n.z / length)
|
|
165
|
-
else:
|
|
166
|
-
n = FreeCAD.Vector(0, 0, 1)
|
|
167
|
-
return [n.x, n.y, n.z]
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def tessellate(body_id, shape, name):
|
|
171
|
-
verts, facets = shape.tessellate(0.4)
|
|
172
|
-
positions = []
|
|
173
|
-
normals = []
|
|
174
|
-
indices = []
|
|
175
|
-
for v in verts:
|
|
176
|
-
positions.extend([v.x, v.y, v.z])
|
|
177
|
-
for f in facets:
|
|
178
|
-
a, b, c = verts[f[0]], verts[f[1]], verts[f[2]]
|
|
179
|
-
n = flat_normal(a, b, c)
|
|
180
|
-
normals.extend(n + n + n)
|
|
181
|
-
indices.extend([f[0], f[1], f[2]])
|
|
182
|
-
return {
|
|
183
|
-
'bodyId': body_id,
|
|
184
|
-
'name': name or body_id,
|
|
185
|
-
'positions': positions,
|
|
186
|
-
'normals': normals,
|
|
187
|
-
'indices': indices,
|
|
188
|
-
'vertexCount': len(verts),
|
|
189
|
-
'triangleCount': len(facets),
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def write_stl(shape, path):
|
|
194
|
-
verts, facets = shape.tessellate(0.2)
|
|
195
|
-
with open(path, 'wb') as handle:
|
|
196
|
-
handle.write(b'\0' * 80)
|
|
197
|
-
handle.write(struct.pack('<I', len(facets)))
|
|
198
|
-
for f in facets:
|
|
199
|
-
a, b, c = verts[f[0]], verts[f[1]], verts[f[2]]
|
|
200
|
-
n = flat_normal(a, b, c)
|
|
201
|
-
handle.write(struct.pack('<3f', n[0], n[1], n[2]))
|
|
202
|
-
for v in (a, b, c):
|
|
203
|
-
handle.write(struct.pack('<3f', v.x, v.y, v.z))
|
|
204
|
-
handle.write(struct.pack('<H', 0))
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def run(spec):
|
|
208
|
-
bodies = {}
|
|
209
|
-
volumes = {}
|
|
210
|
-
source = spec.get('input')
|
|
211
|
-
if source:
|
|
212
|
-
fmt = str(source.get('format', 'step')).lower()
|
|
213
|
-
body_id = source.get('bodyId', 'input')
|
|
214
|
-
if fmt in ('step', 'stp', 'brep'):
|
|
215
|
-
bodies[body_id] = Part.read(str(source['path']))
|
|
216
|
-
elif fmt == 'stl':
|
|
217
|
-
bodies[body_id] = mesh_to_shape(source['path'])
|
|
218
|
-
else:
|
|
219
|
-
raise ValueError('unsupported input format: %s' % fmt)
|
|
220
|
-
for op in spec.get('ops', []):
|
|
221
|
-
kind = op['kind']
|
|
222
|
-
if kind == 'reset':
|
|
223
|
-
bodies = {}
|
|
224
|
-
elif kind == 'create_prim':
|
|
225
|
-
bodies[op['bodyId']] = make_prim(op['prim'], op.get('params'))
|
|
226
|
-
elif kind == 'extrude_profile':
|
|
227
|
-
bodies[op['bodyId']] = extrude_profile(op['points'], op.get('height'), op.get('base'))
|
|
228
|
-
elif kind == 'boolean':
|
|
229
|
-
target = bodies[op['target']]
|
|
230
|
-
operation = op['op']
|
|
231
|
-
for tool_id in op['tools']:
|
|
232
|
-
tool = bodies[tool_id]
|
|
233
|
-
if operation == 'cut':
|
|
234
|
-
target = target.cut(tool)
|
|
235
|
-
elif operation == 'fuse':
|
|
236
|
-
target = target.fuse(tool)
|
|
237
|
-
elif operation == 'common':
|
|
238
|
-
target = target.common(tool)
|
|
239
|
-
else:
|
|
240
|
-
raise ValueError('unknown boolean: %s' % operation)
|
|
241
|
-
del bodies[tool_id]
|
|
242
|
-
bodies[op['target']] = target
|
|
243
|
-
elif kind == 'fillet':
|
|
244
|
-
shape = bodies[op['target']]
|
|
245
|
-
bodies[op['target']] = shape.makeFillet(float(op['radius']), shape.Edges)
|
|
246
|
-
elif kind == 'transform':
|
|
247
|
-
shape = bodies[op['target']]
|
|
248
|
-
if op.get('translate'):
|
|
249
|
-
shape.translate(vec(op['translate']))
|
|
250
|
-
rotate = op.get('rotate')
|
|
251
|
-
if rotate:
|
|
252
|
-
for axis, angle in ((FreeCAD.Vector(1, 0, 0), rotate[0]), (FreeCAD.Vector(0, 1, 0), rotate[1]), (FreeCAD.Vector(0, 0, 1), rotate[2])):
|
|
253
|
-
if abs(float(angle)) > 1e-9:
|
|
254
|
-
shape.rotate(FreeCAD.Vector(0, 0, 0), axis, float(angle))
|
|
255
|
-
mirror = op.get('mirror')
|
|
256
|
-
if mirror:
|
|
257
|
-
shape = shape.mirror(FreeCAD.Vector(0, 0, 0), vec(mirror, (0.0, 0.0, 1.0)))
|
|
258
|
-
bodies[op['target']] = shape
|
|
259
|
-
elif kind == 'volume':
|
|
260
|
-
volumes[op['target']] = bodies[op['target']].Volume
|
|
261
|
-
elif kind == 'delete':
|
|
262
|
-
bodies.pop(op['target'], None)
|
|
263
|
-
else:
|
|
264
|
-
raise ValueError('unsupported op: %s' % kind)
|
|
265
|
-
names = spec.get('names') or {}
|
|
266
|
-
result = {'ok': True, 'meshes': [tessellate(i, s, names.get(i)) for i, s in bodies.items()], 'volumes': volumes}
|
|
267
|
-
export = spec.get('export')
|
|
268
|
-
if export:
|
|
269
|
-
fmt = str(export.get('format', 'step')).lower()
|
|
270
|
-
target_path = str(export['path'])
|
|
271
|
-
shapes = list(bodies.values())
|
|
272
|
-
if len(shapes) == 0:
|
|
273
|
-
raise ValueError('nothing to export — the program produced no bodies')
|
|
274
|
-
if fmt in ('step', 'stp'):
|
|
275
|
-
# Part.export() writes files Part.read() loads as null shapes in
|
|
276
|
-
# FreeCAD 1.1 — a compound through exportStep() round-trips.
|
|
277
|
-
Part.Compound(shapes).exportStep(target_path)
|
|
278
|
-
elif fmt == 'stl':
|
|
279
|
-
write_stl(shapes[0], target_path)
|
|
280
|
-
else:
|
|
281
|
-
raise ValueError('unsupported export format: %s' % fmt)
|
|
282
|
-
result['exported'] = target_path
|
|
283
|
-
if spec.get('display'):
|
|
284
|
-
document = FreeCAD.newDocument('dsh-cad')
|
|
285
|
-
for body_id, shape in bodies.items():
|
|
286
|
-
obj = document.addObject('Part::Feature', body_id)
|
|
287
|
-
obj.Label = names.get(body_id, body_id)
|
|
288
|
-
obj.Shape = shape
|
|
289
|
-
document.recompute()
|
|
290
|
-
return result
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
try:
|
|
294
|
-
with open(OPS_PATH) as handle:
|
|
295
|
-
write(run(json.load(handle)))
|
|
296
|
-
print('###DSH-OK###')
|
|
297
|
-
except Exception as error:
|
|
298
|
-
write({'ok': False, 'error': str(error), 'trace': traceback.format_exc()})
|
|
299
|
-
print('###DSH-ERR###')
|
|
91
|
+
const BRIDGE = String.raw `
|
|
92
|
+
import json, math, os, struct, traceback
|
|
93
|
+
|
|
94
|
+
import FreeCAD
|
|
95
|
+
import Part
|
|
96
|
+
|
|
97
|
+
BASE = os.path.dirname(os.path.abspath(__file__))
|
|
98
|
+
OPS_PATH = os.path.join(BASE, 'ops.json')
|
|
99
|
+
RESULT_PATH = os.path.join(BASE, 'result.json')
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def write(result):
|
|
103
|
+
with open(RESULT_PATH, 'w') as handle:
|
|
104
|
+
json.dump(result, handle)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def vec(value, default=(0.0, 0.0, 0.0)):
|
|
108
|
+
if not value:
|
|
109
|
+
return FreeCAD.Vector(*default)
|
|
110
|
+
return FreeCAD.Vector(float(value[0]), float(value[1]), float(value[2]))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def orient(shape, at, axis):
|
|
114
|
+
if at:
|
|
115
|
+
shape.translate(vec(at))
|
|
116
|
+
if axis:
|
|
117
|
+
target = vec(axis, (0.0, 0.0, 1.0))
|
|
118
|
+
if target.Length < 1e-9:
|
|
119
|
+
return shape
|
|
120
|
+
target.normalize()
|
|
121
|
+
if abs(target.z - 1.0) > 1e-9:
|
|
122
|
+
rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), target)
|
|
123
|
+
shape.transformShape(rotation.toMatrix(), True)
|
|
124
|
+
return shape
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def make_prim(prim, params):
|
|
128
|
+
params = params or {}
|
|
129
|
+
if prim == 'box':
|
|
130
|
+
shape = Part.makeBox(float(params.get('dx', 10)), float(params.get('dy', 10)), float(params.get('dz', 10)))
|
|
131
|
+
elif prim == 'cylinder':
|
|
132
|
+
shape = Part.makeCylinder(float(params.get('radius', 5)), float(params.get('height', 10)))
|
|
133
|
+
elif prim == 'sphere':
|
|
134
|
+
shape = Part.makeSphere(float(params.get('radius', 5)))
|
|
135
|
+
elif prim == 'cone':
|
|
136
|
+
shape = Part.makeCone(float(params.get('radius1', 5)), float(params.get('radius2', 0)), float(params.get('height', 10)))
|
|
137
|
+
elif prim == 'torus':
|
|
138
|
+
shape = Part.makeTorus(float(params.get('majorRadius', 10)), float(params.get('minorRadius', 2)))
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError('unknown primitive: %s' % prim)
|
|
141
|
+
return orient(shape, params.get('at'), params.get('axis'))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def extrude_profile(points, height, base):
|
|
145
|
+
pts = []
|
|
146
|
+
for i in range(0, len(points) - 1, 2):
|
|
147
|
+
pts.append(FreeCAD.Vector(float(points[i]), float(points[i + 1]), float(base or 0.0)))
|
|
148
|
+
if len(pts) < 3:
|
|
149
|
+
raise ValueError('profile needs at least 3 points')
|
|
150
|
+
if (pts[-1] - pts[0]).Length > 1e-9:
|
|
151
|
+
pts.append(pts[0])
|
|
152
|
+
return Part.Face(Part.makePolygon(pts)).extrude(FreeCAD.Vector(0, 0, float(height or 10)))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def mesh_to_shape(path):
|
|
156
|
+
import Mesh, MeshPart
|
|
157
|
+
return MeshPart.meshToShape(Mesh.Mesh(str(path)))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def flat_normal(a, b, c):
|
|
161
|
+
n = (b - a).cross(c - a)
|
|
162
|
+
length = n.Length
|
|
163
|
+
if length > 1e-12:
|
|
164
|
+
n = FreeCAD.Vector(n.x / length, n.y / length, n.z / length)
|
|
165
|
+
else:
|
|
166
|
+
n = FreeCAD.Vector(0, 0, 1)
|
|
167
|
+
return [n.x, n.y, n.z]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def tessellate(body_id, shape, name):
|
|
171
|
+
verts, facets = shape.tessellate(0.4)
|
|
172
|
+
positions = []
|
|
173
|
+
normals = []
|
|
174
|
+
indices = []
|
|
175
|
+
for v in verts:
|
|
176
|
+
positions.extend([v.x, v.y, v.z])
|
|
177
|
+
for f in facets:
|
|
178
|
+
a, b, c = verts[f[0]], verts[f[1]], verts[f[2]]
|
|
179
|
+
n = flat_normal(a, b, c)
|
|
180
|
+
normals.extend(n + n + n)
|
|
181
|
+
indices.extend([f[0], f[1], f[2]])
|
|
182
|
+
return {
|
|
183
|
+
'bodyId': body_id,
|
|
184
|
+
'name': name or body_id,
|
|
185
|
+
'positions': positions,
|
|
186
|
+
'normals': normals,
|
|
187
|
+
'indices': indices,
|
|
188
|
+
'vertexCount': len(verts),
|
|
189
|
+
'triangleCount': len(facets),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def write_stl(shape, path):
|
|
194
|
+
verts, facets = shape.tessellate(0.2)
|
|
195
|
+
with open(path, 'wb') as handle:
|
|
196
|
+
handle.write(b'\0' * 80)
|
|
197
|
+
handle.write(struct.pack('<I', len(facets)))
|
|
198
|
+
for f in facets:
|
|
199
|
+
a, b, c = verts[f[0]], verts[f[1]], verts[f[2]]
|
|
200
|
+
n = flat_normal(a, b, c)
|
|
201
|
+
handle.write(struct.pack('<3f', n[0], n[1], n[2]))
|
|
202
|
+
for v in (a, b, c):
|
|
203
|
+
handle.write(struct.pack('<3f', v.x, v.y, v.z))
|
|
204
|
+
handle.write(struct.pack('<H', 0))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def run(spec):
|
|
208
|
+
bodies = {}
|
|
209
|
+
volumes = {}
|
|
210
|
+
source = spec.get('input')
|
|
211
|
+
if source:
|
|
212
|
+
fmt = str(source.get('format', 'step')).lower()
|
|
213
|
+
body_id = source.get('bodyId', 'input')
|
|
214
|
+
if fmt in ('step', 'stp', 'brep'):
|
|
215
|
+
bodies[body_id] = Part.read(str(source['path']))
|
|
216
|
+
elif fmt == 'stl':
|
|
217
|
+
bodies[body_id] = mesh_to_shape(source['path'])
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError('unsupported input format: %s' % fmt)
|
|
220
|
+
for op in spec.get('ops', []):
|
|
221
|
+
kind = op['kind']
|
|
222
|
+
if kind == 'reset':
|
|
223
|
+
bodies = {}
|
|
224
|
+
elif kind == 'create_prim':
|
|
225
|
+
bodies[op['bodyId']] = make_prim(op['prim'], op.get('params'))
|
|
226
|
+
elif kind == 'extrude_profile':
|
|
227
|
+
bodies[op['bodyId']] = extrude_profile(op['points'], op.get('height'), op.get('base'))
|
|
228
|
+
elif kind == 'boolean':
|
|
229
|
+
target = bodies[op['target']]
|
|
230
|
+
operation = op['op']
|
|
231
|
+
for tool_id in op['tools']:
|
|
232
|
+
tool = bodies[tool_id]
|
|
233
|
+
if operation == 'cut':
|
|
234
|
+
target = target.cut(tool)
|
|
235
|
+
elif operation == 'fuse':
|
|
236
|
+
target = target.fuse(tool)
|
|
237
|
+
elif operation == 'common':
|
|
238
|
+
target = target.common(tool)
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError('unknown boolean: %s' % operation)
|
|
241
|
+
del bodies[tool_id]
|
|
242
|
+
bodies[op['target']] = target
|
|
243
|
+
elif kind == 'fillet':
|
|
244
|
+
shape = bodies[op['target']]
|
|
245
|
+
bodies[op['target']] = shape.makeFillet(float(op['radius']), shape.Edges)
|
|
246
|
+
elif kind == 'transform':
|
|
247
|
+
shape = bodies[op['target']]
|
|
248
|
+
if op.get('translate'):
|
|
249
|
+
shape.translate(vec(op['translate']))
|
|
250
|
+
rotate = op.get('rotate')
|
|
251
|
+
if rotate:
|
|
252
|
+
for axis, angle in ((FreeCAD.Vector(1, 0, 0), rotate[0]), (FreeCAD.Vector(0, 1, 0), rotate[1]), (FreeCAD.Vector(0, 0, 1), rotate[2])):
|
|
253
|
+
if abs(float(angle)) > 1e-9:
|
|
254
|
+
shape.rotate(FreeCAD.Vector(0, 0, 0), axis, float(angle))
|
|
255
|
+
mirror = op.get('mirror')
|
|
256
|
+
if mirror:
|
|
257
|
+
shape = shape.mirror(FreeCAD.Vector(0, 0, 0), vec(mirror, (0.0, 0.0, 1.0)))
|
|
258
|
+
bodies[op['target']] = shape
|
|
259
|
+
elif kind == 'volume':
|
|
260
|
+
volumes[op['target']] = bodies[op['target']].Volume
|
|
261
|
+
elif kind == 'delete':
|
|
262
|
+
bodies.pop(op['target'], None)
|
|
263
|
+
else:
|
|
264
|
+
raise ValueError('unsupported op: %s' % kind)
|
|
265
|
+
names = spec.get('names') or {}
|
|
266
|
+
result = {'ok': True, 'meshes': [tessellate(i, s, names.get(i)) for i, s in bodies.items()], 'volumes': volumes}
|
|
267
|
+
export = spec.get('export')
|
|
268
|
+
if export:
|
|
269
|
+
fmt = str(export.get('format', 'step')).lower()
|
|
270
|
+
target_path = str(export['path'])
|
|
271
|
+
shapes = list(bodies.values())
|
|
272
|
+
if len(shapes) == 0:
|
|
273
|
+
raise ValueError('nothing to export — the program produced no bodies')
|
|
274
|
+
if fmt in ('step', 'stp'):
|
|
275
|
+
# Part.export() writes files Part.read() loads as null shapes in
|
|
276
|
+
# FreeCAD 1.1 — a compound through exportStep() round-trips.
|
|
277
|
+
Part.Compound(shapes).exportStep(target_path)
|
|
278
|
+
elif fmt == 'stl':
|
|
279
|
+
write_stl(shapes[0], target_path)
|
|
280
|
+
else:
|
|
281
|
+
raise ValueError('unsupported export format: %s' % fmt)
|
|
282
|
+
result['exported'] = target_path
|
|
283
|
+
if spec.get('display'):
|
|
284
|
+
document = FreeCAD.newDocument('dsh-cad')
|
|
285
|
+
for body_id, shape in bodies.items():
|
|
286
|
+
obj = document.addObject('Part::Feature', body_id)
|
|
287
|
+
obj.Label = names.get(body_id, body_id)
|
|
288
|
+
obj.Shape = shape
|
|
289
|
+
document.recompute()
|
|
290
|
+
return result
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
with open(OPS_PATH) as handle:
|
|
295
|
+
write(run(json.load(handle)))
|
|
296
|
+
print('###DSH-OK###')
|
|
297
|
+
except Exception as error:
|
|
298
|
+
write({'ok': False, 'error': str(error), 'trace': traceback.format_exc()})
|
|
299
|
+
print('###DSH-ERR###')
|
|
300
300
|
`;
|
|
301
301
|
/**
|
|
302
302
|
* Run a feature program in an external FreeCAD process and return tessellated
|