FreeCAD-macros
509 строк · 16.1 Кб
1# -*- coding: utf-8 -*-
2
3__Version__ = '0.7.3'
4__Date__ = '2024-03-16'
5__License__ = 'LGPL-3.0-or-later'
6__Web__ = ''
7__Wiki__ = 'README.md'
8__Name__ = 'Objects To Python'
9__Comment__ = 'Exports objects from a FreeCAD project to a python script'
10__Author__ = 'Christi'
11__Icon__ = 'ObjectsToPython.svg'
12__Help__ = 'ObjectsToPython/README.md'
13__Status__ = 'Alpha'
14__Requires__ = 'FreeCAD >= 0.19'
15__Communication__ = 'https://forum.freecadweb.org/viewtopic.php?f=22&t=34612'
16__Files__ = 'ObjectsToPython/README.md,ObjectsToPython/ObjectsToPython.ui,ObjectsToPython.svg'
17
18import os
19import re
20
21import FreeCAD as app
22import FreeCADGui as gui
23import Part
24from FreeCAD import Vector, Placement
25
26
27def exportObjectsToPython(doc=None):
28if doc is None:
29doc = app.activeDocument()
30
31add_script_line('from FreeCAD import Vector, Placement, Rotation')
32add_script_line('import Sketcher')
33add_script_line('import Part')
34add_script_line('import FreeCAD as app')
35objectlist = []
36skip_objects = [
37('App::Line', 'X_Axis'),
38('App::Line', 'Y_Axis'),
39('App::Line', 'Z_Axis'),
40('App::Plane', 'XY_Plane'),
41('App::Plane', 'XZ_Plane'),
42('App::Plane', 'YZ_Plane'),
43('App::Origin', 'Origin'),
44]
45
46selection = gui.Selection.getSelection()
47if not selection:
48selection = doc.Objects
49else:
50expandSelection(selection)
51
52for obj in doc.Objects:
53objectlist.append(doc.getObject(obj.Name))
54
55for obj in selection:
56if obj.TypeId == 'Sketcher::SketchObject':
57add_script_line('')
58add_script_line(f'def createSketch_{varname(obj)}(doc):')
59addObject(doc, obj, objectlist)
60add_body_line('return ' + varname(obj))
61
62add_script_line('')
63add_script_line('')
64add_script_line(f'def make_{doc.Name}():')
65if not selection:
66add_body_line(f"doc = app.newDocument('{doc.Name}')")
67else:
68add_body_line('doc = app.activeDocument()')
69
70for obj in selection:
71if (obj.TypeId, obj.Label) not in skip_objects:
72add_script_line('')
73if obj.TypeId == 'Sketcher::SketchObject':
74add_body_line(f'{varname(obj)} = createSketch_{
75varname(obj)}(doc)')
76else:
77addObject(doc, obj, objectlist)
78
79for obj in selection:
80if obj.TypeId == 'PartDesign::Body':
81add_script_line('')
82add_body_line(f'{varname(obj)}.Group = {varname(obj)}_Group')
83if obj.Tip:
84add_body_line(f'{varname(obj)}.Tip = {varname(obj.Tip)}')
85
86add_body_line('')
87add_body_line('doc.recompute()')
88add_script_line('')
89add_script_line('')
90add_script_line(f'make_{doc.Name}()')
91
92
93def varname(obj):
94forbidden = [' ', '.', ',', '%', '+', '-', '*',
95'/', '(', ')', '[', ']', '=', '"', "'"]
96l = obj.Label
97for f in forbidden:
98l = l.replace(f, '_')
99
100if l[0].isdigit():
101l = '_' + l
102return l
103
104
105def add_script_line(line, indent=0):
106spaces = ' ' * 4 * indent
107dialog.form.textEdit.append(spaces + line)
108
109
110def add_body_line(line, indent=1):
111add_script_line(line, indent)
112
113
114def expandSelection(selection):
115addthis = []
116for obj in selection:
117if hasattr(obj, 'Group'):
118addthis += obj.Group
119if hasattr(obj, 'Source'):
120addthis.append(obj.Source)
121if hasattr(obj, 'Links'):
122addthis += obj.Links
123
124addcount = 0
125for a in addthis:
126if ((a not in selection)
127and hasattr(obj, 'Name')
128and hasattr(obj, 'TypeId')):
129selection.insert(0, a)
130addcount = addcount + 1
131
132if addcount > 0:
133expandSelection(selection)
134
135
136def addObject(doc, obj, objectlist):
137objname = varname(obj)
138add_body_line(f"{objname} = doc.addObject('{obj.TypeId}', '{objname}')")
139defaultobj = doc.addObject(obj.TypeId, obj.Name + 'Default')
140addProperties(obj, objname, defaultobj, objectlist)
141addProperties(obj.ViewObject, objname + '.ViewObject',
142defaultobj.ViewObject, [])
143doc.removeObject(obj.Name + 'Default')
144
145
146def addProperties(obj, objname, refobj, objectlist):
147skipProperties = ['Label', 'Shape', 'Proxy',
148'AddSubShape', 'FullyConstrained', 'VisualLayerList']
149skipObjProp = [
150('Sketcher::SketchObject', 'Geometry'),
151('Sketcher::SketchObject', 'Constraints'),
152('PartDesign::Body', 'Origin'),
153('PartDesign::Body', 'Tip'),
154]
155
156if obj.TypeId == 'Spreadsheet::Sheet':
157addSpreadsheet(obj, objname)
158return
159
160if obj.TypeId == 'Sketcher::SketchObject':
161addSketch(obj, objname)
162
163for propname in obj.PropertiesList:
164if ((propname not in skipProperties)
165and ((obj.TypeId, propname) not in skipObjProp)):
166prop = obj.getPropertyByName(propname)
167val = objectToText(prop, objectlist)
168
169try:
170refprop = refobj.getPropertyByName(propname)
171defaultval = objectToText(refprop, objectlist)
172except:
173defaultval = None
174
175if propname == 'Support':
176val = val.replace('XY_Plane', 'doc.XY_Plane')
177val = val.replace('XZ_Plane', 'doc.XZ_Plane')
178val = val.replace('YZ_Plane', 'doc.YZ_Plane')
179
180if propname == 'ExpressionEngine':
181for expression in prop:
182add_body_line(objname + '.setExpression' +
183str(expression).replace('<', '').replace('>', ''))
184
185elif propname == '_Body':
186add_body_line(f'{val}_Group.append({objname})')
187
188elif (obj.TypeId, propname) == ('PartDesign::Body', 'Group'):
189add_body_line(f'{objname}_Group = []')
190
191elif (obj.TypeId, propname) == ('App::Part', 'Group'):
192proplist = []
193for pn in prop:
194proplist.append(varname(pn))
195
196add_body_line(objname + '.Group = ' + str(proplist))
197
198elif objectToText(prop, objectlist) is not None:
199if val != defaultval:
200add_body_line(f'{objname}.{propname} = {val}')
201
202
203def objectToText(obj, objectlist=None):
204if objectlist is None:
205objectlist = []
206if obj in objectlist:
207return obj.Label
208
209# how can I test if type(obj) is Base.Quantity ?
210if hasattr(obj, 'Value') and hasattr(obj, 'Unit'):
211return str(obj.Value)
212
213if isinstance(obj, str):
214return f"'{obj}'"
215
216if isinstance(obj, bool):
217return 'True' if obj else 'False'
218
219if isinstance(obj, int):
220return str(obj)
221
222if isinstance(obj, float):
223return floatstr(obj)
224
225if isinstance(obj, Placement):
226return f'Placement({objectToText(obj.Base)}, {objectToText(obj.Rotation)})'
227
228if isinstance(obj, Part.Point):
229return f'Part.Point(Vector({floatstr(obj.X)}, {floatstr(obj.Y)}, {floatstr(obj.Z)}))'
230
231if isinstance(obj, Part.LineSegment):
232return f'Part.LineSegment({obj.StartPoint}, {obj.EndPoint})'
233
234if isinstance(obj, Part.Circle):
235return f'Part.Circle({vecstr(obj.Center)}, {obj.Axis}, {floatstr(obj.Radius)})'
236
237if isinstance(obj, Part.ArcOfCircle):
238return f'Part.ArcOfCircle({objectToText(obj.Circle)}, {obj.FirstParameter}, {obj.LastParameter})'
239
240if isinstance(obj, Part.Ellipse):
241return f'Part.Ellipse({vecstr(obj.Center)}, {obj.MajorRadius}, {obj.MinorRadius})'
242
243if isinstance(obj, Part.BSplineCurve):
244poles = ''
245komma = False
246for p in obj.getPoles():
247if komma:
248poles += ', '
249poles += vecstr(p)
250komma = True
251
252return f'Part.BSplineCurve([{poles}])'
253
254if isinstance(obj, Part.Parabola):
255return f'Part.Parabola({vecstr(obj.Focus)}, {vecstr(obj.Location)}, {vecstr(obj.Axis)})'
256
257if isinstance(obj, Part.Hyperbola):
258return f'Part.Hyperbola({vecstr(obj.Center)}, {obj.MajorRadius}, {obj.MinorRadius})'
259
260if isinstance(obj, Part.ArcOfEllipse):
261return f'Part.ArcOfEllipse({objectToText(obj.Ellipse)}, {obj.FirstParameter}, {obj.LastParameter})'
262
263if isinstance(obj, Part.ArcOfParabola):
264return f'Part.ArcOfParabola({objectToText(obj.Parabola)}, {obj.FirstParameter}, {obj.LastParameter})'
265
266if isinstance(obj, Part.ArcOfHyperbola):
267return f'Part.ArcOfHyperbola({objectToText(obj.Hyperbola)}, {obj.FirstParameter}, {obj.LastParameter})'
268
269if isinstance(obj, Vector):
270return vecstr(obj)
271
272liststart = ''
273if isinstance(obj, list):
274liststart = '['
275listend = ']'
276
277if isinstance(obj, tuple):
278liststart = '('
279listend = ')'
280
281if liststart != '':
282sline = liststart
283comma = False
284for listele in obj:
285if comma:
286sline = sline + ', '
287else:
288comma = True
289
290val = objectToText(listele, objectlist)
291if val is not None:
292sline = sline + val
293
294sline += listend
295return sline
296
297stringobj = str(obj)
298return stringobj
299
300
301def addSketch(obj, objname):
302prop = obj.getPropertyByName('Geometry')
303gflist = obj.GeometryFacadeList
304
305n = 0
306exposing = 0
307exposed = []
308for geo in prop:
309objstr = objectToText(geo)
310
311if exposing > 0:
312exposed.append(n)
313exposing = exposing - 1
314else:
315add_body_line(f'geo{n} = {objname}.addGeometry({objstr})')
316if gflist[n].Construction:
317add_body_line(f'{objname}.toggleConstruction(geo{n})')
318
319if isinstance(geo, Part.BSplineCurve):
320poles = len(geo.getPoles())
321exposing = poles - 2
322if exposing < 2:
323exposing = 2
324
325if isinstance(geo, Part.ArcOfParabola):
326exposing = 2
327
328if isinstance(geo, Part.ArcOfHyperbola):
329exposing = 3
330
331if isinstance(geo, Part.ArcOfEllipse):
332exposing = 4
333
334n = n + 1
335
336splinecount = 0
337concount = 0
338prop = obj.getPropertyByName('Constraints')
339for con in obj.Constraints:
340conargs = con.Value
341contype = con.Type
342if con.First < 0:
343first = str(con.First)
344else:
345first = f'geo{con.First}'
346
347if con.Second < 0:
348sec = str(con.Second)
349else:
350sec = f'geo{con.Second}'
351
352if con.Third < 0:
353third = str(con.Third)
354else:
355third = f'geo{con.Third}'
356
357if con.Type == 'Coincident':
358conargs = f'{first}, {con.FirstPos}, {sec}, {con.SecondPos}'
359elif con.Type == 'PointOnObject':
360conargs = f'{first}, {con.FirstPos}, {sec}'
361
362elif con.Type == 'Vertical':
363if sec[:3] == 'geo':
364# Two points.
365conargs = f'{first}, {con.FirstPos}, {sec}, {con.SecondPos}'
366else:
367# Line.
368conargs = str(first)
369
370elif con.Type == 'Horizontal':
371if sec[:3] == 'geo':
372# Two points.
373conargs = f'{first}, {con.FirstPos}, {sec}, {con.SecondPos}'
374else:
375# Line.
376conargs = str(first)
377elif con.Type == 'Parallel':
378conargs = f'{first}, {sec}'
379
380elif con.Type == 'PerpendicularViaPoint':
381conargs = f'{first}, {sec}, {third}, {con.ThirdPos}'
382elif con.Type == 'Perpendicular':
383if con.FirstPos == 0:
384conargs = f'{first}, {sec}'
385elif third[:3] == 'geo':
386contype = 'PerpendicularViaPoint' # compatiblity to FreeCAD 2.20
387conargs = f'{first}, {sec}, {third}, {con.ThirdPos}'
388elif con.SecondPos == 0:
389conargs = f'{first}, {con.FirstPos}, {sec}'
390else:
391conargs = f'{first}, {con.FirstPos}, {sec}, {con.SecondPos}'
392
393elif con.Type == 'TangentViaPoint':
394conargs = f'{first}, {sec}, {third}, {con.ThirdPos}'
395elif con.Type == 'Tangent':
396if con.Second < 0:
397conargs = f'{first}, {con.FirstPos}'
398elif third[:3] == 'geo':
399contype = 'TangentViaPoint' # compatiblity to FreeCAD 2.20
400conargs = f'{first}, {sec}, {third}, {con.ThirdPos}'
401elif con.SecondPos == 0:
402conargs = f'{first}, {con.FirstPos}, {sec}'
403else:
404conargs = f'{first}, {con.FirstPos}, {sec}, {con.SecondPos}'
405
406elif con.Type == 'Equal':
407conargs = f'{first}, {sec}'
408
409elif con.Type == 'Symmetric':
410conargs = f'{first}, {con.FirstPos}, {sec}, {
411con.SecondPos}, {third}, {con.ThirdPos}'
412elif con.Type == 'Block':
413conargs = str(first)
414
415elif con.Type == 'Distance':
416if sec[:3] != 'geo':
417conargs = f'{first}, {floatstr(con.Value)}'
418elif con.FirstPos == 0:
419conargs = f'{first}, {sec}, {floatstr(con.Value)}'
420elif con.SecondPos == 0:
421conargs = f'{first}, {con.FirstPos}, {
422sec}, {floatstr(con.Value)}'
423else:
424conargs = f'{first}, {con.FirstPos}, {sec}, {
425con.SecondPos}, {floatstr(con.Value)}'
426elif con.Type == 'DistanceX' or con.Type == 'DistanceY':
427if sec[:3] != 'geo':
428conargs = f'{first}, {con.FirstPos}, {floatstr(con.Value)}'
429else:
430conargs = f'{first}, {con.FirstPos}, {sec}, {
431con.SecondPos}, {floatstr(con.Value)}'
432
433elif con.Type == 'AngleViaPoint':
434conargs = f'{first}, {sec}, {third}, {floatstr(con.Value)}'
435elif con.Type == 'Angle':
436if con.FirstPos == 0:
437conargs = f'{first}, {sec}, {floatstr(con.Value)}'
438elif third[:3] == 'geo':
439contype = 'AngleViaPoint' # compatiblity to FreeCAD 2.20
440conargs = f'{first}, {sec}, {third}, {floatstr(con.Value)}'
441else:
442conargs = f'{first}, {con.FirstPos}, {sec}, {
443con.SecondPos}, {floatstr(con.Value)}'
444
445elif con.Type == 'Weight' or con.Type == 'Radius' or con.Type == 'Diameter':
446conargs = f'{first}, {floatstr(con.Value)}'
447splinecount = 0
448
449elif con.Type == 'InternalAlignment':
450if con.Second > con.First:
451contype = 'InternalAlignment:Sketcher::BSplineControlPoint'
452conargs = f'{first}, {con.FirstPos}, {sec}, {splinecount}'
453splineindex = con.Second
454splinecount = splinecount + 1
455else:
456conargs = None
457
458if conargs is not None:
459add_body_line(
460f"{objname}.addConstraint(Sketcher.Constraint('{contype}', {conargs}))")
461
462if con.Name != "":
463add_body_line(f"{objname}.renameConstraint({
464concount}, '{con.Name}')")
465
466concount = concount + 1
467
468
469def addSpreadsheet(obj, objname):
470for propname in obj.PropertiesList:
471match = re.search('[A-Z]+[0-9]+', propname)
472if match:
473prop = obj.getPropertyByName(propname)
474val = objectToText(prop)
475add_body_line("{}.set('{}', {})".format(
476objname, propname, val.replace("'", '')))
477alias = obj.getAlias(propname)
478if alias is not None:
479add_body_line(f"{objname}.setAlias('{propname}', '{alias}')")
480
481add_body_line('doc.recompute()')
482
483
484def floatstr(f):
485# return '{:.2f}'.format(f)
486return str(f)
487
488
489def vecstr(v):
490return f'Vector({floatstr(v.x)}, {floatstr(v.y)}, {floatstr(v.z)})'
491
492
493class O2PDialog:
494"""Show a dialog for ObjectsToPython"""
495
496def __init__(self):
497macro_dir = app.getUserMacroDir(True)
498self.ui_file = os.path.join(
499macro_dir, 'ObjectsToPython/ObjectsToPython.ui')
500self.form = gui.PySideUic.loadUi(self.ui_file)
501self.form.pushButtonClose.pressed.connect(self.close_callback)
502self.form.show()
503
504def close_callback(self):
505self.form.close()
506
507
508dialog = O2PDialog()
509exportObjectsToPython()
510