FreeCAD-macros
260 строк · 9.9 Кб
1# -*- coding: utf-8 -*-
2
3# compatible to Python 3
4
5import os
6import FreeCAD as app
7import FreeCADGui as gui
8from FreeCAD import Vector, Rotation
9import Part
10import math
11
12
13iconPath = os.path.dirname(__file__)
14
15epsilon = 1e-7
16
17class LasercutterTechdrawExportItem:
18def __init__(self,
19fp, # an instance of Part::FeaturePython
20Part = None,
21BeamWidth = 0.2,
22Normal = Vector(0, 0, 0),
23method = 'auto'):
24self.updating = False
25fp.addProperty('App::PropertyLink', 'Part', 'LasercutterTechdrawExport', 'Selected part').Part = Part
26fp.addProperty('App::PropertyVector', 'Normal', 'LasercutterTechdrawExport', 'vertical vector. (0, 0, 0) = rotate the part that it fits best').Normal = Normal
27fp.addProperty('App::PropertyFloat', 'BeamWidth', 'LasercutterTechdrawExport', 'Laser beam width in mm').BeamWidth = BeamWidth
28fp.addProperty('App::PropertyEnumeration', 'Method', 'LasercutterTechdrawExport', 'How to create the outline').Method = ['auto', '2D', '3D', 'face', 'normal']
29fp.Method = method
30fp.Proxy = self
31
32def execute(self, fp):
33'''Do something when doing a recomputation, this method is mandatory'''
34if fp.Part and fp.Normal and (not self.updating):
35self.make_outline(fp)
36
37def onChanged(self, fp, prop):
38'''Do something when a property has changed'''
39props = ['Part', 'BeamWidth', 'Normal', 'Method']
40if prop in props:
41self.execute(fp)
42
43def make_outline(self, fp):
44self.updating = True
45
46if fp.Method == 'normal':
47outline = fp.Part.Shape.makeOffsetShape(fp.BeamWidth / 2, 1e-7)
48elif fp.Method == '2D':
49outline = fp.Part.Shape.makeOffset2D(fp.BeamWidth / 2)
50fp.Normal = self.getNormal(fp.Part)
51elif fp.Method == '3D':
52outline = fp.Part.Shape.makeOffsetShape(fp.BeamWidth / 2, 1e-7)
53fp.Normal = self.getNormal(fp.Part)
54else:
55face = self.get_biggest_face(fp.Part)
56if face:
57outline = face.makeOffset2D(fp.BeamWidth / 2)
58fp.Normal = face.normalAt(0, 0)
59elif fp.Method == 'auto':
60try:
61outline = fp.Part.Shape.makeOffset2D(fp.BeamWidth / 2)
62except Exception as ex:
63outline = fp.Part.Shape.makeOffsetShape(fp.BeamWidth / 2, 1e-7)
64
65fp.Normal = self.getNormal(fp.Part)
66
67fp.Shape = Part.Compound(outline.Wires);
68fp.Label = fp.Part.Label + ' offset'
69fp.Placement = outline.Placement
70
71if fp.Placement.Rotation.Axis.z < 0:
72fp.Placement.Rotation.Axis = fp.Placement.Rotation.Axis * -1
73
74if fp.Method != 'normal':
75if fp.Normal.z < 0:
76fp.Normal = fp.Normal * -1
77
78rotation_to_apply = Rotation(fp.Normal, Vector(0, 0, 1))
79new_rotation = rotation_to_apply.multiply(fp.Placement.Rotation)
80fp.Placement.Rotation = new_rotation
81
82self.rotate_biggest_side_up(fp)
83
84self.updating = False
85
86def get_biggest_face(self, part):
87max_area = 0
88max_face = None
89for face in part.Shape.Faces:
90if face and face.Area > max_area:
91max_area = face.Area
92max_face = face
93
94if max_face:
95return max_face
96
97def rotate_biggest_side_up(self, fp):
98bbox = fp.Shape.optimalBoundingBox()
99xmin = bbox.XLength
100angle = 0.0
101r = fp.Placement.Rotation
102r_best = r
103step = 180 / 16
104while angle + step < 180:
105angle = angle + step
106rotation_to_apply = Rotation()
107rotation_to_apply.Axis = Vector(0, 0, 1)
108rotation_to_apply.Angle = math.radians(angle)
109fp.Placement.Rotation = rotation_to_apply.multiply(r)
110bbox = fp.Shape.optimalBoundingBox()
111
112if xmin > bbox.XLength:
113xmin = bbox.XLength
114r_best = fp.Placement.Rotation
115
116fp.Placement.Rotation = r_best
117
118def getNormal(self, obj):
119if hasattr(obj, 'Dir'):
120return obj.Dir
121else:
122bbox = obj.Shape.BoundBox
123if bbox.XLength < epsilon: return Vector(1.0,0.0,0.0)
124elif bbox.YLength < epsilon: return Vector(0.0,1.0,0.0)
125elif bbox.ZLength < epsilon: return Vector(0.0,0.0,1.0)
126return obj.Placement.Rotation.multVec(Vector(0, 0, 1))
127
128
129class LasercutterTechdrawExportItemViewProvider:
130def __init__(self, vobj):
131'''Set this object to the proxy object of the actual view provider'''
132vobj.Proxy = self
133self.Object = vobj.Object
134
135def getIcon(self):
136'''Return the icon which will FreeCADear in the tree view. This method is optional and if not defined a default icon is shown.'''
137return (os.path.join(iconPath, 'LasercutterTechdrawExport.svg'))
138
139def attach(self, vobj):
140'''Setup the scene sub-graph of the view provider, this method is mandatory'''
141self.Object = vobj.Object
142self.onChanged(vobj, 'Base')
143
144def updateData(self, fp, prop):
145'''If a property of the handled feature has changed we have the chance to handle this here'''
146pass
147
148def claimChildren(self):
149'''Return a list of objects that will be modified by this feature'''
150pass
151
152def onDelete(self, feature, subelements):
153'''Here we can do something when the feature will be deleted'''
154return True
155
156def onChanged(self, fp, prop):
157'''Here we can do something when a single property got changed'''
158pass
159
160def setEdit(self, vobj=None, mode=0):
161return False
162
163def __getstate__(self):
164'''When saving the document this object gets stored using Python's json module.\
165Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
166to return a tuple of all serializable objects or None.'''
167return None
168
169def __setstate__(self,state):
170'''When restoring the serialized object from document we have the chance to set some internals here.\
171Since no data were serialized nothing needs to be done here.'''
172return None
173
174
175
176def selected_to_techdraw(doc, offsets, techdraw, BeamWidth):
177x = BeamWidth
178y = 0
179
180for offset in offsets:
181viewname = offset.Label.replace('offset', 'contour')
182views = doc.getObjectsByLabel(viewname)
183if len(views) > 0:
184view = views[0]
185else:
186view = doc.addObject('TechDraw::DrawViewPart', viewname)
187techdraw.addView(view)
188
189try:
190view.CoarseView = False
191view.ViewObject.LineWidth = BeamWidth
192view.Source = offset
193view.Direction = Vector(0, 0, 1)
194view.ScaleType = u'Custom'
195view.Scale = 1.00
196except Exception as ex:
197app.Console.PrintError('\nview for ' + viewname + ' cannot be created ! ')
198app.Console.PrintError(ex)
199
200for view in techdraw.Views:
201offset = view.Source[0]
202bbox = offset.Shape.BoundBox
203bsize = Vector(bbox.XLength, bbox.YLength, bbox.ZLength)
204
205# add a 2D view to the TechDraw page right of the last part
206maxheight = y + bsize.y + BeamWidth
207if maxheight > techdraw.Template.Height:
208techdraw.Template.Height = maxheight
209
210maxwidth = x + bsize.x + BeamWidth
211if maxwidth > techdraw.Template.Width:
212techdraw.Template.Width = maxwidth
213
214view.X = x + bsize.x / 2
215view.Y = y + bsize.y - (bsize.y / 2)
216x = x + bsize.x + BeamWidth
217
218def makeLasercutterTechdrawExport(parts, BeamWidth = 0.2, doc = app.activeDocument(), method = 'auto', normal = Vector(0, 0, 0)):
219if len(parts) == 0: return
220
221techdraw = doc.addObject('TechDraw::DrawPage','LasercutterTechdraw')
222template = doc.addObject('TechDraw::DrawSVGTemplate','Template')
223techdraw.Template = template
224doc.recompute()
225
226for p in parts:
227if len(p.Shape.Solids) > 1:
228for sol in p.Shape.Solids:
229sfp = doc.addObject('Part::Feature', p.Label)
230sfp.Shape = Part.Shape(sol)
231sfp.ViewObject.hide()
232addToExportObjects(doc, sfp)
233addLasercutterTechdrawItem(techdraw, sfp, BeamWidth, doc, method, normal)
234
235else:
236addLasercutterTechdrawItem(techdraw, p, BeamWidth, doc, method, normal)
237
238doc.recompute()
239techdraw.ViewObject.show()
240return techdraw
241
242
243def addLasercutterTechdrawItem(techdraw, part, BeamWidth = 0.2, doc = app.activeDocument(), method = 'auto', normal = Vector(0, 0, 0)):
244ifp = doc.addObject('Part::FeaturePython', 'LasercutterTechdrawExport')
245LasercutterTechdrawExportItem(ifp, part, BeamWidth, method=method, Normal=normal)
246LasercutterTechdrawExportItemViewProvider(ifp.ViewObject)
247doc.recompute()
248selected_to_techdraw(doc, [ifp], techdraw, BeamWidth)
249addToExportObjects(doc, ifp)
250return ifp
251
252def addToExportObjects(doc, ifp):
253LaserCutterExportObjects = doc.getObjectsByLabel('LaserCutterExportObjects')
254if len(LaserCutterExportObjects) == 0:
255LaserCutterExportObjects = doc.addObject('App::DocumentObjectGroup', 'LaserCutterExportObjects')
256else:
257LaserCutterExportObjects = LaserCutterExportObjects[0]
258
259LaserCutterExportObjects.Group = LaserCutterExportObjects.Group + [ifp]
260LaserCutterExportObjects.ViewObject.hide()
261