diff --git a/InitGui.py b/InitGui.py
index 52d17bf..1f6b633 100644
--- a/InitGui.py
+++ b/InitGui.py
@@ -73,7 +73,7 @@ class Lattice2Workbench (Workbench):
+ Lattice2.CompoundFeatures.Slice.exportedCommands
+ Lattice2.CompoundFeatures.BoundBox.exportedCommands
+ Lattice2.CompoundFeatures.ShapeString.exportedCommands
- + Lattice2.CompoundFeatures.ParaSeries.exportedCommands
+ + Lattice2.CompoundFeatures.SeriesGroup.exportedCommands
)
self.appendToolbar('Lattice2CompoundFeatures', cmdsCompoundTools)
self.appendMenu('Lattice2', cmdsCompoundTools)
diff --git a/Lattice2CodeModules.py b/Lattice2CodeModules.py
index 5b73d89..b5007d7 100644
--- a/Lattice2CodeModules.py
+++ b/Lattice2CodeModules.py
@@ -6,4 +6,6 @@ import lattice2GeomUtils as GeomUtils
import lattice2InterpolatorUtil as InterpolatorUtil
import lattice2Markers as Markers
import lattice2ValueSeriesGenerator as ValueSeriesGenerator
-import lattice2ShapeCopy as ShapeCopy
\ No newline at end of file
+import lattice2ShapeCopy as ShapeCopy
+import lattice2Subsequencer as Subsequencer
+import lattice2Utils as Utils
\ No newline at end of file
diff --git a/Lattice2CompoundFeatures.py b/Lattice2CompoundFeatures.py
index 2779cc5..d3f50c1 100644
--- a/Lattice2CompoundFeatures.py
+++ b/Lattice2CompoundFeatures.py
@@ -7,4 +7,6 @@ import lattice2BoundBox as BoundBox
import lattice2ShapeString as ShapeString
import lattice2MakeCompound as MakeCompound
import lattice2ParaSeries as ParaSeries
+import lattice2TopoSeries as TopoSeries
+import lattice2SeriesGroup as SeriesGroup
import lattice2Slice as Slice
\ No newline at end of file
diff --git a/PyResources/icons.qrc b/PyResources/icons.qrc
index 92e8e17..0e93b90 100644
--- a/PyResources/icons.qrc
+++ b/PyResources/icons.qrc
@@ -3,6 +3,7 @@
icons/Lattice2_ArrayFilter.svgicons/Lattice2_ArrayFromShape.svgicons/Lattice2_AttachablePlacement.svg
+ icons/Lattice2_AttachedPlacementSubsequence.svgicons/Lattice2_BoundBox.svgicons/Lattice2_BoundBox_Compound.svgicons/Lattice2_CompoundFilter.svg
@@ -52,6 +53,11 @@
icons/Lattice2_SubLink_Edge.svgicons/Lattice2_SubLink_Face.svgicons/Lattice2_SubLink_Vertex.svg
+ icons/Lattice2_SubLinkSubsequence.svg
+ icons/Lattice2_SubLinkSubsequence_Edge.svg
+ icons/Lattice2_SubLinkSubsequence_Face.svg
+ icons/Lattice2_SubLinkSubsequence_Vertex.svgicons/Lattice2_SubstituteObject.svg
+ icons/Lattice2_TopoSeries.svg
diff --git a/PyResources/icons/Lattice2_AttachedPlacementSubsequence.svg b/PyResources/icons/Lattice2_AttachedPlacementSubsequence.svg
new file mode 100644
index 0000000..4b58386
--- /dev/null
+++ b/PyResources/icons/Lattice2_AttachedPlacementSubsequence.svg
@@ -0,0 +1,251 @@
+
+
+
+
diff --git a/PyResources/icons/Lattice2_SubLinkSubsequence.svg b/PyResources/icons/Lattice2_SubLinkSubsequence.svg
new file mode 100644
index 0000000..72dfe31
--- /dev/null
+++ b/PyResources/icons/Lattice2_SubLinkSubsequence.svg
@@ -0,0 +1,173 @@
+
+
+
+
diff --git a/PyResources/icons/Lattice2_SubLinkSubsequence_Edge.svg b/PyResources/icons/Lattice2_SubLinkSubsequence_Edge.svg
new file mode 100644
index 0000000..67a4e90
--- /dev/null
+++ b/PyResources/icons/Lattice2_SubLinkSubsequence_Edge.svg
@@ -0,0 +1,262 @@
+
+
+
+
diff --git a/PyResources/icons/Lattice2_SubLinkSubsequence_Face.svg b/PyResources/icons/Lattice2_SubLinkSubsequence_Face.svg
new file mode 100644
index 0000000..c5c30fb
--- /dev/null
+++ b/PyResources/icons/Lattice2_SubLinkSubsequence_Face.svg
@@ -0,0 +1,173 @@
+
+
+
+
diff --git a/PyResources/icons/Lattice2_SubLinkSubsequence_Vertex.svg b/PyResources/icons/Lattice2_SubLinkSubsequence_Vertex.svg
new file mode 100644
index 0000000..d621a89
--- /dev/null
+++ b/PyResources/icons/Lattice2_SubLinkSubsequence_Vertex.svg
@@ -0,0 +1,238 @@
+
+
+
+
diff --git a/PyResources/icons/Lattice2_TopoSeries.svg b/PyResources/icons/Lattice2_TopoSeries.svg
new file mode 100644
index 0000000..3a6757b
--- /dev/null
+++ b/PyResources/icons/Lattice2_TopoSeries.svg
@@ -0,0 +1,186 @@
+
+
+
+
diff --git a/PyResources/icons/reference/Tree_Part_Sphere_Parametric.svg b/PyResources/icons/reference/Tree_Part_Sphere_Parametric.svg
new file mode 100644
index 0000000..356dc70
--- /dev/null
+++ b/PyResources/icons/reference/Tree_Part_Sphere_Parametric.svg
@@ -0,0 +1,171 @@
+
+
+
+
diff --git a/lattice2AttachablePlacement.py b/lattice2AttachablePlacement.py
index 86461e2..23d11f6 100644
--- a/lattice2AttachablePlacement.py
+++ b/lattice2AttachablePlacement.py
@@ -32,6 +32,7 @@ import Part
from lattice2Common import *
import lattice2BaseFeature
+import lattice2Subsequencer as Subsequencer
def makeAttachablePlacement(name):
'''makeAttachablePlacement(name): makes an attachable Placement object.'''
@@ -47,7 +48,7 @@ def makeAttachablePlacement(name):
return obj
class AttachablePlacement(lattice2BaseFeature.LatticeFeature):
- "The Lattice Placement object"
+ "Attachable Lattice Placement object"
def derivedInit(self,obj):
self.Type = "AttachablePlacement"
@@ -79,6 +80,46 @@ class ViewProviderAttachablePlacement(lattice2BaseFeature.ViewProviderLatticeFea
Gui.Control.closeDialog()
return True
+
+def makeLatticeAttachedPlacementSubsequence(name):
+ '''makeLatticeAttachedPlacementSubsequence(name): makes a AttachedPlacementSubsequence object.'''
+ return lattice2BaseFeature.makeLatticeFeature(name, AttachedPlacementSubsequence, ViewProviderAttachedPlacementSubsequence)
+
+class AttachedPlacementSubsequence(lattice2BaseFeature.LatticeFeature):
+ "Array Maker from Attachable Lattice Placement"
+
+ def derivedInit(self,obj):
+ self.Type = "AttachablePlacementSubsequence"
+
+ obj.ExposePlacement = False
+ obj.setEditorMode("ExposePlacement", 1) #read-only
+
+ obj.addProperty("App::PropertyLink", "Base", "Lattice Attached Placement Subsequence", "Link to Lattice Attached Placement, which is to be subsequenced.")
+ obj.addProperty("App::PropertyString", "RefIndexFilter","Lattice Attached Placement Subsequence","Sets which references of attachment to cycle through children. '0000' = no cycle, '1000' = cycle only ref1. '' = cycle all if possible")
+ obj.addProperty("App::PropertyEnumeration", "CycleMode","Lattice Attached Placement Subsequence", "How to cycle through chidren. Open = advance each link till one reaches the end of array. Periodic = if array end reached, continue from begin if any children left.")
+ obj.CycleMode = ['Open','Periodic']
+
+ def derivedExecute(self,obj):
+ attacher = obj.Base.Attacher.copy()
+ attacher.readParametersFromFeature(obj.Base)
+ i_filt_str = obj.RefIndexFilter
+ ifilt = None if i_filt_str == "" else [i for i in range(len(i_filt_str)) if int(i_filt_str[i]) != 0]
+ sublinks = Subsequencer.Subsequence_auto(attacher.References,
+ loop= ('Till end' if obj.CycleMode == 'Open' else 'All around'),
+ index_filter= ifilt)
+ plms = []
+ for lnkval in sublinks:
+ attacher.References = lnkval
+ plms.append(attacher.calculateAttachedPlacement(obj.Base.Placement))
+ return plms
+
+class ViewProviderAttachedPlacementSubsequence(lattice2BaseFeature.ViewProviderLatticeFeature):
+ def getIcon(self):
+ return getIconPath('Lattice2_AttachedPlacementSubsequence.svg')
+
+ def claimChildren(self):
+ return [self.Object.Base]
+
# -------------------------- /document object --------------------------------------------------
# -------------------------- Gui command --------------------------------------------------
@@ -96,6 +137,19 @@ def CreateAttachablePlacement(name):
"callback_Cancel= lambda: App.ActiveDocument.abortTransaction())")
FreeCAD.ActiveDocument.commitTransaction()
+def cmdCreateAttachedPlacementSubsequence(name):
+ sel = FreeCADGui.Selection.getSelectionEx()
+ FreeCAD.ActiveDocument.openTransaction("Array an attached placement")
+ FreeCADGui.addModule("lattice2AttachablePlacement")
+ FreeCADGui.addModule("lattice2Executer")
+ FreeCADGui.doCommand("f = lattice2AttachablePlacement.makeLatticeAttachedPlacementSubsequence(name='"+name+"')")
+ FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[0].Object.Name)
+ FreeCADGui.doCommand("f.Base.ViewObject.hide()")
+ FreeCADGui.doCommand("lattice2Executer.executeFeature(f)")
+ FreeCAD.ActiveDocument.commitTransaction()
+ FreeCADGui.doCommand("Gui.Selection.addSelection(f)")
+ deselect(sel)
+
class CommandAttachablePlacement:
"Command to create Lattice Placement feature"
@@ -120,6 +174,56 @@ class CommandAttachablePlacement:
else:
return False
+class CommandAttachedPlacementSubsequence:
+ "Command to convert a attached placement into an array"
+
+ def __init__(self):
+ pass
+
+ def GetResources(self):
+ return {'Pixmap' : getIconPath("Lattice2_AttachedPlacementSubsequence.svg"),
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_Placement","Array an attached placement") ,
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_Placement","Attached Placement: makes an array of placements from an attached placement by cycling attachment references...")}
+
+ def Activated(self):
+ try:
+ sel = FreeCADGui.Selection.getSelectionEx()
+ if len(sel) == 0:
+ infoMessage("Attached Placement Subsequence",
+ "Attached Placement Subsequence feature: makes an array of placements from an attached placement by cycling attachment references through children of an array the placement is attached to."+
+ "\n\nPlease select an attached placement object, first. Then invoke this tool. Adjust the properties of the created object if necessary." )
+ else:
+ if len(sel)!=1:
+ raise SelectionError("PlacementSubsequence", "Please select just one object, an attached placement. You have seleced {num}.".format(num= len(sel)))
+ cmdCreateAttachedPlacementSubsequence(name= "PlacementSubsequence")
+ except Exception as err:
+ msgError(err)
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
FreeCADGui.addCommand("Lattice2_AttachedPlacement", CommandAttachablePlacement())
-exportedCommands = ["Lattice2_AttachedPlacement"]
+FreeCADGui.addCommand("Lattice2_AttachedPlacementSubsequence", CommandAttachedPlacementSubsequence())
+
+class CommandAttachedPlacementGroup:
+ def GetCommands(self):
+ return ("Lattice2_AttachedPlacement","Lattice2_AttachedPlacementSubsequence")
+
+ def GetDefaultCommand(self): # return the index of the tuple of the default command.
+ return 0
+
+ def GetResources(self):
+ return { 'MenuText': 'Attached Placement:',
+ 'ToolTip': 'Attached Placement (group): tools to work with attached placement objects.'}
+
+ def IsActive(self): # optional
+ return App.ActiveDocument is not None
+
+FreeCADGui.addCommand("Lattice2_AttachedPlacement_Group", CommandAttachedPlacementGroup())
+
+exportedCommands = ["Lattice2_AttachedPlacement_Group"]
# -------------------------- /Gui command --------------------------------------------------
diff --git a/lattice2BaseFeature.py b/lattice2BaseFeature.py
index 611bc48..4c62c41 100644
--- a/lattice2BaseFeature.py
+++ b/lattice2BaseFeature.py
@@ -118,16 +118,7 @@ class LatticeFeature():
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(selfobj, propname):
- #todo: check type match
- return False
-
- selfobj.addProperty(proptype, propname, group, tooltip)
- if defvalue is not None:
- setattr(selfobj, propname, defvalue)
- return True
-
-
+ return assureProperty(selfobj, proptype, propname, defvalue, group, tooltip)
def derivedInit(self, obj):
'''for overriding by derived classes'''
@@ -296,6 +287,21 @@ class ViewProviderLatticeFeature:
FreeCAD.Console.PrintError("Error in onDelete: " + err.message)
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 -------------------------------------
diff --git a/lattice2Executer.py b/lattice2Executer.py
index c8ce060..35cca41 100644
--- a/lattice2Executer.py
+++ b/lattice2Executer.py
@@ -37,13 +37,13 @@ def executeFeature(obj):
obj.Proxy.execute(obj)
obj.purgeTouched()
except CancelError:
- FreeCAD.ActiveDocument.abortTransaction()
+ obj.Document.abortTransaction()
raise
except Exception as err:
try:
error(obj,err.message)
except CancelError:
- FreeCAD.ActiveDocument.abortTransaction()
+ obj.Document.abortTransaction()
raise
finally:
globalIsCreatingLatticeFeature = False
diff --git a/lattice2LinearArray.py b/lattice2LinearArray.py
index a61d487..7902d52 100644
--- a/lattice2LinearArray.py
+++ b/lattice2LinearArray.py
@@ -32,9 +32,11 @@ import Part
from lattice2Common import *
import lattice2BaseFeature
+from lattice2BaseFeature import assureProperty
import lattice2Executer
import lattice2GeomUtils
from lattice2ValueSeriesGenerator import ValueSeriesGenerator
+from lattice2Utils import sublinkFromApart, syncSublinkApart
def makeLinearArray(name):
'''makeLinearArray(name): makes a LinearArray object.'''
@@ -77,6 +79,8 @@ class LinearArray(lattice2BaseFeature.LatticeFeature):
obj.SpanEnd = 12.0
obj.Step = 3.0
obj.Count = 5.0
+
+ self.assureProperties(obj)
def updateReadonlyness(self, obj):
obj.setEditorMode("Dir", 1 if (obj.Link and obj.DirIsDriven) else 0)
@@ -98,10 +102,12 @@ class LinearArray(lattice2BaseFeature.LatticeFeature):
valuestype= "App::PropertyDistance")
self.updateReadonlyness(obj)
-
+ def assureProperties(self, selfobj):
+ assureProperty(selfobj, "App::PropertyLinkSub", "SubLink", sublinkFromApart(selfobj.Link, selfobj.LinkSubelement), "Lattice Array", "Mirror of Object+SubNames properties")
def derivedExecute(self,obj):
self.assureGenerator(obj)
+ self.assureProperties(obj)
self.updateReadonlyness(obj)
# Apply links
@@ -166,6 +172,12 @@ class LinearArray(lattice2BaseFeature.LatticeFeature):
output.append( App.Placement(obj.Point + obj.Dir*v, ori) )
return output
+
+ def onChanged(self, selfobj, prop): #prop is a string - name of the property
+ # synchronize SubLink and Object+SubNames properties
+ syncSublinkApart(selfobj, prop, 'SubLink', 'Link', 'LinkSubelement')
+ return lattice2BaseFeature.LatticeFeature.onChanged(self, selfobj, prop)
+
class ViewProviderLinearArray(lattice2BaseFeature.ViewProviderLatticeFeature):
diff --git a/lattice2PolarArray.py b/lattice2PolarArray.py
index 2bab27b..c4943c8 100644
--- a/lattice2PolarArray.py
+++ b/lattice2PolarArray.py
@@ -32,9 +32,11 @@ import Part
from lattice2Common import *
import lattice2BaseFeature
+from lattice2BaseFeature import assureProperty
import lattice2Executer
import lattice2GeomUtils
from lattice2ValueSeriesGenerator import ValueSeriesGenerator
+from lattice2Utils import sublinkFromApart, syncSublinkApart
def makePolarArray(name):
'''makePolarArray(name): makes a PolarArray object.'''
@@ -72,6 +74,8 @@ class PolarArray(lattice2BaseFeature.LatticeFeature):
obj.SpanEnd = 360
obj.EndInclusive = False
obj.Count = 5
+
+ self.assureProperties(obj)
def assureGenerator(self, obj):
'''Adds an instance of value series generator, if one doesn't exist yet.'''
@@ -90,10 +94,14 @@ class PolarArray(lattice2BaseFeature.LatticeFeature):
obj.setEditorMode("AxisDirIsDriven", 0 if obj.AxisLink else 1)
obj.setEditorMode("AxisPointIsDriven", 0 if obj.AxisLink else 1)
self.generator.updateReadonlyness()
-
+
+ def assureProperties(self, selfobj):
+ assureProperty(selfobj, "App::PropertyLinkSub", "AxisSubLink", sublinkFromApart(selfobj.AxisLink, selfobj.AxisLinkSubelement), "Lattice Array", "Mirror of Object+SubNames properties")
+
def derivedExecute(self,obj):
self.assureGenerator(obj)
+ self.assureProperties(obj)
self.updateReadonlyness(obj)
# Apply links
@@ -153,6 +161,11 @@ class PolarArray(lattice2BaseFeature.LatticeFeature):
return output
+ def onChanged(self, selfobj, prop): #prop is a string - name of the property
+ # synchronize SubLink and Object+SubNames properties
+ syncSublinkApart(selfobj, prop, 'AxisSubLink', 'AxisLink', 'AxisLinkSubelement')
+ return lattice2BaseFeature.LatticeFeature.onChanged(self, selfobj, prop)
+
class ViewProviderPolarArray(lattice2BaseFeature.ViewProviderLatticeFeature):
def getIcon(self):
diff --git a/lattice2SeriesGroup.py b/lattice2SeriesGroup.py
new file mode 100644
index 0000000..f8e1b08
--- /dev/null
+++ b/lattice2SeriesGroup.py
@@ -0,0 +1,52 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2016 - Victor Titov (DeepSOIC) *
+#* *
+#* *
+#* 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__="Group command for Lattice Series features"
+__author__ = "DeepSOIC"
+__url__ = ""
+__doc__ = "Group command for Lattice Series features"
+
+import lattice2ParaSeries as ParaSeries
+import lattice2TopoSeries as TopoSeries
+
+import FreeCAD as App
+import FreeCADGui
+
+class CommandSeriesGroup:
+ def GetCommands(self):
+ return tuple(ParaSeries.exportedCommands + TopoSeries.exportedCommands)
+
+ def GetDefaultCommand(self): # return the index of the tuple of the default command.
+ return 0
+
+ def GetResources(self):
+ return { 'MenuText': 'Series features:',
+ 'ToolTip': 'Series features (group): features that collect permutations of an object by changing dependent objects.'}
+
+ def IsActive(self): # optional
+ return App.ActiveDocument is not None
+
+FreeCADGui.addCommand('Lattice2_Series_GroupCommand',CommandSeriesGroup())
+
+exportedCommands = ['Lattice2_Series_GroupCommand']
+
diff --git a/lattice2SubLink.py b/lattice2SubLink.py
index f0d312b..5db8b9b 100644
--- a/lattice2SubLink.py
+++ b/lattice2SubLink.py
@@ -26,10 +26,13 @@ __author__ = "DeepSOIC"
__doc__ = "Lattice SubLink is like Draft Facebinder, but for edges and vertices too."
from lattice2Common import *
-from lattice2BaseFeature import isObjectLattice
+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 --------------------------------------------------
@@ -51,32 +54,75 @@ class LatticeSubLink:
obj.Proxy = self
+ self.assureProperties(obj)
+
+ def assureProperties(self, selfobj):
+ assureProperty(selfobj, "App::PropertyEnumeration","Looping", ["Single"] + LSS.LOOP_MODES, "Lattice SubLink", "Sets wether 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(selfobj.Object, selfobj.SubNames), "Lattice SubLink", "Mirror of Object+SubNames properties")
def execute(self,selfobj):
+ self.assureProperties(selfobj)
+
#validity check
if isObjectLattice(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.")
- rst = [] #variable to receive the final list of shapes
lnkobj = selfobj.Object
- for subname in selfobj.SubNames:
- subname = subname.strip()
- if len(subname)==0:
- raise ValueError("Empty subname! Not allowed.")
- if 'Face' in subname:
- index = int(subname.replace('Face',''))-1
- rst.append(lnkobj.Shape.Faces[index])
- elif 'Edge' in subname:
- index = int(subname.replace('Edge',''))-1
- rst.append(lnkobj.Shape.Edges[index])
- elif 'Vertex' in subname:
- index = int(subname.replace('Vertex',''))-1
- rst.append(lnkobj.Shape.Vertexes[index])
+ 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.
+ 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:
- lattice2Executer.warning(selfobj,"Unexpected subelement name: "+subname+". Trying to extract it with .Shape.getElement()...")
- rst.append(linkobj.Shape.getElement(subname))
- if len(rst) == 0:
+ 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 selfobj.Object:
@@ -88,16 +134,13 @@ class LatticeSubLink:
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.
- if len(rst) > 1:
- selfobj.Shape = Part.makeCompound(rst)
- else: # don't make compound of one shape, output it directly
- sh = rst[0]
- # absorb placement of original shape
- sh = ShapeCopy.transformCopy(sh)
- # apply Placement that is filled into feature's Placement property (not necessary)
- sh.Placement = selfobj.Placement
- selfobj.Shape = sh
+ # 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
@@ -111,15 +154,20 @@ class ViewProviderSubLink:
vobj.Proxy = self
def getIcon(self):
+ ret = ""
if len(self.Object.SubNames) == 1:
subname = self.Object.SubNames[0]
if 'Face' in subname:
- return getIconPath("Lattice2_SubLink_Face.svg")
+ ret = getIconPath("Lattice2_SubLink_Face.svg")
elif 'Edge' in subname:
- return getIconPath("Lattice2_SubLink_Edge.svg")
+ ret = getIconPath("Lattice2_SubLink_Edge.svg")
elif 'Vertex' in subname:
- return getIconPath("Lattice2_SubLink_Vertex.svg")
- return getIconPath("Lattice2_SubLink.svg")
+ 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
@@ -141,7 +189,7 @@ class ViewProviderSubLink:
def claimChildren(self):
return []
-def CreateSubLink(object, subnames):
+def CreateSubLink(object, subnames, looping):
#stabilize links
subnames = list(subnames) #'tuple' object does not support item assignment; SubElementNames of SelectionObject is a tuple
try:
@@ -187,11 +235,14 @@ def CreateSubLink(object, subnames):
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 = unicode(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")
@@ -200,7 +251,7 @@ def CreateSubLink(object, subnames):
FreeCADGui.doCommand("f = None")
return App.ActiveDocument.ActiveObject
-def cmdSubLink():
+def cmdSubLink(looping = 'Single'):
sel = FreeCADGui.Selection.getSelectionEx()
if len(sel) == 0:
raise SelectionError("Bad selection", "Please select some subelements from one object, first.")
@@ -209,7 +260,7 @@ def cmdSubLink():
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)
+ CreateSubLink(sel[0].Object,sel[0].SubElementNames, looping)
deselect(sel)
App.ActiveDocument.commitTransaction()
@@ -217,7 +268,7 @@ def cmdSubLink():
# -------------------------- Gui command --------------------------------------------------
-class _CommandSubLink:
+class CommandSubLink:
"Command to create SubLink feature"
def GetResources(self):
return {'Pixmap' : getIconPath("Lattice2_SubLink.svg"),
@@ -242,8 +293,51 @@ class _CommandSubLink:
else:
return False
-FreeCADGui.addCommand('Lattice2_SubLink', _CommandSubLink())
+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
-exportedCommands = ['Lattice2_SubLink']
+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
+
+FreeCADGui.addCommand('Lattice2_Sublink_GroupCommand',CommandSublinkGroup())
+
+
+exportedCommands = ['Lattice2_Sublink_GroupCommand']
# -------------------------- /Gui command --------------------------------------------------
diff --git a/lattice2Subsequencer.py b/lattice2Subsequencer.py
new file mode 100644
index 0000000..18ae860
--- /dev/null
+++ b/lattice2Subsequencer.py
@@ -0,0 +1,388 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2017 - Victor Titov (DeepSOIC) *
+#* *
+#* *
+#* 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__="Subsequencer: module with code to extract chains of similar elements from compounds"
+__author__ = "DeepSOIC"
+__url__ = ""
+
+# ------------------------------------------------
+TRAVERSAL_MODES = ['Compund children','Compound leaves']
+LOOP_MODES = ['Till end', 'All around', 'All from first']
+# -----------------------------------------------
+
+# -----------------------------------------------
+class TraversalError(TypeError):
+ "Error to raise when extracting children from compound fails"
+ pass
+
+class SubsequencingError(ValueError):
+ "General error, that subsequencing is impossible or failed"
+ pass
+
+class SubsequencingError_LinkType(SubsequencingError):
+ "Subsequencing can't be applied to this link type (e.g. App::PropertyLink)."
+ pass
+
+class SubsequencingError_LinkValue(SubsequencingError):
+ "Type of links to subsequence are right, but they happen to be links to whole objects, or otherwise impossible to iterate"
+ pass
+# -----------------------------------------------
+
+# ----------------------------------------------
+class HashableShape(object):
+ "Decorator for Part.Shape, that can be used as key in dicts. Based on isSame method."
+ def __init__(self, shape):
+ self.Shape = shape
+ self.hash = shape.hashCode()
+
+ def __eq__(self, other):
+ return self.Shape.isSame(other.Shape)
+
+ def __hash__(self):
+ return self.hash
+
+def traverseCompound(compound, traversal):
+ if traversal == 'Compund children':
+ if compound.ShapeType != 'Compound':
+ raise TraversalError("Shape is not compound; can't traverse it in direct children mode.")
+ return compound.childShapes()
+ elif traversal == 'Compound leaves':
+ import lattice2CompoundExplorer as LCE
+ return LCE.allLeaves(compound)
+
+element_extractors = {
+ "Vertex": (lambda(sh): sh.Vertexes),
+ "Edge": (lambda(sh): sh.Edges),
+ "Wire": (lambda(sh): sh.Wires),
+ "Face": (lambda(sh): sh.Faces),
+ "Shell": (lambda(sh): sh.Shells),
+ "Solid": (lambda(sh): sh.Solids),
+ "CompSolid": (lambda(sh): sh.CompSolids),
+ "Compound": (lambda(sh): sh.Compounds),
+}
+
+def getIndexesIntoList(element, list_of_shapes):
+ """Finds this element in shapes in list_of_shapes. This is a generator function (to be
+ used if there are multiple occurences of the element).
+ [(index_into_list, element_type_string, subelement_index), ...]"""
+
+ element_type_string = element.ShapeType
+ element_extractor = element_extractors[element_type_string]
+
+ ret = []
+
+ for i_sh in xrange(len(list_of_shapes)):
+ elements = element_extractor(list_of_shapes[i_sh])
+ for i_el in xrange(len(elements)):
+ if elements[i_el].isEqual(element):
+ # to make link more robust, use negative index if one is closer to the end
+ if i_el * 2 > len(elements):
+ i_el = i_el - len(elements)
+ yield (i_sh, element_type_string, i_el)
+# ----------------------------------------------
+
+# ---------------------------------------
+
+def linkSubList_convertToOldStyle(references):
+ ("input: [(obj1, (sub1, sub2)), (obj2, (sub1, sub2))]\n"
+ "output: [(obj1, sub1), (obj1, sub2), (obj2, sub1), (obj2, sub2)]")
+ result = []
+ for tup in references:
+ if type(tup[1]) is tuple or type(tup[1]) is list:
+ for subname in tup[1]:
+ result.append((tup[0], subname))
+ if len(tup[1]) == 0:
+ result.append((tup[0], ''))
+ elif isinstance(tup[1],basestring):
+ # old style references, no conversion required
+ result.append(tup)
+ return result
+
+def toLinkSubList(linksub):
+ if linksub is None:
+ return []
+ object, subs = linksub
+ if not hasattr(subs, '__iter__'):
+ subs = [subs]
+ return [(object, sub) for sub in subs]
+
+def toLinkSub(linksublist):
+ if not linksublist:
+ return None
+ linksublist = linkSubList_convertToOldStyle(linksublist)
+ subs = []
+ object = linksublist[0][0]
+ for itobj, sub in linksublist:
+ if itobj is not object:
+ raise ValueError("LinkSubList can't be converted into LinkSub because it links to more than one object.")
+ subs.append(sub)
+ return (object, subs)
+
+# ---------------------------------------
+
+# ---------------------------------------------------------
+
+def Subsequence_basic(link, traversal, loop):
+ """Subsequence_basic(link, traversal, loop): low-level function powering Subsequencing. Transforms
+ a single sub-link into a list of sub-links to every child of the linked compound.
+
+ link: tuple (object, subelement), where object is a document object, and subelement is
+ a string like 'Edge3'.
+
+ traversal: either of values from TRAVERSAL_MODES, that sets how to enumerate children
+
+ loop: either of values from LOOP_MODES. Sets which children to use.
+
+ returns: list of links [(object,subelement), ....]"""
+
+ # extract shapes of the array
+ compound = link[0].Shape
+ children = traverseCompound(compound, traversal)
+
+ # parse link string. Input: element_string. Output: element_shape, element_type_string
+ element_string = link[1]
+ if not issubclass(type(element_string), basestring):
+ raise TypeError("Second element of link tuple must be a string, not {typ}".format(typ= type(element_string).__name__))
+ element_type_string = None
+ for key in element_extractors.keys():
+ if element_string.startswith(key):
+ element_type_string = key
+ break
+ if element_type_string is None:
+ # raise ValueError("Subelement string format not recognized")
+ # resort to generic FreeCAD method
+ element_shape = compound.getElement(element_string)
+ else:
+ # use negative-index-aware method
+ #extract index from string:
+ index = -1 + int( element_string[ len(element_type_string) : ] )
+ element_shape = element_extractors[element_type_string](compound)[index]
+
+ # convert global element index to index in child
+ i_first_child, element_type_string, i_in_child = getIndexesIntoList(element_shape, children).next()
+ if loop == 'All from first':
+ i_first_child = 0
+
+ elements = element_extractors[element_type_string](compound)
+ index_dict = dict([(HashableShape(elements[i]), i) for i in xrange(len(elements))])
+
+ # find the element in each child, find out its global index, and output result in a form of a string for a link
+ ret = [] #list of tuples (object, subelement_string)
+ for i in range( len(children) if loop != 'Till end' else len(children) - i_first_child ):
+ i_child = (i + i_first_child) % len(children)
+ element = element_extractors[element_type_string](children[i_child])[i_in_child]
+ ret.append((
+ link[0],
+ element_type_string + str(index_dict[HashableShape(element)]+1)
+ ))
+ return ret
+
+def Subsequence_LinkSubList(linksublist, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ """Subsequence_LinkSubList(linksublist, traversal = TRAVERSAL_MODES[0], loop = 'Till end', object_filter = None):
+ form a list of values for iterating over elements in App::PropertyLinkSubList.
+
+ linksublist: the value of property (either of type [(object, sub), ..], or
+ [(object, [sub1, sub2, ...]), ...]
+
+ traversal: how to unpack compounds
+
+ loop: sets which children to process.
+
+ object_filter: list or set of objects that should be considered being arrays. If
+ omitted, all links to subelements are attempted to be enumerated as arrays.
+
+ index_list: list or set of ints, that sets which parts of link are to be subsequenced.
+ If None, all elements are attempted to be subsequenced, and only if none can be, an error
+ is raised. If index_list is specified, it is treated as strict, and if any
+ corresponding sublink can't be subsequenced, an error is raised.
+
+ return: list of values that can be assigned to the link in a loop.
+ """
+
+ linksublist = linkSubList_convertToOldStyle(linksublist)
+ if object_filter is None:
+ object_filter = [obj for (obj,sub) in linksublist] #number of links is likely quite low, so using sets might give more of a penalty than gain
+
+ loops = [] #list to receive subsequences for made for pieces of the link
+ n_seq = None
+ i = -1
+ for object,sub in linksublist:
+ i += 1
+ if (sub
+ and (object in object_filter)
+ and (index_filter is None or i in index_filter)
+ and hasattr(object, "Shape") ):
+ try:
+ seq = Subsequence_basic((object, sub), traversal, loop)
+ if len(seq) < 2:
+ from lattice2Executer import warning
+ warning(None, u"Subsequencing link index {i} to {sub} of '{obj}' yielded only one item."
+ .format(i= i, obj= object.Label, sub= sub))
+ except TraversalError:
+ if index_filter is not None:
+ raise # re-raise. When index list is given, treat it as that it must be subsequenced.
+ loops.append((object,sub))
+ continue
+ loops.append(seq)
+ if n_seq is None:
+ n_seq = len(seq)
+ else:
+ n_seq = min(n_seq, len(seq))
+ else:
+ if index_filter and i in index_filter:
+ if not sub:
+ raise SubsequencingError_LinkValue("Sublink part {index} can't be subsequenced, because it's a link to whole object, not to subelement.".format(index= i))
+ loops.append((object,sub))
+ assert(len(loops) == len(linksublist))
+ if n_seq is None:
+ raise SubsequencingError_LinkValue("In supplied link, nothing to loop over compounds was found.")
+
+ # expand non-subsequenced parts of linksublist
+ for i_loop in range(len(loops)):
+ if type(loops[i_loop]) is not list:
+ loops[i_loop] = [loops[i_loop]]*n_seq
+
+ # form the result
+ ret = []
+ for i_seq in range(n_seq):
+ ret.append(
+ [loop[i_seq] for loop in loops]
+ )
+ return ret
+
+
+def Subsequence_LinkSub(linksub, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ """Subsequence_LinkSub(linksub, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None):
+ form a list of values for iterating over elements in App::PropertyLinkSub.
+
+ linksub: the value of property, like (object, ["Edge1", "Edge3"...])
+
+ traversal: how to unpack compounds
+
+ loop: sets which children to process.
+
+ object_filter: list or set of objects that should be considered being arrays. If
+ omitted, all links to subelements are attempted to be enumerated as arrays.
+
+ index_list: list or set of ints, that sets which parts of link are to be subsequenced.
+ If None, all elements are attempted to be subsequenced, and only if none can be, an error
+ is raised. If index_list is specified, it is treated as strict, and if any
+ corresponding sublink can't be subsequenced, an error is raised.
+
+ return: list of values that can be assigned to the link in a loop.
+ """
+ object, subs = linksub
+ if not hasattr(subs, '__iter__'):
+ subs = [subs]
+ linksublist = toLinkSubList(linksub)
+ vals = Subsequence_LinkSubList(linksublist, traversal, loop, object_filter, index_filter)
+ return [toLinkSub(v) for v in vals]
+
+def Subsequence_auto(link, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ """Subsequence_auto(link, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ form a list of values for iterating over elements in App::PropertyLinkSub or
+ App::PropertyLinkSubList.
+
+ link: the value of property
+
+ traversal: how to unpack compounds
+
+ loop: sets which children to process.
+
+ object_filter: list or set of objects that should be considered being arrays. If
+ omitted, all links to subelements are attempted to be enumerated as arrays.
+
+ index_list: list or set of ints, that sets which parts of link are to be subsequenced.
+ If None, all elements are attempted to be subsequenced, and only if none can be, an error
+ is raised. If index_list is specified, it is treated as strict, and if any
+ corresponding sublink can't be subsequenced, an error is raised.
+
+ return: list of values that can be assigned to the link in a loop.
+ """
+ if type(link) is list:
+ # linkSubList or linkList!
+ if len(link) == 0:
+ raise SubsequencingError_LinkValue("Cannot subsequence empty link (link is empty list).")
+ if type(link[0]) is not tuple:
+ raise SubsequencingError_LinkType("LinkList cannot be subsequenced (only LinkSub or LinkSubList can).")
+ return Subsequence_LinkSubList(link, traversal, loop, object_filter, index_filter)
+ elif type(link) is tuple:
+ # linkSub!
+ return Subsequence_LinkSub(link, traversal, loop, object_filter, index_filter)
+ elif link is None:
+ raise SubsequencingError_LinkValue("Cannot subsequence empty link (link is None).")
+ else:
+ raise SubsequencingError_LinkType("Link type not recognized.")
+
+def Subsequence_LinkDict(link_dict, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ """Subsequence_LinkDict(link_dict, traversal = TRAVERSAL_MODES[0], loop = LOOP_MODES[0], object_filter = None, index_filter = None):
+ subsequence a heterogeneous list of links supplied as a dict.
+
+ link_dict: dictionary. Key is any, value is the value of some link property. It can be
+ SubLink, SubList, and plain Link, and LinkList (last two are skipped).
+
+ traversal: how to unpack compounds
+
+ loop: sets which children to process.
+
+ object_filter: list or set of objects that should be considered being arrays. If
+ omitted, all links to subelements are attempted to be enumerated as arrays.
+
+ index_list: list or set of ints, that sets which parts of link are to be subsequenced.
+ If None, all elements are attempted to be subsequenced, and only if none can be, an error
+ is raised. If index_list is specified, it is treated as strict, and if any
+ corresponding sublink can't be subsequenced, an error is raised.
+
+ return: tuple (int, dict). Int is the length of the subsequence. Dict contains the
+ subsequences. It is similar to input dikt, but link values were replaced with lists of
+ link values. If a link failed to be subsequenced, the key/value pair is not added to
+ return dict at all."""
+
+ ret_dict = {} #ret_dict is the dict to receive result
+ err_dict = {}
+
+ # subsequence all links
+ n_seq = None # length of shortest subsequence; None means no subsequences were made yet.
+ for key in link_dict.keys():
+ link = link_dict[key]
+ try:
+ link_seq = Subsequence_auto(link, traversal, loop, object_filter, index_filter)
+ n_seq = len(link_seq) if n_seq is None else min(n_seq, len(link_seq))
+ ret_dict[key] = link_seq
+ # silently absorb non-subsequencable links; fail only if everything fails
+ except SubsequencingError_LinkType as err:
+ err_dict[key] = err
+ except SubsequencingError_LinkValue as err:
+ err_dict[key] = err
+
+ # trim them to shortest subsequence
+ if n_seq is None:
+ raise SubsequencingError_LinkValue("In supplied links, nothing to loop over compounds was found. Key-by-key errors follow: {err_list}"
+ .format(err_list= "\n".join([repr(key)+": "+err_dict[key].message for key in err_dict])))
+ for key in ret_dict.keys():
+ seq = ret_dict[key]
+ seq = seq[0:n_seq]
+ ret_dict[key] = seq
+
+ # done!
+ return (n_seq, ret_dict)
diff --git a/lattice2TopoSeries.py b/lattice2TopoSeries.py
new file mode 100644
index 0000000..78b75a5
--- /dev/null
+++ b/lattice2TopoSeries.py
@@ -0,0 +1,309 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2016 - Victor Titov (DeepSOIC) *
+#* *
+#* *
+#* 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 TopoSeries feature"
+__author__ = "DeepSOIC"
+__url__ = ""
+__doc__ = "Lattice TopoSeries feature: generates series of shapes by subsequencing sublinks"
+
+import math
+
+import FreeCAD as App
+import Part
+
+from lattice2Common import *
+import lattice2BaseFeature
+import lattice2Executer as Executer
+import lattice2Markers as markers
+import lattice2Subsequencer as Subsequencer
+
+# --------------------------- general routines ------------------------------------------------
+
+def findAllLinksTo(doc_obj):
+ """findAllLinksTo(doc_obj): finds all link properties pointing to supplied object.
+ Returns them as list of tuples (dependent_object_name, property_name). Does not include
+ expression links."""
+ ret = []
+ doc = doc_obj.Document
+ for obj in doc_obj.InList:
+ for prop_name in obj.PropertiesList:
+ typ = obj.getTypeIdOfProperty(prop_name)
+ if typ == 'App::PropertyLink':
+ if readProperty(doc, obj.Name, prop_name) is doc_obj:
+ ret.append((obj.Name, prop_name))
+ elif typ == 'App::PropertyLinkList':
+ if doc_obj in readProperty(doc, obj.Name, prop_name):
+ ret.append((obj.Name, prop_name))
+ elif typ == 'App::PropertyLinkSub':
+ val = readProperty(doc, obj.Name, prop_name)
+ if val is not None and doc_obj is val[0]:
+ ret.append((obj.Name, prop_name))
+ elif typ == 'App::PropertyLinkSubList':
+ if doc_obj in [tup[0] for tup in readProperty(doc, obj.Name, prop_name)]:
+ ret.append((obj.Name, prop_name))
+ return ret
+
+def readProperty(doc, object_name, property_name):
+ return getattr(doc.getObject(object_name), property_name)
+
+def writeProperty(doc, object_name, property_name, value):
+ setattr(doc.getObject(object_name), property_name, value)
+
+# -------------------------- document object --------------------------------------------------
+
+def makeLatticeTopoSeries(name):
+ '''makeLatticeTopoSeries(name): makes a LatticeTopoSeries object.'''
+ return lattice2BaseFeature.makeLatticeFeature(name, LatticeTopoSeries, ViewProviderLatticeTopoSeries)
+
+class LatticeTopoSeries(lattice2BaseFeature.LatticeFeature):
+ "The Lattice TopoSeries object"
+
+ def derivedInit(self,obj):
+ self.Type = "LatticeTopoSeries"
+
+ obj.addProperty("App::PropertyLink","ObjectToTake","Lattice TopoSeries","Object to collect permutations of. Can be any generic shape, as well as an array of placements.")
+ obj.addProperty("App::PropertyLink","ObjectToLoopOver","Lattice TopoSeries","Array object to subsequence sublinks to.")
+
+ obj.addProperty("App::PropertyEnumeration", "CycleMode", "Lattice TopoSeries", "Sets how to treat the ObjectToLoopOver")
+ obj.CycleMode = ["Open", "Periodic"]
+
+ obj.addProperty("App::PropertyEnumeration","Recomputing","Lattice TopoSeries","Sets recomputing policy.")
+ obj.Recomputing = ["Disabled", "Recompute Once", "Enabled"]
+ obj.Recomputing = "Disabled" # recomputing TopoSeries can be very long, so disable it by default
+
+ def makeSubsequence(self, selfobj, object_to_loop):
+
+ # gather up the links
+ links = findAllLinksTo(object_to_loop)
+ if self.isVerbose():
+ print ("All links to {feature}:\n {links}"
+ .format(feature= object_to_loop.Document.Name+"."+object_to_loop.Name,
+ links= "\n ".join([link[0]+"."+link[1] for link in links]) ) )
+
+ # subsequencing
+ # prepare dict of link values
+ linkdict = {} #key is tuple (object_name, property_name). Value is the value of property.
+ for link in links:
+ link_val = readProperty(object_to_loop.Document, link[0], link[1])
+ linkdict[link] = link_val
+ # do the subsequencing
+ ret = Subsequencer.Subsequence_LinkDict(
+ linkdict,
+ loop= ('Till end' if selfobj.CycleMode == 'Open' else 'All around'),
+ object_filter= [object_to_loop] )
+ if self.isVerbose():
+ print ("Subsequence made. Length: {n_seq}".format(n_seq= ret[0]))
+ return ret
+
+
+ def isVerbose(self):
+ return True
+
+ def derivedExecute(self,selfobj):
+
+ if selfobj.Recomputing == "Disabled":
+ raise ValueError(selfobj.Name+": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it.")
+ try:
+
+ # do the subsequencing in this document first, to verify stuff is set up correctly, and to obtain sequence length
+ if self.isVerbose():
+ print ("In-place pre-subsequencing, for early check")
+ n_seq, subs_linkdict = self.makeSubsequence(selfobj, selfobj.ObjectToLoopOver)
+
+
+ bGui = bool(App.GuiUp) and Executer.globalIsCreatingLatticeFeature #disabled for most recomputes, because it causes a crash if property edits are approved by hitting Enter
+ if bGui:
+ import PySide
+ progress = PySide.QtGui.QProgressDialog(u"Recomputing "+selfobj.Label, u"Abort", 0, n_seq+1)
+ progress.setModal(True)
+ progress.show()
+
+ doc1 = selfobj.Document
+ doc2 = App.newDocument()
+ object_to_take_in_doc2 = None # define the variable, to prevent del() in finally block from raising another error
+ object_to_loop_in_doc2 = None
+ try:
+ if self.isVerbose():
+ print ("Copying object with dependencies to a temporary document...")
+
+ doc2.copyObject(selfobj.ObjectToTake, True)
+
+ if self.isVerbose():
+ print ("Enabling nested para/toposeries, if any...")
+ #if there are nested para/toposeries in the dependencies, make sure to enable them
+ for objd2 in doc2.Objects:
+ if hasattr(objd2,"Recomputing"):
+ try:
+ objd2.Recomputing = "Enabled"
+ objd2.purgeTouched()
+ except exception:
+ Executer.warning(selfobj,"Failed to enable recomputing of "+objd2.Name)
+
+ object_to_take_in_doc2 = doc2.getObject(selfobj.ObjectToTake.Name)
+ object_to_loop_in_doc2 = doc2.getObject(selfobj.ObjectToLoopOver.Name)
+ if bGui:
+ progress.setValue(1)
+
+ if self.isVerbose():
+ print ("Repeating subsequencing in temporary document...")
+ n_seq, subs_linkdict = self.makeSubsequence(selfobj, object_to_loop_in_doc2)
+
+ output_shapes = []
+ for i in range(n_seq):
+ if self.isVerbose():
+ print ("Computing {x}/{y}".format(x= i+1, y= n_seq))
+
+ for key in subs_linkdict:
+ writeProperty(doc2, key[0], key[1], subs_linkdict[key][i])
+
+ #recompute
+ doc2.recompute()
+
+ #get shape
+ shape = None
+ for obj in doc2.Objects:
+ if 'Invalid' in obj.State:
+ Executer.error(obj,"Recomputing shape for subsequence index "+repr(i)+" failed.")
+
+ scale = 1.0
+ try:
+ if not selfobj.ObjectToTake.Shape.isNull():
+ scale = selfobj.ObjectToTake.Shape.BoundBox.DiagonalLength/math.sqrt(3)
+ except Exception:
+ pass
+ if scale < DistConfusion * 100:
+ scale = 1.0
+ shape = markers.getNullShapeShape(scale)
+ if shape is None:
+ shape = object_to_take_in_doc2.Shape.copy()
+ output_shapes.append(shape)
+
+ #update progress
+ if bGui:
+ progress.setValue(progress.value()+1)
+ if progress.wasCanceled():
+ raise Executer.CancelError()
+
+ finally:
+ #delete all references, before destroying the document. Probably not required, but to be sure...
+ if self.isVerbose():
+ print ("Cleanup...")
+
+ del(object_to_take_in_doc2)
+ del(object_to_loop_in_doc2)
+ doc2_name = doc2.Name
+ del(doc2)
+ App.closeDocument(doc2_name)
+ if bGui:
+ progress.setValue(n_seq+1)
+
+
+ selfobj.Shape = Part.makeCompound(output_shapes)
+
+ output_is_lattice = lattice2BaseFeature.isObjectLattice(selfobj.ObjectToTake)
+ if 'Auto' in selfobj.isLattice:
+ new_isLattice = 'Auto-On' if output_is_lattice else 'Auto-Off'
+ if selfobj.isLattice != new_isLattice:#check, to not cause onChanged without necessity (onChange messes with colors, it's better to keep user color)
+ selfobj.isLattice = new_isLattice
+ finally:
+ if selfobj.Recomputing == "Recompute Once":
+ selfobj.Recomputing = "Disabled"
+ return "suppress" # "suppress" disables most convenience code of lattice2BaseFeature. We do it because we build a nested array, which are not yet supported by lattice WB.
+
+class ViewProviderLatticeTopoSeries(lattice2BaseFeature.ViewProviderLatticeFeature):
+
+ def getIcon(self):
+ return getIconPath("Lattice2_TopoSeries.svg")
+
+ def claimChildren(self):
+ return [self.Object.ObjectToTake]
+
+# -------------------------- /document object --------------------------------------------------
+
+# -------------------------- Gui command --------------------------------------------------
+
+
+
+def CreateLatticeTopoSeries(name, shapeObj, loopObj):
+ FreeCADGui.addModule("lattice2TopoSeries")
+ FreeCADGui.addModule("lattice2Executer")
+
+ #fill in properties
+ FreeCADGui.doCommand("f = lattice2TopoSeries.makeLatticeTopoSeries(name='"+name+"')")
+ FreeCADGui.doCommand("f.ObjectToTake = App.ActiveDocument."+shapeObj.Name)
+ FreeCADGui.doCommand("f.ObjectToLoopOver = App.ActiveDocument."+loopObj.Name)
+
+ #execute
+ FreeCADGui.doCommand("f.Recomputing = 'Recompute Once'")
+ FreeCADGui.doCommand("lattice2Executer.executeFeature(f)")
+
+ #hide something
+ FreeCADGui.doCommand("f.ObjectToTake.ViewObject.hide()")
+
+ #finalize
+ FreeCADGui.doCommand("Gui.Selection.addSelection(f)")
+ FreeCADGui.doCommand("f = None")
+
+def cmdCreateSeries():
+ sel = FreeCADGui.Selection.getSelectionEx()
+ if len(sel) == 2 :
+ doc = FreeCAD.ActiveDocument #remember it! Recomputing TopoSeries messes up ActiveDocument, so committing transaction is screwed up...
+ if sel[1].Object.Shape.ShapeType != "Compound":
+ raise SelectionError("Bad selection", "Second selected object ({label}) should be an array of shapes (a compound). It is not.".format(label= sel[1].Object.Label))
+ doc.openTransaction("TopoSeries")
+ CreateLatticeTopoSeries("TopoSeries",sel[0].Object, sel[1].Object)
+ deselect(sel)
+ doc.commitTransaction()
+ else:
+ raise SelectionError("Bad selection","Please select two objects, first. First one is the result shape to collect variation of. Second one is an array to loop over.")
+
+class _CommandLatticeTopoSeries:
+ "Command to create LatticeTopoSeries feature"
+ def GetResources(self):
+ return {'Pixmap' : getIconPath("Lattice2_TopoSeries.svg"),
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_TopoSeries","TopoSeries"),
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_TopoSeries","TopoSeries: generate an array of shapes by subsequencing (looping subelement links across an array).")}
+
+ def Activated(self):
+ try:
+ if len(FreeCADGui.Selection.getSelection())==0:
+ infoMessage("TopoSeries",
+ "TopoSeries command. Generates an array of shapes by subsequencing links in dependencies (looping subelement links across an array).\n\n"+
+ "Please select an object to generate array from, and an array object to loop over (order of selection matters!). Then invoke the command.\n\n"+
+ "TopoSeries will find all objects that link to the array object, and if a link is a link to subelement (e.g., to Edge1), it will advance it to the corresponding subelements of next array child. Then recompute the result shape and output it as a child. So on until any link goes out of bounds (or it will go around, if 'CycleMode' property is set to `Periodic`)."
+ )
+ return
+ cmdCreateSeries()
+ except Exception as err:
+ msgError(err)
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
+FreeCADGui.addCommand('Lattice2_TopoSeries', _CommandLatticeTopoSeries())
+
+exportedCommands = ['Lattice2_TopoSeries']
diff --git a/lattice2Utils.py b/lattice2Utils.py
new file mode 100644
index 0000000..9762399
--- /dev/null
+++ b/lattice2Utils.py
@@ -0,0 +1,64 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2017 - Victor Titov (DeepSOIC) *
+#* *
+#* *
+#* 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__="Utility functions for Lattice2"
+__author__ = "DeepSOIC"
+__url__ = ""
+
+def sublinkFromApart(object, subnames):
+ if type(subnames) is not list and type(subnames) is not tuple:
+ subnames = [subnames]
+ return (object, [str(sub) for sub in subnames]) if object is not None else None
+
+def syncSublinkApart(selfobj, prop, sublink_prop_name, obj_property_name, subnames_prop_name):
+ """syncSublinkApart(selfobj, prop, sublink_prop_name, obj_property_name, subnames_prop_name):
+ Function that synchronizes a sublink mirror property with split apart property pair.
+
+ selfobj, prop: as in onChanged
+
+ sublink_prop_name: name of property of type "App::PropertyLinkSub"
+
+ obj_property_name, subnames_prop_name: names of apart properties. First must be
+ App::PropertyLink. Second can be PropertyString or PropertyStringList.
+ """
+
+ # check if changed property is relevant
+ if not prop in (sublink_prop_name, obj_property_name, subnames_prop_name):
+ return
+
+ #if restoring, some of the properties may not exist yet. If they don't, skip, and wait for the last relevant onChanged.
+ if not ( hasattr(selfobj, sublink_prop_name) and hasattr(selfobj, obj_property_name) and hasattr(selfobj, subnames_prop_name) ):
+ return
+
+ sl = sublinkFromApart(getattr(selfobj,obj_property_name), getattr(selfobj,subnames_prop_name))
+ if getattr(selfobj,sublink_prop_name) != sl: #assign only if actually changed, to prevent recursive onChanged calls
+ if prop == sublink_prop_name:
+ # update apart properties
+ tup_apart = (None, []) if getattr(selfobj,sublink_prop_name) is None else getattr(selfobj,sublink_prop_name)
+ if type(getattr(selfobj, subnames_prop_name)) is not list:
+ tup_apart = (tup_apart[0], tup_apart[1][0] if len(tup_apart[1]) == 1 else "")
+ setattr(selfobj, obj_property_name, tup_apart[0])
+ if tup_apart[0] is not None:
+ setattr(selfobj, subnames_prop_name, tup_apart[1])
+ else:
+ setattr(selfobj, sublink_prop_name, sl)
diff --git a/lattice2_rc.py b/lattice2_rc.py
index 46e1728..52b759e 100644
--- a/lattice2_rc.py
+++ b/lattice2_rc.py
@@ -2,16 +2,16 @@
# Resource object code
#
-# Created: Ср 15. июн 23:30:08 2016
+# Created: Чт 12. янв 03:38:06 2017
# by: The Resource Compiler for PySide (Qt v4.8.6)
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore
-qt_resource_data = "\x00\x00$\x12\x0a\x0a\x0a\x0a\x00\x00\x12\xce\x0a\x0a\x0a\x0a\x00\x006 \x0a\x0a\x0a\x0a\x00\x00\x13P\x0a\x0a\x0a\x0a\x00\x00#\x0d\x0a\x0a\x0a\x0a\x00\x00%}\x0a\x0a\x0a\x0a\x00\x00\x18\xe0\x0a\x0a\x0a\x0a\x00\x00\x16*\x0a\x0a\x0a\x0a\x00\x008v\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x005\xf0\x0a\x0a\x0a\x0a\x00\x00E\x1a\x0a\x0a\x0a\x0a\x00\x00+%\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00-\xa6\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x009Q\x0a\x0a\x0a\x0a\x00\x00#t\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00PD\x0a\x0a\x0a\x0a\x00\x00*\x0e\x0a\x0a\x0a\x0a\x00\x00/]\x0a\x0a\x0a\x0a\x00\x00?\x94\x0a\x0a\x0a\x0a\x00\x000\x10\x0a\x0a\x0a\x0a\x00\x00\x0a\x0a\x0a\x0a\x00\x00>\x17\x0a\x0a\x0a\x0a\x00\x00EV\x0a\x0a\x0a\x0a\x00\x00&\xe2\x0a\x0a\x0a\x0a\x00\x00\x1f\xc7\x0a\x0a\x0a\x0a\x00\x00%\x92\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00!\x06\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00B\xc2\x0a\x0a\x0a\x0a\x00\x00J\xa9\x0a\x0a\x0a\x0a\x00\x00D\xc9\x0a\x0a\x0a\x0a\x00\x00D\xcd\x0a\x0a\x0a\x0a\x00\x007\x08\x0a\x0a\x0a\x0a\x00\x00$\xa4\x0a\x0a\x0a\x0a\x00\x00\x1f\x03\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00\x1d\x9f\x0a\x0a\x0a\x0a\x00\x006A\x0a\x0a\x0a\x0a\x00\x00\x18\xe0\x0a\x0a\x0a\x0a\x00\x00+g\x0a\x0a\x0a\x0a\x00\x007\xba\x0a\x0a\x0a\x0a\x00\x00P\xdf\x0a\x0a\x0a\x0a\x00\x00do\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00J\xb2\x0a\x0a\x0a\x0a\x00\x00:\xf0\x0a\x0a\x0a\x0a\x00\x009M\x0a\x0a\x0a\x0a\x00\x00\x224\x0a\x0a\x0a\x0a\x00\x00#\x83\x0a\x0a\x0a\x0a\x00\x009\x04\x0a\x0a\x0a\x0a\x00\x00\x1d\xb9\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00=U\x0a\x0a\x0a\x0a\x00\x00 C\x0a\x0a\x0a\x0a\x00\x00!.\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00Q\x8b\x0a\x0a\x0a\x0a\x00\x00J\xd2\x0a\x0a\x0a\x0a"
-qt_resource_name = "\x00\x05\x00o\xa6S\x00i\x00c\x00o\x00n\x00s\x00\x1c\x09wnG\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00L\x00i\x00n\x00e\x00a\x00r\x00A\x00r\x00r\x00a\x00y\x00_\x00N\x00e\x00w\x00.\x00s\x00v\x00g\x00\x16\x0a\xbf\xa2\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00D\x00o\x00w\x00n\x00g\x00r\x00a\x00d\x00e\x00.\x00s\x00v\x00g\x00\x1b\x00\x1f\xc6\xc7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00A\x00r\x00r\x00a\x00y\x00F\x00r\x00o\x00m\x00S\x00h\x00a\x00p\x00e\x00.\x00s\x00v\x00g\x00\x19\x01\xd9\xdf\xa7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00F\x00u\x00s\x00e\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00 \x0b\xec\xceg\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00A\x00t\x00t\x00a\x00c\x00h\x00a\x00b\x00l\x00e\x00P\x00l\x00a\x00c\x00e\x00m\x00e\x00n\x00t\x00.\x00s\x00v\x00g\x00\x1e\x05c\xeb\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00B\x00o\x00u\x00n\x00d\x00B\x00o\x00x\x00_\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00\x14\x0e4\xfb\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00L\x00i\x00n\x00k\x00.\x00s\x00v\x00g\x00\x18\x03\xfb\xfd\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00h\x00a\x00p\x00e\x00S\x00t\x00r\x00i\x00n\x00g\x00.\x00s\x00v\x00g\x00\x17\x00.\xc7G\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00o\x00l\x00a\x00r\x00A\x00r\x00r\x00a\x00y\x00.\x00s\x00v\x00g\x00\x22\x04/\xebg\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00o\x00p\x00u\x00l\x00a\x00t\x00e\x00C\x00o\x00p\x00i\x00e\x00s\x00_\x00N\x00o\x00r\x00m\x00a\x00l\x00.\x00s\x00v\x00g\x00\x1d\x05}\xb5\x87\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00s\x00t\x00i\x00t\x00u\x00t\x00e\x00O\x00b\x00j\x00e\x00c\x00t\x00.\x00s\x00v\x00g\x00\x15\x00\x85\x84\x07\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00R\x00e\x00s\x00a\x00m\x00p\x00l\x00e\x00.\x00s\x00v\x00g\x00\x1f\x09\xd4\x7f'\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00l\x00a\x00c\x00e\x00m\x00e\x00n\x00t\x00F\x00r\x00o\x00m\x00S\x00h\x00a\x00p\x00e\x00.\x00s\x00v\x00g\x00\x19\x01\xde}\xa7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00M\x00a\x00k\x00e\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00\x14\x09\x0a\x0a\x0a\x0a\x00\x00\x12\xce\x0a\x0a\x0a\x0a\x00\x00'\xf2\x0a\x0a\x0a\x0a\x00\x006 \x0a\x0a\x0a\x0a\x00\x00\x13P\x0a\x0a\x0a\x0a\x00\x00$(\x0a\x0a\x0a\x0a\x00\x00#\xde\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00%}\x0a\x0a\x0a\x0a\x00\x00\x18\xe0\x0a\x0a\x0a\x0a\x00\x00\x16*\x0a\x0a\x0a\x0a\x00\x008v\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x005\xf0\x0a\x0a\x0a\x0a\x00\x00E\x1a\x0a\x0a\x0a\x0a\x00\x00+%\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00*\xce\x0a\x0a\x0a\x0a\x00\x00-\xa6\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00\x1cF\x0a\x0a\x0a\x0a\x00\x009Q\x0a\x0a\x0a\x0a\x00\x00#t\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00PD\x0a\x0a\x0a\x0a\x00\x00*\x0e\x0a\x0a\x0a\x0a\x00\x00/]\x0a\x0a\x0a\x0a\x00\x00?\x94\x0a\x0a\x0a\x0a\x00\x000\x10\x0a\x0a\x0a\x0a\x00\x00\x0a\x0a\x0a\x0a\x00\x00>\x17\x0a\x0a\x0a\x0a\x00\x00EV\x0a\x0a\x0a\x0a\x00\x00&\xe2\x0a\x0a\x0a\x0a\x00\x00\x1f\xc7\x0a\x0a\x0a\x0a\x00\x00%\x92\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00\x1c>\x0a\x0a\x0a\x0a\x00\x00!\x06\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00B\xc2\x0a\x0a\x0a\x0a\x00\x00J\xa9\x0a\x0a\x0a\x0a\x00\x00D\xc9\x0a\x0a\x0a\x0a\x00\x00D\xcd\x0a\x0a\x0a\x0a\x00\x007\x08\x0a\x0a\x0a\x0a\x00\x00$\xa4\x0a\x0a\x0a\x0a\x00\x00\x1f\x03\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00\x1e@\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00\x1b$\x0a\x0a\x0a\x0a\x00\x006A\x0a\x0a\x0a\x0a\x00\x00\x18\xe0\x0a\x0a\x0a\x0a\x00\x00+g\x0a\x0a\x0a\x0a\x00\x007\xba\x0a\x0a\x0a\x0a\x00\x00P\xdf\x0a\x0a\x0a\x0a\x00\x00do\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00J\xb2\x0a\x0a\x0a\x0a\x00\x00:\xf0\x0a\x0a\x0a\x0a\x00\x009M\x0a\x0a\x0a\x0a\x00\x00\x224\x0a\x0a\x0a\x0a\x00\x00#\x83\x0a\x0a\x0a\x0a\x00\x009\x04\x0a\x0a\x0a\x0a\x00\x00\x1d\xb9\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00=U\x0a\x0a\x0a\x0a\x00\x00 C\x0a\x0a\x0a\x0a\x00\x00!.\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x00\x00Q\x8b\x0a\x0a\x0a\x0a\x00\x00J\xd2\x0a\x0a\x0a\x0a"
+qt_resource_name = "\x00\x05\x00o\xa6S\x00i\x00c\x00o\x00n\x00s\x00\x1c\x09wnG\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00L\x00i\x00n\x00e\x00a\x00r\x00A\x00r\x00r\x00a\x00y\x00_\x00N\x00e\x00w\x00.\x00s\x00v\x00g\x00\x16\x0a\xbf\xa2\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00D\x00o\x00w\x00n\x00g\x00r\x00a\x00d\x00e\x00.\x00s\x00v\x00g\x00$\x08\xd5og\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00L\x00i\x00n\x00k\x00S\x00u\x00b\x00s\x00e\x00q\x00u\x00e\x00n\x00c\x00e\x00_\x00E\x00d\x00g\x00e\x00.\x00s\x00v\x00g\x00\x1b\x00\x1f\xc6\xc7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00A\x00r\x00r\x00a\x00y\x00F\x00r\x00o\x00m\x00S\x00h\x00a\x00p\x00e\x00.\x00s\x00v\x00g\x00\x19\x01\xd9\xdf\xa7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00F\x00u\x00s\x00e\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00&\x0f\xf0\x07\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00L\x00i\x00n\x00k\x00S\x00u\x00b\x00s\x00e\x00q\x00u\x00e\x00n\x00c\x00e\x00_\x00V\x00e\x00r\x00t\x00e\x00x\x00.\x00s\x00v\x00g\x00 \x0b\xec\xceg\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00A\x00t\x00t\x00a\x00c\x00h\x00a\x00b\x00l\x00e\x00P\x00l\x00a\x00c\x00e\x00m\x00e\x00n\x00t\x00.\x00s\x00v\x00g\x00\x1e\x05c\xeb\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00B\x00o\x00u\x00n\x00d\x00B\x00o\x00x\x00_\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00\x14\x0e4\xfb\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00L\x00i\x00n\x00k\x00.\x00s\x00v\x00g\x00\x18\x03\xfb\xfd\xe7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00h\x00a\x00p\x00e\x00S\x00t\x00r\x00i\x00n\x00g\x00.\x00s\x00v\x00g\x00\x17\x00.\xc7G\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00o\x00l\x00a\x00r\x00A\x00r\x00r\x00a\x00y\x00.\x00s\x00v\x00g\x00\x22\x04/\xebg\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00o\x00p\x00u\x00l\x00a\x00t\x00e\x00C\x00o\x00p\x00i\x00e\x00s\x00_\x00N\x00o\x00r\x00m\x00a\x00l\x00.\x00s\x00v\x00g\x00\x1d\x05}\xb5\x87\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00s\x00t\x00i\x00t\x00u\x00t\x00e\x00O\x00b\x00j\x00e\x00c\x00t\x00.\x00s\x00v\x00g\x00\x15\x00\x85\x84\x07\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00R\x00e\x00s\x00a\x00m\x00p\x00l\x00e\x00.\x00s\x00v\x00g\x00)\x0e\xe3\x9a\x07\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00A\x00t\x00t\x00a\x00c\x00h\x00e\x00d\x00P\x00l\x00a\x00c\x00e\x00m\x00e\x00n\x00t\x00S\x00u\x00b\x00s\x00e\x00q\x00u\x00e\x00n\x00c\x00e\x00.\x00s\x00v\x00g\x00\x1f\x09\xd4\x7f'\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00P\x00l\x00a\x00c\x00e\x00m\x00e\x00n\x00t\x00F\x00r\x00o\x00m\x00S\x00h\x00a\x00p\x00e\x00.\x00s\x00v\x00g\x00$\x05\x95o\x87\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00S\x00u\x00b\x00L\x00i\x00n\x00k\x00S\x00u\x00b\x00s\x00e\x00q\x00u\x00e\x00n\x00c\x00e\x00_\x00F\x00a\x00c\x00e\x00.\x00s\x00v\x00g\x00\x19\x01\xde}\xa7\x00L\x00a\x00t\x00t\x00i\x00c\x00e\x002\x00_\x00M\x00a\x00k\x00e\x00C\x00o\x00m\x00p\x00o\x00u\x00n\x00d\x00.\x00s\x00v\x00g\x00\x14\x09\x00\x00\x00\x00\x00\x01\x00\x01\xf8\x83\x00\x00\x0e\xbc\x00\x00\x00\x00\x00\x01\x00\x0a\x85-\x00\x00\x0a\x1a\x00\x00\x00\x00\x00\x01\x00\x06\xea\xc1\x00\x00\x08\x98\x00\x00\x00\x00\x00\x01\x00\x05\xba\xc2\x00\x00\x07\x80\x00\x00\x00\x00\x00\x01\x00\x05\x15\x1a\x00\x00\x04\xec\x00\x00\x00\x00\x00\x01\x00\x03E\x87\x00\x00\x0d\xc8\x00\x00\x00\x00\x00\x01\x00\x09\xe8\x9e\x00\x00\x01\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x95\x02\x00\x00\x04X\x00\x00\x00\x00\x00\x01\x00\x02\x98r\x00\x00\x08\xf4\x00\x00\x00\x00\x00\x01\x00\x06\x05o\x00\x00\x0b\x18\x00\x00\x00\x00\x00\x01\x00\x07\x92]\x00\x00\x09\x90\x00\x00\x00\x00\x00\x01\x00\x06\x8f\x0d\x00\x00\x05(\x00\x00\x00\x00\x00\x01\x00\x03o\x99\x00\x00\x02J\x00\x00\x00\x00\x00\x01\x00\x01.\xc9\x00\x00\x02\xb4\x00\x00\x00\x00\x00\x01\x00\x01}q\x00\x00\x0a\xe0\x00\x00\x00\x00\x00\x01\x00\x07yy\x00\x00\x05\xc2\x00\x00\x00\x00\x00\x01\x00\x03\xde\x92\x00\x00\x07\x10\x00\x00\x00\x00\x00\x01\x00\x04\xcei\x00\x00\x08\x0e\x00\x00\x00\x00\x00\x01\x00\x05V\xf2\x00\x00\x0dz\x00\x00\x00\x00\x00\x01\x00\x09\xaf\x96\x00\x00\x01\xda\x00\x00\x00\x00\x00\x01\x00\x00\xf0d\x00\x00\x02\xfe\x00\x00\x00\x00\x00\x01\x00\x01\xb3e\x00\x00\x06f\x00\x00\x00\x00\x00\x01\x00\x04J\xf4\x00\x00\x04\x0a\x00\x00\x00\x00\x00\x01\x00\x02|(\x00\x00\x04\xbe\x00\x00\x00\x00\x00\x01\x00\x02\xf5?\x00\x00\x0dN\x00\x00\x00\x00\x00\x01\x00\x09\x8c\x0f\x00\x00\x0aJ\x00\x00\x00\x00\x00\x01\x00\x07\x09\xc8\x00\x00\x0a\xa8\x00\x00\x00\x00\x00\x01\x00\x07C4\x00\x00\x05|\x00\x00\x00\x00\x00\x01\x00\x03\x9e\xfa\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x006\xe8\x00\x00\x09\xe2\x00\x00\x00\x00\x00\x01\x00\x06\xc6\x19\x00\x00\x04\x90\x00\x00\x00\x00\x00\x01\x00\x02\xd1\xc7\x00\x00\x0c\x96\x00\x00\x00\x00\x00\x01\x00\x08\xf5\x92\x00\x00\x0d\xfa\x00\x00\x00\x00\x00\x01\x00\x0a\x06[\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xc6\x00\x00\x00\x00\x00\x01\x00\x02N~\x00\x00\x00N\x00\x00\x00\x00\x00\x01\x00\x00$\x16\x00\x00\x06\xba\x00\x00\x00\x00\x00\x01\x00\x04\x89\x0f\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\xcc\x82\x00\x00\x0bL\x00\x00\x00\x00\x00\x01\x00\x07\xbd\xc8\x00\x00\x0c\xd2\x00\x00\x00\x00\x00\x01\x00\x090\x86\x00\x00\x0f\x1c\x00\x00\x00\x00\x00\x01\x00\x0a\xd6\xbc\x00\x00\x02\x1c\x00\x00\x00\x00\x00\x01\x00\x01\x15\xe5\x00\x00\x0d\x1a\x00\x00\x00\x00\x00\x01\x00\x09i\xd7\x00\x00\x0e\x86\x00\x00\x00\x00\x00\x01\x00\x0ac\xfb\x00\x00\x09@\x00\x00\x00\x00\x00\x01\x00\x06J<\x00\x00\x03n\x00\x00\x00\x00\x00\x01\x00\x02#\xac\x00\x00\x0eJ\x00\x00\x00\x00\x00\x01\x00\x0aC\xb4\x00\x00\x0c6\x00\x00\x00\x00\x00\x01\x00\x08\xaa\xdc\x00\x00\x0b\xda\x00\x00\x00\x00\x00\x01\x00\x08Fi\x00\x00\x08N\x00\x00\x00\x00\x00\x01\x00\x05w\xfc\x00\x00\x0b\xa2\x00\x00\x00\x00\x00\x01\x00\x07\xf5\x86\x00\x00\x0at\x00\x00\x00\x00\x00\x01\x00\x07(\x0c\x00\x00\x01B\x00\x00\x00\x00\x00\x01\x00\x00\xa8V\x00\x00\x06\x1a\x00\x00\x00\x00\x00\x01\x00\x04\x0e\xa6"
def qInitResources():
QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)