diff --git a/latticeBaseFeature.py b/latticeBaseFeature.py index d309180..6a64228 100644 --- a/latticeBaseFeature.py +++ b/latticeBaseFeature.py @@ -25,11 +25,12 @@ __title__="Base feature module for lattice object of lattice workbench for FreeC __author__ = "DeepSOIC" __url__ = "" +import FreeCAD as App import Part -from pivy import coin from latticeCommon import * import latticeCompoundExplorer as LCE +import latticeMarkers def makeLatticeFeature(name, AppClass, icon, ViewClass = None): '''makeLatticeFeature(name, AppClass, ViewClass = None): makes a document object for a LatticeFeature-derived object.''' @@ -42,16 +43,47 @@ def makeLatticeFeature(name, AppClass, icon, ViewClass = None): vp.icon = icon 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 documentObject.isLattice == 'True': + ret = True + return ret + +def getMarkerSizeEstimate(ListOfPlacements): + '''getMarkerSizeEstimate(ListOfPlacements): computes the default marker size for the array of placements''' + 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: + sz = 1.0 + return sz + + + class LatticeFeature(): "Base object for lattice objects (arrays of placements)" def __init__(self,obj): + # please, don't override. Override derivedInit instead. self.Type = "latticeFeature" prop = "NumElements" obj.addProperty("App::PropertyInteger",prop,"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","isLattice","Lattice","Sets whether this object should be treated as a lattice by further operations") + obj.isLattice = ['Auto','False','True'] + self.derivedInit(obj) obj.Proxy = self @@ -62,14 +94,53 @@ class LatticeFeature(): pass def execute(self,obj): - derivedExecute(self, obj) - obj.NumElements = LCE.CalculateNumberOfLeaves(obj.Shape) + # please, don't override. Override derivedExecute instead. + + plms = self.derivedExecute(obj) + + if plms is not None: + obj.NumElements = len(plms) + shapes = [] + markerSize = obj.MarkerSize + if markerSize < DistConfusion: + markerSize = getMarkerSizeEstimate(plms) + marker = latticeMarkers.getPlacementMarker(scale=markerSize) + #FIXME: make hierarchy-aware + for plm in plms: + sh = marker.copy() + sh.Placement = plm + shapes.append(sh) + + if len(shapes) == 0: + obj.Shape = markers.getNullShapeShape(markerSize) + raise ValueError('Lattice object is null') #Feeding empty compounds to FreeCAD seems to cause rendering issues, otherwise it would have been a good idea to output nothing. + + obj.Shape = Part.makeCompound(shapes) + + if obj.isLattice == 'Auto': + obj.isLattice = 'True' + + 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': + obj.isLattice = 'False' + obj.NumElements = len(obj.Shape.childShapes(False,False)) return - def derivedExecute(): - '''For overriding by derived class''' - pass + 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.''' + return [] + + def verifyIntegrity(self,obj): + 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") class ViewProviderLatticeFeature: @@ -78,10 +149,9 @@ class ViewProviderLatticeFeature: def __init__(self,vobj): vobj.Proxy = self # vobj.DisplayMode = "Markers" - vobj.PointSize = 4 - vobj.PointColor = (1.0, 0.7019608020782471, 0.0, 0.0) #orange def getIcon(self): + self.Object.Proxy.verifyIntegrity(self.Object) if hasattr(self, "icon"): if self.icon: return getIconPath(self.icon) @@ -111,6 +181,7 @@ class ViewProviderLatticeFeature: try: children = self.claimChildren() if children and len(children) > 0: + marker = latticeMarkers for child in children: child.ViewObject.show() except Exception as err: diff --git a/latticeCompose.py b/latticeCompose.py index 81304ea..97367de 100644 --- a/latticeCompose.py +++ b/latticeCompose.py @@ -68,8 +68,8 @@ class Compose(latticeBaseFeature.LatticeFeature): obj.ToolFlattenHierarchy = True - def execute(self,obj): - # Fill in (update read-only) properties that are driven by the mode. + def derivedExecute(self,obj): + # cache stuff base = obj.Base.Shape if base.ShapeType != 'Compound': base = Part.makeCompound([base]) @@ -87,45 +87,82 @@ class Compose(latticeBaseFeature.LatticeFeature): toolChildren = tool.childShapes() iBase = 0 - isMult = obj.Operation == 'MultiplyPlacements' # cache comparisons to speed them up + isMult = obj.Operation == 'MultiplyPlacements' # cache mode comparisons to speed them up isAvg = obj.Operation == 'AveragePlacements' isIgnore = obj.Operation == 'IgnoreBasePlacements' - isOvrride = obj.Operation == 'OverrideBasePlacements' - rst = [] + isOverride = obj.Operation == 'OverrideBasePlacements' + + #mode validity logic + if not latticeBaseFeature.isObjectLattice(obj.Tool): + FreeCAD.Console.PrintWarning(obj.Name+': Tool is not a lattice object. Results may be unexpected.\n') + outputIsLattice = latticeBaseFeature.isObjectLattice(obj.Base) + if isOverride and outputIsLattice: + FreeCAD.Console.PrintWarning(obj.Name+': Base is a lattice object. OverrideBasePlacements operation requires a generic compound as Base. So, the lattice is being treated as a generic compound.\n') + outputIsLattice = False + + # initialize output containers and loop variables + outputShapes = [] #output list of shapes + outputPlms = [] #list of placements bFirst = True plmMatcher = App.Placement() #extra placement, that aligns first tool member and first base member + + + # the essence for toolChild in toolChildren: - sh = baseChildren[iBase].copy() - if bFirst: - bFirst = False - if obj.BaseKeepPosOfFirst: - plmMatcher = toolChild.Placement.inverse() - toolPlm = plmMatcher.multiply(toolChild.Placement) - if isMult: - sh.Placement = toolPlm.multiply(sh.Placement) - elif isAvg: - plm1 = toolPlm - plm2 = sh.Placement - transl = plm1.Base*0.5 + plm2.Base*0.5 - a1,b1,c1,d1 = plm1.Rotation.Q - a2,b2,c2,d2 = plm2.Rotation.Q - rot = App.Rotation((a1+a2,b1+b2,c1+c2,d1+d2)) #no divide-by-two, because FreeCAD will normalize the quaternion automatically - sh.Placement = App.Placement(transl,rot) - elif isIgnore: - sh.Placement = toolPlm - elif isOverride: - sh.transformShape(toolPlm.inverse.multiply(sh.Placement)) - sh.Placement = toolPlm - rst.append(sh) - iBase += 1 + # early test for termination if iBase > len(baseChildren)-1: if obj.BaseLoopSequence: iBase = 0 else: + FreeCAD.Console.PrintWarning(obj.Name+': There are '+str(len(toolChildren)-len(baseChildren))+ + ' more placements in Tool than children in Base. Those placements'+ + ' were dropped.\n') break - - obj.Shape = Part.makeCompound(rst) + + #cache some stuff + basePlm = baseChildren[iBase].Placement + toolPlm = toolChild.Placement + + if not outputIsLattice: + outputShape = baseChildren[iBase].copy() + + #prepare alignment placement + if bFirst: + bFirst = False + if obj.BaseKeepPosOfFirst: + plmMatcher = toolPlm.inverse() + + #mode logic + if isMult: + outPlm = toolPlm.multiply(plmMatcher.multiply(basePlm)) + elif isAvg: + plm1 = toolPlm + plm2 = pltMatcher.multiply(basePlm) + transl = plm1.Base*0.5 + plm2.Base*0.5 + a1,b1,c1,d1 = plm1.Rotation.Q + a2,b2,c2,d2 = plm2.Rotation.Q + rot = App.Rotation((a1+a2,b1+b2,c1+c2,d1+d2)) #no divide-by-two, because FreeCAD will normalize the quaternion automatically + outPlm = App.Placement(transl,rot) + elif isIgnore: + outPlm = toolPlm + elif isOverride: + assert(not outputIsLattice) + outputShape.transformShape(toolPlm.inverse.multiply(plmMatcher.multiply(basePlm))) + outPlm = toolPlm + + if outputIsLattice: + outputPlms.append(outPlm) + else: + outputShape.Placement = outPlm + outputShapes.append(outputShape) + + iBase += 1 + + if outputIsLattice: + return outputPlms + else: + obj.Shape = Part.makeCompound(outputShapes) class ViewProviderCompose(latticeBaseFeature.ViewProviderLatticeFeature): diff --git a/latticeCompoundExplorer.py b/latticeCompoundExplorer.py index a84ac01..94b689c 100644 --- a/latticeCompoundExplorer.py +++ b/latticeCompoundExplorer.py @@ -144,7 +144,7 @@ def CalculateNumberOfLeaves(compound): return cnt def AllLeaves(compound): - 'AllLeaves(compound): Traverses the compound and collects all the leaves into a single list' + 'AllLeaves(compound): Traverses the compound and collects all the leaves into a single list. Returns list of shapes.' output = [] for (child, msg, it) in CompoundExplorer(compound): if msg == it.MSG_LEAF: diff --git a/latticeMarkers.py b/latticeMarkers.py index dfde102..7f2dc4a 100644 --- a/latticeMarkers.py +++ b/latticeMarkers.py @@ -29,6 +29,7 @@ __url__ = "" __doc__ = "Module for loading marker shapes for Lattice workbench" _nullShapeShape = 0 +_ShapeDict = {} def getShapePath(shapeName): """ @@ -56,3 +57,31 @@ def getNullShapeShape(scale = 1.0): ret.scale(scale) return ret + +def loadShape(shapeID): + global _ShapeDict + sh = _ShapeDict.get(shapeID) + if sh is None: + try: + sh = Part.Shape() + f = open(getShapePath(shapeID + '.brep')) + sh.importBrep(f) + f.close() + except Exception as err: + FreeCAD.Console.PrintError('Failed to load standard shape "'+shapeID+'". \n' + err.message + '\n') + sh = Part.Point() #Create at least something! + _ShapeDict[shapeID] = sh + return sh + + +def getPlacementMarker(scale = 1.0, markerID = None): + '''getPlacementMarker(scale = 1.0, markerID = None): returns a placement marker shape. + The shape is scaled according to "scale" argument. + markerID sets the marker file name. If omitted, default placement marker is returned.''' + if markerID is None: + markerID = 'tetra-orimarker' + sh = loadShape(markerID) + if scale != 1.0: + sh = sh.copy() + sh.scale(scale) + return sh diff --git a/latticePolarArray.py b/latticePolarArray.py index 8397f9d..a76694b 100644 --- a/latticePolarArray.py +++ b/latticePolarArray.py @@ -86,7 +86,7 @@ class PolarArray(latticeBaseFeature.LatticeFeature): obj.setEditorMode("AxisLinkIgnorePoint", 0 if obj.AxisLink else 1) - def execute(self,obj): + def derivedExecute(self,obj): # Fill in (update read-only) properties that are driven by the mode. self.updateReadOnlyness(obj) if obj.Mode == 'SpanN': @@ -151,27 +151,16 @@ class PolarArray(latticeBaseFeature.LatticeFeature): overallPlacement = App.Placement(obj.AxisPoint, rot_ini) # Make the array - rst = [] # Shapes for holding placements (each shape is a vertex with placement) + output = [] # list of placements for i in range(0, n): ang = startAng + step*i p = Part.Vertex() localrot = App.Rotation(App.Vector(0,0,1), ang) localtransl = localrot.multVec(App.Vector(radius,0,0)) localplm = App.Placement(localtransl, localrot) - p.Placement = overallPlacement.multiply(localplm) - rst.append(p) + output.append( overallPlacement.multiply(localplm) ) - # Make final compound (or empty-set marker) - if len(rst) == 0: - scale = 1.0 - if not obj.Base.Shape.isNull(): - scale = abs(obj.Radius) - if scale < DistConfusion * 100: - scale = 1.0 - obj.Shape = markers.getNullShapeShape(scale) - raise ValueError('Array output is null') #Feeding empty compounds to FreeCAD seems to cause rendering issues, otherwise it would have been a good idea to output nothing. - - obj.Shape = Part.makeCompound(rst) + return output else: raise ValueError("Spreadsheet mode not implemeted yet") diff --git a/shapes/tetra-orimarker.FCStd b/shapes/tetra-orimarker.FCStd new file mode 100644 index 0000000..4ed8e4f Binary files /dev/null and b/shapes/tetra-orimarker.FCStd differ diff --git a/shapes/tetra-orimarker.brep b/shapes/tetra-orimarker.brep new file mode 100644 index 0000000..8682af4 --- /dev/null +++ b/shapes/tetra-orimarker.brep @@ -0,0 +1,249 @@ +DBRep_DrawableShape + +CASCADE Topology V1, (c) Matra-Datavision +Locations 15 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +1 + 0 0 -1 -0 + -1 0 0 -0 + 0 1 0 0 +2 4 1 3 1 0 +2 3 -1 4 -1 0 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +1 +-0.978231976089037 2.77555756156289e-017 0.207514339159822 0.043062200956938 +0.146734796413355 -0.707106781186547 0.691714463866075 0.14354066985646 +0.146734796413356 0.707106781186547 0.691714463866075 0.14354066985646 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +2 8 1 9 1 0 +2 9 -1 8 -1 0 +1 +0.978231976089037 2.77555756156289e-017 0.207514339159822 0.043062200956938 +0.146734796413355 0.707106781186547 -0.691714463866075 -0.14354066985646 +-0.146734796413356 0.707106781186547 0.691714463866075 0.14354066985646 +1 + 1 0 0 0 + 0 1 0 0 + 0 0 1 0 +2 12 1 13 1 0 +2 13 -1 12 -1 0 +Curve2ds 0 +Curves 7 +1 0 0 0 1 0 0 +1 -0.29999999999998128 -4.2695787882546599e-015 0 1 1.4231929294183087e-014 0 +1 0.29999999999999999 0 0 -0.70710678118654524 0.70710678118654957 0 +1 2.2233792906188499e-017 0.30000000000000182 0 -0.70710678118651826 -0.70710678118657666 0 +1 0.044020438924006873 -0.21213203435596539 0 -0.9791402332302942 0.20318563844357879 0 +1 -0.04402043892400688 -0.21213203435596534 0 0.9791402332302942 0.20318563844357873 0 +1 -0.97823197608904322 1.214306433183765e-016 0 0.97914023323029442 0.20318563844357873 0 +Polygon3D 0 +PolygonOnTriangulations 14 +2 1 2 +p 0.00316666666666669 1 0 0.3 +2 2 4 +p 0.00316666666666669 1 0 0.3 +2 3 1 +p 0.00316666666666669 1 0 0.299999999999981 +2 1 2 +p 0.00316666666666669 1 0 0.299999999999981 +2 2 4 +p 0.00316666666666669 1 0 0.42426406871193 +2 1 3 +p 0.00316666666666669 1 0 0.42426406871193 +2 4 3 +p 0.00316666666666669 1 0 0.42426406871192 +2 2 3 +p 0.00316666666666669 1 0 0.42426406871192 +2 1 3 +p 0.00316666666666669 1 0 1.04403065089106 +2 3 1 +p 0.00316666666666669 1 0 1.04403065089106 +2 4 3 +p 0.00316666666666669 1 0 1.04403065089106 +2 1 2 +p 0.00316666666666669 1 0 1.04403065089106 +2 2 3 +p 0.00316666666666669 1 0 1.04403065089106 +2 1 2 +p 0.00316666666666669 1 0 1.04403065089106 +Surfaces 4 +1 0 0 0 1 0 -0 0 0 1 0 -1 0 +1 0 0 0 0 0 1 1 0 -0 -0 1 0 +1 0.41556347085957146 -0.087665479371065044 0.087665479371065141 -0.20751433915982237 0.6917144638660746 -0.69171446386607471 0 -0.70710678118654757 -0.70710678118654746 -0.978231976089037 -0.14673479641335552 0.14673479641335554 +1 0.41556347085957152 0.087665479371065058 0.087665479371065114 -0.20751433915982231 -0.69171446386607471 -0.69171446386607471 0 -0.70710678118654757 0.70710678118654757 -0.97823197608903711 0.14673479641335549 0.14673479641335549 +Triangulations 4 +4 2 1 4.2491376305591e-018 +0 0 0 -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 0 0 1.38777878078145e-017 0.300000000000001 -2.13598435909256e-015 -0.299999999999991 0.300000000000002 0 4 2 1 4 1 3 +4 2 1 7.18933680268093e-016 +1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 0 0 0 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.73472347597681e-018 0.299999999999992 0 0 1.00000000000001 0 -1.73472347597681e-018 -0.300000000000001 3 1 2 3 2 4 +3 1 1 6.93889390390723e-017 +-1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 0.212132034355965 0.424810761677399 8.32667268468867e-017 -0.59744165333565 -0.212132034355965 0.4248107616774 1 3 2 +3 1 1 2.692290834716e-015 +1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 -2.77555756156289e-017 -0.59744165333565 0.212132034355966 0.4248107616774 -0.21213203435596 0.424810761677398 2 3 1 + +TShapes 23 +Ve +1e-007 +0 0 0 +0 0 + +0101101 +* +Ve +1.00000000839442e-007 +-1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 +0 0 + +0101101 +* +Ed + 1e-007 1 1 0 +1 1 0 0 0.3 +6 1 1 6 +6 2 2 6 +0 + +0101000 ++23 6 -22 6 * +Ve +1.00000010384143e-007 +1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 +0 0 + +0101101 +* +Ed + 1e-007 1 1 0 +1 2 0 0 0.299999999999981 +6 3 1 6 +6 4 2 6 +0 + +0101000 ++20 6 -23 6 * +Ve +1.00000000178043e-007 +-1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 +0 0 + +0101101 +* +Ed + 1e-007 1 1 0 +1 3 0 0 0.42426406871193 +6 5 1 6 +6 6 3 6 +0 + +0101000 ++22 6 -18 6 * +Ed + 1e-007 1 1 0 +1 4 0 0 0.42426406871192 +6 7 1 6 +6 8 4 6 +0 + +0101000 ++18 6 -20 6 * +Wi + +0101000 +-21 5 -19 5 -17 5 -16 5 * +Fa +0 1e-007 1 3 +2 1 +0111000 ++15 0 * +Ve +1.00000000466544e-007 +1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 +0 0 + +0101101 +* +Ed + 1e-007 1 1 0 +1 5 0 0 1.04403065089106 +6 9 2 11 +6 10 4 11 +0 + +0101000 ++20 11 -13 11 * +Ed + 1e-007 1 1 0 +1 6 0 0 1.04403065089106 +6 11 2 15 +6 12 3 15 +0 + +0101000 ++22 15 -13 15 * +Wi + +0101000 ++19 5 -12 10 +21 5 +11 14 * +Fa +0 1e-007 2 7 +2 2 +0111000 ++10 0 * +Ed + 1e-007 1 1 0 +1 7 0 0 1.04403065089106 +6 13 3 11 +6 14 4 11 +0 + +0101000 ++13 11 -18 11 * +Wi + +0101000 +-11 14 +17 5 -8 10 * +Fa +0 1e-007 3 13 +2 3 +0111000 ++7 0 * +Wi + +0101000 ++8 10 +12 10 +16 5 * +Fa +0 1e-007 4 9 +2 4 +0111000 ++5 0 * +Sh + +0101000 ++14 2 +9 2 +6 2 +4 2 * +So + +0100000 ++3 0 * +Co + +1100000 +-2 1 * + ++1 0 \ No newline at end of file