-
Notifications
You must be signed in to change notification settings - Fork 9
/
lattice2BaseFeature.py
398 lines (326 loc) · 17.7 KB
/
lattice2BaseFeature.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#***************************************************************************
#* *
#* Copyright (c) 2015 - Victor Titov (DeepSOIC) *
#* <[email protected]> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
__title__="Base feature module for lattice object of lattice workbench for FreeCAD"
__author__ = "DeepSOIC"
__url__ = ""
import FreeCAD as App
import Part
from lattice2Common import *
import lattice2CompoundExplorer as LCE
import lattice2Markers
import lattice2Executer
from lattice2ShapeCopy import shallowCopy
def getDefLatticeFaceColor():
return (1.0, 0.7019608020782471, 0.0, 0.0) #orange
def getDefShapeColor():
clr = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned("DefaultShapeColor")
#convert color in int to color in tuple of 4 floats.
#This is probably implemented already somewhere, but I couldn't find, so I rolled my own --DeepSOIC
# clr in hex looks like this: 0xRRGGBBOO (r,g,b,o = red, green, blue, opacity)
o = clr & 0x000000FF
b = (clr >> 8) & 0x000000FF
g = (clr >> 16) & 0x000000FF
r = (clr >> 24) & 0x000000FF
return (r/255.0, g/255.0, b/255.0, (255-o)/255.0)
def makeLatticeFeature(name, AppClass, ViewClass, no_body = False, no_disable_attacher = False):
'''makeLatticeFeature(name, AppClass, ViewClass, no_body = False): makes a document object for a LatticeFeature-derived object.
no_body: if False, the Lattice object will end up in an active body, and Part2DObject will be used.
no_disable_attacher: if True, attachment properties of Part2DObject won't be hidden'''
body = activeBody()
if body and not no_body:
obj = body.newObject("Part::Part2DObjectPython",name) #hack: body accepts any 2dobjectpython, thinking it is a sketch. Use it to get into body. This does cause some weirdness (e.g. one can Pad a placement), but that is rather minor.
obj.AttacherType = 'Attacher::AttachEngine3D'
else:
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
AppClass(obj)
if FreeCAD.GuiUp:
if ViewClass:
vp = ViewClass(obj.ViewObject)
else:
vp = ViewProviderLatticeFeature(obj.ViewObject)
return obj
def isObjectLattice(documentObject):
'''isObjectLattice(documentObject): When operating on the object, it is to be treated as a lattice object. If False, treat as a regular shape.'''
ret = False
if hasattr(documentObject,"isLattice"):
if 'On' in documentObject.isLattice:
ret = True
if documentObject.isDerivedFrom('App::Placement') or documentObject.isDerivedFrom('PartDesign::CoordinateSystem'):
ret = True
if documentObject.isDerivedFrom('PartDesign::ShapeBinder'):
if len(documentObject.Support) == 1 and documentObject.Support[0][1] == ('',):
ret = isObjectLattice(documentObject.Support[0][0])
if hasattr(documentObject, 'IAm') and documentObject.IAm == 'PartOMagic.Ghost':
ret = isObjectLattice(documentObject.Base)
return ret
def getMarkerSizeEstimate(ListOfPlacements, feature = None):
'''getMarkerSizeEstimate(ListOfPlacements, feature = None): computes the default marker size for the array of placements.
If feature is provided, marker size property will be assigned to viewer-based autosize if size based on array content is zero.'''
if len(ListOfPlacements) == 0:
return 1.0
pathLength = 0
for i in range(1, len(ListOfPlacements)):
pathLength += (ListOfPlacements[i].Base - ListOfPlacements[i-1].Base).Length
sz = pathLength/len(ListOfPlacements)/2.0
#FIXME: make hierarchy-aware
if sz < DistConfusion*10:
from lattice2Base import Autosize
sz = Autosize.convenientMarkerSize()
if feature.MarkerSize == 0:
feature.MarkerSize = sz
return sz
class LatticeFeature(object):
"Base object for lattice objects (arrays of placements)"
def __init__(self,obj):
# please, don't override. Override derivedInit instead.
obj.addProperty('App::PropertyString', 'Type', "Lattice", "module_name.class_name of this object, for proxy recovery", 0, True, True)
obj.Type = self.__module__ + '.' + type(self).__name__
prop = "NumElements"
obj.addProperty("App::PropertyInteger",prop,"Lattice","Info: number of placements in the array")
obj.setEditorMode(prop, 1) # set read-only
obj.addProperty("App::PropertyLength","MarkerSize","Lattice","Size of placement markers (set to zero for automatic).")
obj.addProperty("App::PropertyEnumeration","MarkerShape","Lattice","Choose the preferred shape of placement markers.")
obj.MarkerShape = ["tetra-orimarker","paperplane-orimarker"]
obj.MarkerShape = "paperplane-orimarker" #TODO: setting for choosing the default
obj.addProperty("App::PropertyEnumeration","isLattice","Lattice","Sets whether this object should be treated as a lattice by further operations")
obj.isLattice = ['Auto-Off','Auto-On','Force-Off','Force-On']
# Auto-On an Auto-Off can be modified when recomputing. Force values are going to stay.
prop = "ExposePlacement"
obj.addProperty("App::PropertyBool",prop,"Lattice","Makes the placement synchronized to Placement property. This will often make this object unmovable. Not applicable to arrays.")
self.derivedInit(obj)
obj.Proxy = self
def assureProperty(self, selfobj, proptype, propname, defvalue, group, tooltip):
"""assureProperty(selfobj, proptype, propname, defvalue, group, tooltip): adds
a property if one is missing, and sets its value to default. Does nothing if property
already exists. Returns True if property was created, or False if not."""
return assureProperty(selfobj, proptype, propname, defvalue, group, tooltip)
def derivedInit(self, obj):
'''for overriding by derived classes'''
pass
def execute(self,obj):
# please, don't override. Override derivedExecute instead.
plms = self.derivedExecute(obj)
if plms is not None:
if plms == "suppress":
return
obj.NumElements = len(plms)
shapes = []
markerSize = obj.MarkerSize
if markerSize < DistConfusion:
markerSize = getMarkerSizeEstimate(plms, obj)
marker = lattice2Markers.getPlacementMarker(scale= markerSize, markerID= obj.MarkerShape)
bExposing = False
if obj.ExposePlacement:
if len(plms) == 1:
bExposing = True
else:
lattice2Executer.warning(obj,"Multiple placements are being fed, can't expose placements. Placement property will be forced to zero.")
obj.Placement = App.Placement()
if bExposing:
obj.Shape = shallowCopy(marker)
obj.Placement = plms[0]
else:
for plm in plms:
sh = shallowCopy(marker)
sh.Placement = plm
shapes.append(sh)
if len(shapes) == 0:
obj.Shape = lattice2Markers.getNullShapeShape(markerSize)
raise ValueError('Lattice object is null')
sh = Part.makeCompound(shapes)
sh.Placement = obj.Placement
obj.Shape = sh
if obj.isLattice == 'Auto-Off':
obj.isLattice = 'Auto-On'
else:
# DerivedExecute didn't return anything. Thus we assume it
# has assigned the shape, and thus we don't do anything.
# Moreover, we assume that it is no longer a lattice object, so:
if obj.isLattice == 'Auto-On':
obj.isLattice = 'Auto-Off'
if obj.ExposePlacement:
if obj.Shape.ShapeType == "Compound":
children = obj.Shape.childShapes()
if len(children) == 1:
obj.Placement = children[0].Placement
obj.Shape = children[0]
else:
obj.Placement = App.Placement()
else:
#nothing to do - FreeCAD will take care to make obj.Placement and obj.Shape.Placement synchronized.
pass
return
def derivedExecute(self,obj):
'''For overriding by derived class. If this returns a list of placements,
it's going to be used to build the shape. If returns None, it is assumed that
derivedExecute has already assigned the shape, and no further actions are needed.
Moreover, None is a signal that the object is not a lattice array, and it will
morph into a non-lattice if isLattice is set to auto'''
return []
def verifyIntegrity(self):
try:
if self.__init__.__func__ is not LatticeFeature.__init__.__func__:
FreeCAD.Console.PrintError("__init__() of lattice object is overridden. Please don't! Fix it!\n")
if self.execute.__func__ is not LatticeFeature.execute.__func__:
FreeCAD.Console.PrintError("execute() of lattice object is overridden. Please don't! Fix it!\n")
except AttributeError as err:
pass # quick-n-dirty fix for Py3. TODO: restore the functionality in Py3, or remove this routine altogether.
def onChanged(self, obj, prop): #prop is a string - name of the property
if prop == 'isLattice':
if obj.ViewObject is not None:
try:
if isObjectLattice(obj):
#obj.ViewObject.DisplayMode = 'Shaded'
obj.ViewObject.ShapeColor = getDefLatticeFaceColor()
obj.ViewObject.Lighting = 'One side'
else:
#obj.ViewObject.DisplayMode = 'Flat Lines'
obj.ViewObject.ShapeColor = getDefShapeColor()
except App.Base.FreeCADError as err:
#these errors pop up while loading project file, apparently because
# viewprovider is up already, but the shape vis mesh wasn't yet
# created. It is safe to ignore them, as DisplayMode is eventually
# restored to the correct values.
#Proper way of dealing with it would have been by testing for
# isRestoring(??), but I failed to find the way to do it.
#--DeepSOIC
pass
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def dumps(self):
return None
def loads(self,state):
return None
def disableAttacher(self, selfobj, enable= False):
if selfobj.isDerivedFrom('Part::Part2DObject'):
attachprops = [
'MapMode',
'MapReversed',
'MapPathParameter',
'AttachmentOffset',
]
if hasattr(selfobj, 'AttachmentSupport'):
attachprops.append('AttachmentSupport')
elif hasattr(selfobj, 'Support'):
attachprops.append('Support')
else:
raise AttributeError('No support property found')
for prop in attachprops:
selfobj.setEditorMode(prop, 0 if enable else 2)
if enable:
selfobj.MapMode = selfobj.MapMode #trigger attachment, to make it update property states
def onDocumentRestored(self, selfobj):
#override to have attachment!
self.disableAttacher(selfobj)
class ViewProviderLatticeFeature(object):
"A View Provider for base lattice object"
def __init__(self,vobj):
'''Don't override. Override derivedInit, please!'''
vobj.Proxy = self
vobj.addProperty('App::PropertyString', 'Type', "Lattice", "module_name.class_name of this object, for proxy recovery", 0, True, True)
vobj.Type = self.__module__ + '.' + type(self).__name__
prop = "DontUnhideOnDelete"
vobj.addProperty("App::PropertyBool",prop,"Lattice","Makes the element be populated into object's Placement property")
vobj.setEditorMode(prop, 2) # set hidden
self.derivedInit(vobj)
def derivedInit(self,vobj):
pass
def verifyIntegrity(self):
try:
if self.__init__.__func__ is not ViewProviderLatticeFeature.__init__.__func__:
FreeCAD.Console.PrintError("__init__() of lattice object view provider is overridden. Please don't! Fix it!\n")
except AttributeError as err:
pass # quick-n-dirty fix for Py3. TODO: restore the functionality in Py3, or remove this routine altogether.
def getIcon(self):
return getIconPath("Lattice.svg")
def attach(self, vobj):
self.ViewObject = vobj
self.Object = vobj.Object
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def dumps(self):
return None
def loads(self,state):
return None
def claimChildren(self):
self.Object.Proxy.verifyIntegrity()
self.verifyIntegrity()
return []
def onDelete(self, feature, subelements): # subelements is a tuple of strings
try:
if hasattr(self.ViewObject,"DontUnhideOnDelete") and self.ViewObject.DontUnhideOnDelete:
pass
else:
children = self.claimChildren()
if children and len(children) > 0:
marker = lattice2Markers
for child in children:
child.ViewObject.show()
except Exception as err:
# catch all exceptions, because we don't want to prevent deletion if something goes wrong
FreeCAD.Console.PrintError("Error in onDelete: " + str(err))
return True
def assureProperty(docobj, proptype, propname, defvalue, group, tooltip):
"""assureProperty(docobj, proptype, propname, defvalue, group, tooltip): adds
a property if one is missing, and sets its value to default. Does nothing if property
already exists. Returns True if property was created, or False if not."""
if hasattr(docobj, propname):
#todo: check type match
return False
docobj.addProperty(proptype, propname, group, tooltip)
if defvalue is not None:
setattr(docobj, propname, defvalue)
return True
# ----------------------utility functions -------------------------------------
def makeMoveFromTo(plmFrom, plmTo):
'''makeMoveFromTo(plmFrom, plmTo): construct a placement that moves something
from one placement to another placement'''
return plmTo.multiply(plmFrom.inverse())
def getPlacementsList(documentObject, context = None, suppressWarning = False):
'''getPlacementsList(documentObject, context = None): extract list of placements
from an array object. Context is an object to report as context, when displaying
a warning if the documentObject happens to be a non-lattice.'''
if not isObjectLattice(documentObject):
if not suppressWarning:
lattice2Executer.warning(context, documentObject.Name + " is not a placement or an array of placements. Results may be unexpected.")
if documentObject.isDerivedFrom('App::Placement') or documentObject.isDerivedFrom('PartDesign::CoordinateSystem'):
return [documentObject.Placement]
leaves = LCE.AllLeaves(documentObject.Shape)
return [leaf.Placement for leaf in leaves]
def splitSelection(sel):
'''splitSelection(sel): splits sel (use getSelectionEx()) into lattices and non-lattices.
returns a tuple: (lattices, shapes). lattices is a list, containing all objects
that are lattices (placements of arrays of placements). shapes contains all
the rest. The lists contain SelectionObjects, not the actual document objects.'''
lattices = []
shapes = []
for selobj in sel:
if isObjectLattice(selobj.Object):
lattices.append(selobj)
else:
shapes.append(selobj)
return (lattices, shapes)