-
Notifications
You must be signed in to change notification settings - Fork 9
/
lattice2SubLink.py
353 lines (297 loc) · 15.3 KB
/
lattice2SubLink.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
#***************************************************************************
#* *
#* Copyright (c) 2016 - 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__= "Lattice SubLink feature for FreeCAD"
__author__ = "DeepSOIC"
__doc__ = "Lattice SubLink is like Draft Facebinder, but for edges and vertices too."
from lattice2Common import *
from lattice2BaseFeature import isObjectLattice, assureProperty #assureProperty(self, selfobj, proptype, propname, defvalue, group, tooltip)
import lattice2Markers as markers
import FreeCAD as App
import lattice2ShapeCopy as ShapeCopy
import lattice2Subsequencer as LSS
from lattice2Utils import sublinkFromApart, syncSublinkApart
# -------------------------- feature --------------------------------------------------
def makeSubLink(name):
'''makeSubLink(name): makes a SubLink object.'''
obj = App.ActiveDocument.addObject("Part::FeaturePython",name)
LatticeSubLink(obj)
if FreeCAD.GuiUp:
ViewProviderSubLink(obj.ViewObject)
return obj
class LatticeSubLink:
"The Lattice SubLink object"
def __init__(self,obj):
self.Type = "SubLink"
obj.addProperty("App::PropertyLink","Object","Lattice SubLink","Object to extract an element from")
obj.addProperty("App::PropertyStringList","SubNames","Lattice SubLink", "List of elements to extract. Example: Edge5,Edge8")
obj.Proxy = self
self.assureProperties(obj)
def assureProperties(self, selfobj):
assureProperty(selfobj, "App::PropertyEnumeration","Looping", ["Single"] + LSS.LOOP_MODES, "Lattice SubLink", "Sets whether to collect just the element, or all similar from array.")
assureProperty(selfobj, "App::PropertyEnumeration","CompoundTraversal", LSS.TRAVERSAL_MODES, "Lattice SubLink", "Sets how to unpack compounds if Looping is not 'Single'.")
assureProperty(selfobj, "App::PropertyLinkSub", "SubLink", sublinkFromApart(screen(selfobj.Object), selfobj.SubNames), "Lattice SubLink", "Mirror of Object+SubNames properties")
def execute(self,selfobj):
self.assureProperties(selfobj)
#validity check
if isObjectLattice(screen(selfobj.Object)):
import lattice2Executer
lattice2Executer.warning(selfobj,"A generic shape is expected, but a placement/array was supplied. It will be treated as a generic shape.")
lnkobj = screen(selfobj.Object)
sh = lnkobj.Shape
# subsequencing
full_link = (lnkobj, selfobj.SubNames)
if selfobj.Looping == 'Single':
lnkseq = [full_link]
else:
lnkseq = LSS.Subsequence_auto(full_link, selfobj.CompoundTraversal, selfobj.Looping )
# main code
seq_packs = [] #pack = single item of subsequence. Pack contains list of elements that were selected.
shape_count = 0
for lnk in lnkseq: # loop over subsequence (if Looping == 'Single', this loop will only loop once)
# extract the pack
assert(lnk[0] is lnkobj) # all links should point to elements of one object anyway
subnames = lnk[1]
pack = [] #acculumator, to eventually become a compound of shapes for this subsequence item
for subname in subnames:
subname = subname.strip()
if len(subname)==0:
raise ValueError("Empty subname! Not allowed.")
if 'Face' in subname: # manual handling of standard cases, because support for negative indexing is needed
index = int(subname.replace('Face',''))-1
pack.append(sh.Faces[index])
elif 'Edge' in subname:
index = int(subname.replace('Edge',''))-1
pack.append(sh.Edges[index])
elif 'Vertex' in subname:
index = int(subname.replace('Vertex',''))-1
pack.append(sh.Vertexes[index])
else: #fail-safe. non-standard sublink.
import lattice2Executer
lattice2Executer.warning(selfobj,"Unexpected subelement name: "+subname+". Trying to extract it with .Shape.getElement()...")
pack.append(sh.getElement(subname))
shape_count += len(pack)
# convert list into compound
if len(pack) == 1:
pack = ShapeCopy.transformCopy(pack[0])
else:
pack = Part.makeCompound(pack)
# accumulate
seq_packs.append(pack)
# convert list into compound
if len(seq_packs) == 1:
seq_packs = seq_packs[0]
else:
seq_packs = Part.makeCompound(seq_packs)
if shape_count == 0:
# no shapes collected, FAIL!
scale = 1.0
try:
if screen(selfobj.Object):
scale = screen(selfobj.Object).Shape.BoundBox.DiagonalLength/math.sqrt(3)
except Exception as err:
App.Console.PrintError(selfobj.Name+": Failed to estimate size of marker shape")
if scale < DistConfusion * 100:
scale = 1.0
selfobj.Shape = markers.getNullShapeShape(scale)
raise ValueError('Nothing is linked, apparently!') #Feeding empty compounds to FreeCAD seems to cause rendering issues, otherwise it would have been a good idea to output nothing.
# done!
selfobj.Shape = seq_packs
def onChanged(self, selfobj, prop): #prop is a string - name of the property
# synchronize SubLink and Object+SubNames properties
syncSublinkApart(selfobj, prop, 'SubLink', 'Object', 'SubNames')
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def dumps(self):
return None
def loads(self,state):
return None
class ViewProviderSubLink:
"A View Provider for the SubLink object"
def __init__(self,vobj):
vobj.Proxy = self
def getIcon(self):
ret = ""
if len(self.Object.SubNames) == 1:
subname = self.Object.SubNames[0]
if 'Face' in subname:
ret = getIconPath("Lattice2_SubLink_Face.svg")
elif 'Edge' in subname:
ret = getIconPath("Lattice2_SubLink_Edge.svg")
elif 'Vertex' in subname:
ret = getIconPath("Lattice2_SubLink_Vertex.svg")
if len(ret) == 0:
ret = getIconPath("Lattice2_SubLink.svg")
if hasattr(self.Object,'Looping') and self.Object.Looping != 'Single':
ret = ret.replace("SubLink","SubLinkSubsequence")
return ret
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):
return []
def CreateSubLink(object, subnames, looping = 'Single'):
#stabilize links
subnames = list(subnames) #'tuple' object does not support item assignment; SubElementNames of SelectionObject is a tuple
try:
cnt_faces = 0
cnt_edges = 0
cnt_vertexes = 0
cnt_somethingelse = 0
n_faces = None #vars to receive counts of respective subelements in the shape of object. Not prefilling them, for speed - filled only as needed
n_edges = None
n_vertexes = None
for i in range(len(subnames)):
subname = subnames[i].strip()
if 'Face' in subname:
index = int(subname.replace('Face',''))
if n_faces is None:
n_faces = len(object.Shape.Faces)
if (index-1)*2 > n_faces:
index = index - n_faces
subname = "Face"+str(index)
cnt_faces += 1
elif 'Edge' in subname:
index = int(subname.replace('Edge',''))
if n_edges is None:
n_edges = len(object.Shape.Edges)
if (index-1)*2 > n_edges:
index = index - n_edges
subname = "Edge"+str(index)
cnt_edges += 1
elif 'Vertex' in subname:
index = int(subname.replace('Vertex',''))
if n_vertexes is None:
n_vertexes = len(object.Shape.Vertexes)
if (index-1)*2 > n_vertexes:
index = index - n_vertexes
subname = "Vertex"+str(index)
cnt_vertexes += 1
else:
cnt_somethingelse += 1
pass #something unexpected, pass through unchanged
subnames[i] = subname
except Exception:
pass
FreeCADGui.addModule("lattice2SubLink")
FreeCADGui.addModule("lattice2Executer")
name = object.Name+"_"+subnames[0] if len(subnames)==1 else "SubLink"
if looping != 'Single':
name = object.Name+"_"+"Elements"
FreeCADGui.doCommand("f = lattice2SubLink.makeSubLink(name = "+repr(name)+")")
label = (subnames[0] if len(subnames)==1 else "subelements") + u" of " + object.Label
FreeCADGui.doCommand("f.Label = "+repr(label))
FreeCADGui.doCommand("f.Object = App.ActiveDocument."+object.Name)
FreeCADGui.doCommand("f.SubNames = "+repr(subnames))
FreeCADGui.doCommand("f.Looping = "+repr(looping))
FreeCADGui.doCommand("lattice2Executer.executeFeature(f)")
if cnt_vertexes > 0 and cnt_faces+cnt_edges+cnt_somethingelse == 0: #only vertices selected - make them bigger to make them visible
FreeCADGui.doCommand("f.ViewObject.PointSize = 10")
FreeCADGui.doCommand("f.Object.ViewObject.hide()")
FreeCADGui.doCommand("Gui.Selection.addSelection(f)")
FreeCADGui.doCommand("f = None")
return App.ActiveDocument.ActiveObject
def cmdSubLink(looping = 'Single'):
sel = FreeCADGui.Selection.getSelectionEx()
if len(sel) == 0:
raise SelectionError("Bad selection", "Please select some subelements from one object, first.")
if len(sel) > 1:
raise SelectionError("Bad selection", "You have selected subelements from more than one object. Not allowed. You can only select subelements of one object.")
if len(sel[0].SubElementNames)==0:
raise SelectionError("Bad selection", "Please select some subelements, not the whole object.")
App.ActiveDocument.openTransaction("Create SubLink")
CreateSubLink(sel[0].Object,sel[0].SubElementNames, looping)
deselect(sel)
App.ActiveDocument.commitTransaction()
# -------------------------- /common stuff --------------------------------------------------
# -------------------------- Gui command --------------------------------------------------
class CommandSubLink:
"Command to create SubLink feature"
def GetResources(self):
return {'Pixmap' : getIconPath("Lattice2_SubLink.svg"),
'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubLink","SubLink"),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubLink","SubLink: extract individual vertices, edges and faces from shapes")}
def Activated(self):
try:
if len(FreeCADGui.Selection.getSelection())==0:
infoMessage("SubLink",
"'SubLink' command. Extracts selected faces, edges or vertices from the object.\n\n"+
"Please select subelements of one object, then invoke the command.")
return
cmdSubLink()
except Exception as err:
msgError(err)
def IsActive(self):
if App.ActiveDocument:
return True
else:
return False
class CommandSublinkSubsequence:
"Command to create SubLink Subsequence feature"
def GetResources(self):
return {'Pixmap' : getIconPath("Lattice2_SubLinkSubsequence.svg"),
'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubLink","Subsequence"),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubLink","Subsequence: extract individual vertices, edges and faces from shapes, from each instance in an array.")}
def Activated(self):
try:
if len(FreeCADGui.Selection.getSelection())==0:
infoMessage("SubLink",
"'Subsequence' command. Extracts all faces/edges/vertexes similar to those selected, from an array of shapes.\n\n"+
"Please select one or more subelements of one array (compound), then invoke the command.")
return
cmdSubLink(looping= 'All around')
except Exception as err:
msgError(err)
def IsActive(self):
if App.ActiveDocument:
return True
else:
return False
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Lattice2_SubLink', CommandSubLink())
FreeCADGui.addCommand('Lattice2_SublinkSubsequence', CommandSublinkSubsequence())
class CommandSublinkGroup:
def GetCommands(self):
return ("Lattice2_SubLink","Lattice2_SublinkSubsequence")
def GetDefaultCommand(self): # return the index of the tuple of the default command.
return 0
def GetResources(self):
return { 'MenuText': 'Sublink:',
'ToolTip': 'Sublink (group): extract elements from shapes.'}
def IsActive(self): # optional
return App.ActiveDocument is not None and activeBody() is None
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Lattice2_Sublink_GroupCommand',CommandSublinkGroup())
exportedCommands = ['Lattice2_Sublink_GroupCommand']
# -------------------------- /Gui command --------------------------------------------------