def derivedExecute(self,obj): self.assureGenerator(obj) self.updateReadonlyness(obj) # Apply links if obj.AxisLink: if lattice2BaseFeature.isObjectLattice(obj.AxisLink): lattice2Executer.warning(obj,"For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape.") #resolve the link if len(obj.AxisLinkSubelement) > 0: linkedShape = obj.AxisLink.Shape.getElement(obj.AxisLinkSubelement) else: linkedShape = obj.AxisLink.Shape #Type check if linkedShape.ShapeType != 'Edge': raise ValueError('Axis link must be an edge; it is '+linkedShape.ShapeType+' instead.') #prepare dir = App.Vector() point = App.Vector() if isinstance(linkedShape.Curve, Part.Line): dir = linkedShape.Curve.EndPoint - linkedShape.Curve.StartPoint point = linkedShape.Curve.StartPoint elif isinstance(linkedShape.Curve, Part.Circle): dir = linkedShape.Curve.Axis point = linkedShape.Curve.Center else: raise ValueError("Edge " + repr(linkedShape) + " can't be used to derive an axis. It must be either a line or a circle/arc.") #apply if obj.AxisDirIsDriven: obj.AxisDir = dir if obj.AxisPointIsDriven: obj.AxisPoint = point self.generator.execute() # cache properties into variables radius = float(obj.Radius) values = [float(strv) for strv in obj.Values] # compute initial vector. It is to be perpendicular to Axis rot_ini = lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= obj.AxisDir) overallPlacement = App.Placement(obj.AxisPoint, rot_ini) # Make the array output = [] # list of placements for ang in values: 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) resultplm = overallPlacement.multiply(localplm) if obj.OrientMode == 'None': resultplm.Rotation = App.Rotation() output.append(resultplm) return output
def cmdExposeLinkSubs(): sel = FreeCADGui.Selection.getSelectionEx() if len(sel) != 1: raise SelectionError( "Bad selection", "Select one object, first! You have selected %i objects".replace("%i",str(len(sel))) ) App.ActiveDocument.openTransaction("Expose LinkSub of "+sel[0].Object.Name) obj = sel[0].Object cnt = 0 try: for propname in obj.PropertiesList: if 'App::PropertyLinkSub' in obj.getTypeIdOfProperty(propname): if getattr(obj,propname) is None: continue try: if obj.isDerivedFrom("Part::Part2DObject") and propname == "Support": if False == askYesNo("Support", "ExposeLinkSub is about to expose Support link of %feat. This will cause PartDesign additive operations to start new objects instead of adding to support solid, and subtractive PartDesign operations to fail. Expose the support link?" .replace("%feat",obj.Label)): continue ExposeLinkSub(obj, propname) cnt += 1 except Exception as err: Executer.warning(None,"Attempting to expose sublink property {prop} of {feat} caused an error:\n{err}" .format(prop= propname, feat= obj.Name, err= str(err)) ) if cnt == 0: raise ValueError("No links to expose were found.") except Exception: App.ActiveDocument.abortTransaction() raise
def cmdExposeLinkSubs(): sel = FreeCADGui.Selection.getSelectionEx() if len(sel) != 1: raise SelectionError( "Bad selection", "Select one object, first! You have selected %i objects".replace("%i",str(len(sel))) ) App.ActiveDocument.openTransaction("Expose LinkSub of "+sel[0].Object.Name) obj = sel[0].Object cnt = 0 try: for propname in obj.PropertiesList: if 'App::PropertyLinkSub' in obj.getTypeIdOfProperty(propname): if getattr(obj,propname) is None: continue try: if obj.isDerivedFrom("Part::Part2DObject") and propname == "Support": if False == askYesNo("Support", "ExposeLinkSub is about to expose Support link of %feat. This will cause PartDesign additive operations to start new objects instead of adding to support solid, and subtractive PartDesign operations to fail. Expose the support link?" .replace("%feat",obj.Label)): continue ExposeLinkSub(obj, propname) cnt += 1 except Exception as err: Executer.warning(None,"Attempting to expose sublink property %prop of %feat caused an error:\n%err" .replace("%prop",propname) .replace("%feat",obj.Name) .replace("%err",err.message) ) if cnt == 0: raise ValueError("No links to expose were found.") except Exception: App.ActiveDocument.abortTransaction() raise
def Activated(self): try: if len(FreeCADGui.Selection.getSelection()) == 0: infoMessage( "Make compound", "Make compound command. Combines several shapes into one. The shapes are kept as-is. They are not fused together, and can be extracted unchanged.\n\n" + "Compounds can contain combination of shapes of any topology: one can compound some edges with some solids. Compound is effectively another kind of group. But unlike normal FreeCAD group, compound only accepts OCC geometry. Compound cannot include meshes, dimensions, labels, or other objects that provide no Shape property.\n\n" + "Note that compounds that have objects that touch or intersect are considered invalid by Part CheckGeometry. Such invalid compounds cannot be used for Part Cut/Common/Fuse." ) return oldVal = lattice2Executer.globalIsCreatingLatticeFeature lattice2Executer.globalIsCreatingLatticeFeature = True sel = FreeCADGui.Selection.getSelectionEx() for s in sel: if isObjectLattice(s.Object): lattice2Executer.warning( None, "For making a compound, generic shapes are expected, but some of the selected objects are placements/arrays of placements. These will be treated as generic shapes; results may be unexpected." ) break FreeCADGui.runCommand("Part_Compound") except Exception as err: msgError(err) finally: lattice2Executer.globalIsCreatingLatticeFeature = oldVal
def DereferenceArray(obj,placements, lnkFrom, refmode): '''common implementation of treatment Referencing property. Returns a list of placements to use directly. obj - feature being executed (used for error reporting; can be None) placements - the array, converted into a list of placements. lnkFrom - object linked as a lattice of 'from' placements. Can be None, if mode is not 'Use PlacemenetsFrom' refmode - a string - enum property item''' plmDeref = App.Placement() #inverse placement of reference (reference is a substitute of origin) if lnkFrom is not None and refmode != "Use PlacementsFrom": lattice2Executer.warning(obj,"Referencing mode is '"+refmode+"', doesn't need PlacementsFrom link to be set. The link is set, but it will be ignored.") if refmode == "Origin": return placements elif refmode == "First item": plmDeref = placements[0].inverse() elif refmode == "Last item": plmDeref = placements[0].inverse() elif refmode == "Use PlacementsFrom": if lnkFrom is None: raise ValueError("Referencing mode is 'Move from to', but PlacementsFrom link is not set.") placementsFrom = lattice2BaseFeature.getPlacementsList(lnkFrom, obj) if len(placementsFrom) == 1: plmDeref = placementsFrom[0].inverse() elif len(placementsFrom) == len(placements): return [lattice2BaseFeature.makeMoveFromTo(placementsFrom[i], placements[i]) for i in range(0, len(placements))] else: lattice2Executer.warning(obj,"Lengths of arrays linked as PlacementsTo and PlacementsFrom must equal, or PlacementsFrom can be one placement. Violation: lengths are "+str(len(placements))+ " and "+str(len(placementsFrom))) else: raise ValueError("Referencing mode not implemented: "+refmode) return [plm.multiply(plmDeref) for plm in placements]
def derivedExecute(self,obj): #validity check if not lattice2BaseFeature.isObjectLattice(obj.Base): lattice2Executer.warning(obj,"A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected.") output = [] #variable to receive the final list of placements leaves = LCE.AllLeaves(obj.Base.Shape) input = [leaf.Placement for leaf in leaves] if obj.FilterType == 'bypass': output = input elif obj.FilterType == 'specific items': flags = [False] * len(input) ranges = obj.items.split(';') for r in ranges: r_v = r.split(':') if len(r_v) == 1: i = int(r_v[0]) output.append(input[i]) flags[i] = True elif len(r_v) == 2 or len(r_v) == 3: if len(r_v) == 2: r_v.append("") # fix issue #1: instead of checking length here and there, simply add the missing field =) ifrom = None if len(r_v[0].strip()) == 0 else int(r_v[0]) ito = None if len(r_v[1].strip()) == 0 else int(r_v[1]) istep = None if len(r_v[2].strip()) == 0 else int(r_v[2]) output=output+input[ifrom:ito:istep] for b in flags[ifrom:ito:istep]: b = True else: raise ValueError('index range cannot be parsed:'+r) if obj.Invert : output = [] for i in xrange(0,len(input)): if not flags[i]: output.append(input[i]) elif obj.FilterType == 'collision-pass': stencil = obj.Stencil.Shape for plm in input: pnt = Part.Vertex(plm.Base) d = pnt.distToShape(stencil) if bool(d[0] < DistConfusion) ^ bool(obj.Invert): output.append(plm) elif obj.FilterType == 'window-distance': vals = [0.0] * len(input) for i in xrange(0,len(input)): if obj.FilterType == 'window-distance': pnt = Part.Vertex(input[i].Base) vals[i] = pnt.distToShape(obj.Stencil.Shape)[0] valFrom = obj.WindowFrom valTo = obj.WindowTo for i in xrange(0,len(input)): if bool(vals[i] >= valFrom and vals[i] <= valTo) ^ obj.Invert: output.append(input[i]) else: raise ValueError('Filter mode not implemented:'+obj.FilterType) return output
def getPlacementsList(documentObject, context=None, suppressWarning=False): """getPlacementsList(documentObject, context = None): extract list of placements from an array object. Context is an object to report as context, when displaying a warning if the documentObject happens to be a non-lattice.""" if not isObjectLattice(documentObject): if not suppressWarning: lattice2Executer.warning( context, documentObject.Name + " is not a placement or an array of placements. Results may be unexpected.", ) leaves = LCE.AllLeaves(documentObject.Shape) return [leaf.Placement for leaf in leaves]
def getPlacementsList(documentObject, context=None, suppressWarning=False): '''getPlacementsList(documentObject, context = None): extract list of placements from an array object. Context is an object to report as context, when displaying a warning if the documentObject happens to be a non-lattice.''' if not isObjectLattice(documentObject): if not suppressWarning: lattice2Executer.warning( context, documentObject.Name + " is not a placement or an array of placements. Results may be unexpected." ) if documentObject.isDerivedFrom( 'App::Placement') or documentObject.isDerivedFrom( 'PartDesign::CoordinateSystem'): return [documentObject.Placement] leaves = LCE.AllLeaves(documentObject.Shape) return [leaf.Placement for leaf in leaves]
def execute(self,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]) 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: scale = 1.0 try: if selfobj.Object: scale = selfobj.Object[0].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. 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
def derivedExecute(self, obj): # cache stuff base = screen(obj.Base).Shape if not lattice2BaseFeature.isObjectLattice(screen(obj.Base)): lattice2Executer.warning( obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n" ) baseChildren = LCE.AllLeaves(base) #cache mode comparisons, for speed posIsInvert = obj.TranslateMode == 'invert' posIsKeep = obj.TranslateMode == 'keep' posIsReset = obj.TranslateMode == 'reset' oriIsInvert = obj.OrientMode == 'invert' oriIsKeep = obj.OrientMode == 'keep' oriIsReset = obj.OrientMode == 'reset' # initialize output containers and loop variables outputPlms = [] #list of placements # the essence for child in baseChildren: pos = App.Vector() ori = App.Rotation() inverted = child.Placement.inverse() if posIsInvert: pos = inverted.Base elif posIsKeep: pos = child.Placement.Base elif posIsReset: pass if oriIsInvert: ori = inverted.Rotation elif oriIsKeep: ori = child.Placement.Rotation elif oriIsReset: pass plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms
def derivedExecute(self, obj): # cache stuff base = obj.Base.Shape if not lattice2BaseFeature.isObjectLattice(obj.Base): lattice2Executer.warning( obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n" ) baseChildren = LCE.AllLeaves(base) # cache mode comparisons, for speed posIsInvert = obj.TranslateMode == "invert" posIsKeep = obj.TranslateMode == "keep" posIsReset = obj.TranslateMode == "reset" oriIsInvert = obj.OrientMode == "invert" oriIsKeep = obj.OrientMode == "keep" oriIsReset = obj.OrientMode == "reset" # initialize output containers and loop variables outputPlms = [] # list of placements # the essence for child in baseChildren: pos = App.Vector() ori = App.Rotation() inverted = child.Placement.inverse() if posIsInvert: pos = inverted.Base elif posIsKeep: pos = child.Placement.Base elif posIsReset: pass if oriIsInvert: ori = inverted.Rotation elif oriIsKeep: ori = child.Placement.Rotation elif oriIsReset: pass plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms
def derivedExecute(self, obj): # validity check nonLattices = [] for iArr in range(0, len(obj.Links)): link = obj.Links[iArr] if not lattice2BaseFeature.isObjectLattice(link): nonLattices.append(link.Label) if len(nonLattices) > 0: lattice2Executer.warning( obj, "Only lattice objects are expected to be linked as arrays in JoinArrays. There are " + len(nonLattices) + " objects which are not lattice objects. Results may me unexpected.", ) # extract placements listlistPlms = [] lengths = [] for link in obj.Links: leaves = LCE.AllLeaves(link.Shape) listlistPlms.append([child.Placement for child in leaves]) lengths.append(len(leaves)) # processing output = [] # list of placements if obj.Interleave: for l in lengths[1:]: if l != lengths[0]: lattice2Executer.warning( obj, "Array lengths are unequal: " + repr(lengths) + ". Interleaving will be inconsistent." ) break for iItem in range(0, max(lengths)): for list in listlistPlms: if iItem < len(list): output.append(list[iItem]) else: for list in listlistPlms: output.extend(list) return output
def derivedExecute(self, obj): #validity check nonLattices = [] for iArr in range(0, len(obj.Links)): link = obj.Links[iArr] if not lattice2BaseFeature.isObjectLattice(link): nonLattices.append(link.Label) if len(nonLattices) > 0: lattice2Executer.warning( obj, "Only lattice objects are expected to be linked as arrays in JoinArrays. There are " + len(nonLattices) + " objects which are not lattice objects. Results may me unexpected." ) #extract placements listlistPlms = [] lengths = [] for link in obj.Links: leaves = LCE.AllLeaves(link.Shape) listlistPlms.append([child.Placement for child in leaves]) lengths.append(len(leaves)) #processing output = [] #list of placements if obj.Interleave: for l in lengths[1:]: if l != lengths[0]: lattice2Executer.warning( obj, "Array lengths are unequal: " + repr(lengths) + ". Interleaving will be inconsistent.") break for iItem in range(0, max(lengths)): for list in listlistPlms: if iItem < len(list): output.append(list[iItem]) else: for list in listlistPlms: output.extend(list) return output
def Activated(self): try: if len(FreeCADGui.Selection.getSelection())==0: infoMessage("Make compound", "Make compound command. Combines several shapes into one. The shapes are kept as-is. They are not fused together, and can be extracted unchanged.\n\n"+ "Compounds can contain combination of shapes of any topology: one can compound some edges with some solids. Compound is effectively another kind of group. But unlike normal FreeCAD group, compound only accepts OCC geometry. Compound cannot include meshes, dimensions, labels, or other objects that provide no Shape property.\n\n"+ "Note that compounds that have objects that touch or intersect are considered invalid by Part CheckGeometry. Such invalid compounds cannot be used for Part Cut/Common/Fuse.") return oldVal = lattice2Executer.globalIsCreatingLatticeFeature lattice2Executer.globalIsCreatingLatticeFeature = True sel = FreeCADGui.Selection.getSelectionEx() for s in sel: if isObjectLattice(s.Object): lattice2Executer.warning(None,"For making a compound, generic shapes are expected, but some of the selected objects are placements/arrays of placements. These will be treated as generic shapes; results may be unexpected.") break FreeCADGui.runCommand("Part_Compound") except Exception as err: msgError(err) finally: lattice2Executer.globalIsCreatingLatticeFeature = oldVal
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 derivedExecute(self,obj): # cache stuff base = obj.Base.Shape if not lattice2BaseFeature.isObjectLattice(obj.Base): lattice2Executer.warning(obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n") input = [leaf.Placement for leaf in LCE.AllLeaves(base)] if len(input) < 2: raise ValueError("At least 2 placements ar needed to interpolate; there are just "+str(len(input))+" in base array.") if obj.NumberSamples < 2: raise ValueError("Can output no less than 2 samples; "+str(obj.NumberSamples)+" was requested.") #cache mode comparisons, for speed posIsInterpolate = obj.TranslateMode == 'interpolate' posIsReset = obj.TranslateMode == 'reset' oriIsInterpolate = obj.OrientMode == 'interpolate' oriIsReset = obj.OrientMode == 'reset' # construct interpolation functions # prepare lists of input samples IArray = [float(i) for i in range(0,len(input))] XArray = [plm.Base.x for plm in input] YArray = [plm.Base.y for plm in input] ZArray = [plm.Base.z for plm in input] QArrays = [[],[],[],[]] prevQ = [0.0]*4 for plm in input: Q = plm.Rotation.Q #test if quaernion has changed sign compared to previous one. # Quaternions of opposite sign are equivalent in terms of rotation, # but sign changes confuse interpolation, so we are detecting sign # changes and discarding them if dotProduct(Q,prevQ) < -ParaConfusion: Q = [-v for v in Q] for iQ in [0,1,2,3]: QArrays[iQ].append( Q[iQ] ) prevQ = Q # constuct function objects if posIsInterpolate: FX = LIU.InterpolateF(IArray,XArray) FY = LIU.InterpolateF(IArray,YArray) FZ = LIU.InterpolateF(IArray,ZArray) if oriIsInterpolate: FQs = [] for iQ in [0,1,2,3]: FQs.append(LIU.InterpolateF(IArray,QArrays[iQ])) # initialize output containers and loop variables outputPlms = [] #list of placements for i_output in range(0,math.trunc(obj.NumberSamples+ParaConfusion)): i_input = float(i_output) / (obj.NumberSamples-1) * (len(input)-1) pos = App.Vector() ori = App.Rotation() if posIsInterpolate: pos = App.Vector(FX.value(i_input), FY.value(i_input), FZ.value(i_input)) if oriIsInterpolate: ori = App.Rotation(FQs[0].value(i_input), FQs[1].value(i_input), FQs[2].value(i_input), FQs[3].value(i_input)) plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms
def execute(self, obj): base = screen(obj.ShapeLink).Shape if obj.CompoundTraversal == "Use as a whole": baseChildren = [base] else: if base.ShapeType != 'Compound': base = Part.makeCompound([base]) if obj.CompoundTraversal == "Recursive": baseChildren = LCE.AllLeaves(base) else: baseChildren = base.childShapes() N = len(baseChildren) orients = [] if obj.OrientMode == "global": orients = [App.Placement()] * N elif obj.OrientMode == "local of compound": orients = [screen(obj.ShapeLink).Placement] * N elif obj.OrientMode == "local of child": orients = [child.Placement for child in baseChildren] elif obj.OrientMode == "use OrientLink": orients = LBF.getPlacementsList(screen(obj.OrientLink), context=obj) if len(orients) == N: pass elif len(orients) > N: Executer.warning( obj, "Array of placements linked in OrientLink has more placements (" + str(len(orients)) + ") than bounding boxes to be constructed (" + str(len(baseChildren)) + "). Extra placements will be dropped.") elif len(orients) == 1: orients = [orients[0]] * N else: raise ValueError( obj.Name + ": Array of placements linked in OrientLink has not enough placements (" + str(len(orients)) + ") than bounding boxes to be constructed (" + str(len(baseChildren)) + ").") else: raise ValueError(obj.Name + ": OrientMode " + obj.OrientMode + " not implemented =(") # mark placements with no rotation for i in range(N): Q = orients[i].Rotation.Q # Quaternions for zero rotation are either (0,0,0,1) or (0,0,0,-1). For non-zero # rotations, some of first three values will be nonzero, and fourth value will # not be equal to 1. While it's enough to compare absolute value of fourth value # to 1, precision is seriously lost in such comparison, so we are checking if # first three values are zero instead. if abs(Q[0]) + abs(Q[1]) + abs(Q[2]) < ParaConfusion: orients[i] = None from lattice2ShapeCopy import shallowCopy boxes_shapes = [] for i in range(N): child = baseChildren[i] if orients[i] is not None: child = shallowCopy(child) child.Placement = orients[i].inverse().multiply( child.Placement) if obj.Precision: bb = getPrecisionBoundBox(child) else: bb = child.BoundBox bb = scaledBoundBox(bb, obj.ScaleFactor) bb.enlarge(obj.Padding) bb_shape = boundBox2RealBox(bb) if orients[i] is not None: bb_shape.transformShape(orients[i].toMatrix(), True) boxes_shapes.append(bb_shape) #Fill in read-only properties if N == 1: obj.Size = App.Vector(bb.XLength, bb.YLength, bb.ZLength) cnt = bb.Center if orients[0] is not None: cnt = orients[0].multVec(cnt) obj.Center = cnt else: obj.Size = App.Vector() obj.Center = App.Vector() if obj.CompoundTraversal == "Use as a whole": assert (N == 1) obj.Shape = boxes_shapes[0] else: obj.Shape = Part.makeCompound(boxes_shapes)
def derivedExecute(self, obj): #validity check if not lattice2BaseFeature.isObjectLattice(screen(obj.Base)): lattice2Executer.warning( obj, "A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected." ) toolShape = screen(obj.Tool).Shape if lattice2BaseFeature.isObjectLattice(screen(obj.Tool)): lattice2Executer.warning( obj, "A lattice object was provided as Tool. It will be converted into points; orientations will be ignored." ) leaves = LCE.AllLeaves(toolShape) points = [Part.Vertex(leaf.Placement.Base) for leaf in leaves] toolShape = Part.makeCompound(points) leaves = LCE.AllLeaves(screen(obj.Base).Shape) input = [leaf.Placement for leaf in leaves] output = [] #variable to receive the final list of placements #cache settings elev = float(obj.PosElevation) posIsKeep = obj.TranslateMode == 'keep' posIsProjected = obj.TranslateMode == 'projected' posIsMixed = obj.TranslateMode == 'mixed' mixF = float(obj.PosMixFraction) oriIsKeep = obj.OrientMode == 'keep' oriIsAlongGap = obj.OrientMode == 'along gap' oriIsTangentPlane = obj.OrientMode == 'tangent plane' oriIsAlongU = obj.OrientMode == 'along u' oriIsAlongV = obj.OrientMode == 'along v' isMultiSol = obj.Multisolution == 'use all' for plm in input: v = Part.Vertex(plm.Base) projection = v.distToShape(toolShape) (dist, gaps, infos) = projection for iSol in range(0, len(gaps)): (posKeep, posPrj) = gaps[iSol] (dummy, dummy, dummy, el_topo, el_index, el_params) = infos[iSol] # Fetch all possible parameters (some may not be required, depending on modes) normal = posKeep - posPrj if normal.Length < DistConfusion: normal = None tangU = None tangV = None if el_topo == 'Face': face = toolShape.Faces[el_index] if normal is None: normal = face.normalAt(*el_params) (tangU, tangV) = face.tangentAt(*el_params) elif el_topo == "Edge": edge = toolShape.Edges[el_index] tangU = edge.tangentAt(el_params) if normal is not None: normal.normalize() #mode logic - compute new placement if posIsKeep: pos = plm.Base elif posIsProjected: pos = posPrj elif posIsMixed: pos = posKeep * mixF + posPrj * (1 - mixF) else: raise ValueError("Positioning mode not implemented: " + obj.TranslateMode) if abs(elev) > DistConfusion: if normal is None: raise ValueError( "Normal vector not available for a placement resting on " + el_topo + ". Normal vector is required for nonzero position elevation." ) pos += normal * elev if oriIsKeep: ori = plm.Rotation elif oriIsAlongGap: if normal is None: raise ValueError( "Normal vector not available for a placement resting on " + el_topo + ". Normal vector is required for orientation mode '" + obj.OrientMode + "'") ori = Utils.makeOrientationFromLocalAxesUni("X", XAx=normal * (-1.0)) elif oriIsTangentPlane: if normal is None: raise ValueError( "Normal vector not available for a placement resting on " + el_topo + ". Normal vector is required for orientation mode '" + obj.OrientMode + "'") ori = Utils.makeOrientationFromLocalAxesUni("Z", ZAx=normal) elif oriIsAlongU: if normal is None: raise ValueError( "Normal vector not available for a placement resting on " + el_topo + ". Normal vector is required for orientation mode '" + obj.OrientMode + "'") if tangU is None: raise ValueError( "TangentU vector not available for point on " + el_topo + ". TangentU vector is required for orientation mode '" + obj.OrientMode + "'") ori = Utils.makeOrientationFromLocalAxesUni("ZX", ZAx=normal, XAx=tangU) elif oriIsAlongV: if normal is None: raise ValueError( "Normal vector not available for a placement resting on " + el_topo + ". Normal vector is required for orientation mode '" + obj.OrientMode + "'") if tangV is None: raise ValueError( "TangentV vector not available for point on " + el_topo + ". TangentV vector is required for orientation mode '" + obj.OrientMode + "'") ori = Utils.makeOrientationFromLocalAxesUni("ZX", ZAx=normal, XAx=tangV) else: raise ValueError("Orientation mode not implemented: " + obj.OrientMode) output.append(App.Placement(pos, ori)) if not isMultiSol: break return output
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 derivedExecute(self,obj): self.initNewProperties(obj) outputIsLattice = lattice2BaseFeature.isObjectLattice(screen(obj.Object)) if not lattice2BaseFeature.isObjectLattice(screen(obj.Object)): if obj.ObjectTraversal == "Direct children only": objectShapes = screen(obj.Object).Shape.childShapes() if screen(obj.Object).Shape.ShapeType != "Compound": lattice2Executer.warning(obj,"shape supplied as object is not a compound. It is going to be downgraded one level down (e.g, if it is a wire, the edges are going to be enumerated as children).") elif obj.ObjectTraversal == "Recursive": objectShapes = LCE.AllLeaves(screen(obj.Object).Shape) else: raise ValueError("Traversal mode not implemented: "+obj.ObjectTraversal) else: objectPlms = lattice2BaseFeature.getPlacementsList(screen(obj.Object), obj) placements = lattice2BaseFeature.getPlacementsList(screen(obj.PlacementsTo), obj) # Precompute referencing placements = DereferenceArray(obj, placements, screen(obj.PlacementsFrom), obj.Referencing) # initialize output containers and loop variables outputShapes = [] #output list of shapes outputPlms = [] #list of placements iChild = 0 numChildren = len(objectPlms) if outputIsLattice else len(objectShapes) copy_method_index = ShapeCopy.getCopyTypeIndex(obj.Copying) # the essence for iPlm in range(len(placements)): if iChild == numChildren: if obj.LoopObjectSequence: iChild = 0 else: break plm = placements[iPlm] if outputIsLattice: objectPlm = objectPlms[iChild] outputPlms.append(plm.multiply(objectPlm)) else: outputShape = ShapeCopy.copyShape(objectShapes[iChild], copy_method_index, plm) # outputShape.Placement = plm.multiply(outputShape.Placement) #now done by shape copy routine outputShapes.append(outputShape) iChild += 1 if len(placements) > numChildren and not obj.LoopObjectSequence: lattice2Executer.warning(obj,"There are fewer children to populate, than placements to be populated (%1, %2). Extra placements will be dropped.".replace("%1", str(numChildren)).replace("%2",str(len(placements)))) if len(placements) < numChildren: lattice2Executer.warning(obj,"There are more children to populate, than placements to be populated (%1, %2). Extra children will be dropped.".replace("%1", str(numChildren)).replace("%2",str(len(placements)))) if outputIsLattice: return outputPlms else: obj.Shape = Part.makeCompound(outputShapes) return None
def execute(self,obj): nOfStrings = len(obj.Strings) lattice = screen(obj.ArrayLink) if lattice is None: plms = [App.Placement() for i in range(0,nOfStrings)] else: if not lattice2BaseFeature.isObjectLattice(lattice): lattice2Executer.warning(obj,"ShapeString's link to array must point to a lattice. It points to a generic shape. Results may be unexpected.") leaves = LCE.AllLeaves(lattice.Shape) plms = [leaf.Placement for leaf in leaves] #update foolObj's properties self.makeFoolObj(obj) #make sure we have one - fixes defunct Lattice ShapeString after save-load for (proptype, propname, group, hint) in self.foolObj.properties: if propname != "String": #ignore "String", that will be taken care of in the following loop setattr(self.foolObj, propname, getattr(obj, propname)) self.foolObj.FontFile = findFont(obj.FontFile) obj.FullPathToFont = self.foolObj.FontFile shapes = [] for i in range( 0 , min(len(plms),len(obj.Strings)) ): if len(obj.Strings[i]) > 0: #generate shapestring using Draft self.foolObj.String = obj.Strings[i] self.foolObj.Shape = None self.draft_shape_string.execute(self.foolObj) shape = self.foolObj.Shape #calculate alignment point if obj.XAlign == 'None' and obj.YAlign == 'None': pass #need not calculate boundbox else: if obj.AlignPrecisionBoundBox: bb = getPrecisionBoundBox(shape) else: bb = shape.BoundBox alignPnt = App.Vector() if obj.XAlign == 'Left': alignPnt.x = bb.XMin elif obj.XAlign == 'Right': alignPnt.x = bb.XMax elif obj.XAlign == 'Middle': alignPnt.x = bb.Center.x if obj.YAlign == 'Bottom': alignPnt.y = bb.YMin elif obj.YAlign == 'Top': alignPnt.y = bb.YMax elif obj.YAlign == 'Middle': alignPnt.y = bb.Center.y #Apply alignment shape.Placement = App.Placement(alignPnt*(-1.0), App.Rotation()).multiply(shape.Placement) #Apply placement from array shape.Placement = plms[i].multiply(shape.Placement) shapes.append(shape.copy()) if len(shapes) == 0: scale = 1.0 if lattice is not None: scale = lattice.Shape.BoundBox.DiagonalLength/math.sqrt(3)/math.sqrt(len(shps)) if scale < DistConfusion * 100: scale = 1.0 obj.Shape = markers.getNullShapeShape(scale) raise ValueError('No strings were converted into shapes') #Feeding empty compounds to FreeCAD seems to cause rendering issues, otherwise it would have been a good idea to output nothing. result = Part.makeCompound(shapes) result.Placement = obj.Placement obj.Shape = result
def execute(self, obj): # please, don't override. Override derivedExecute instead. plms = self.derivedExecute(obj) if plms is not None: if plms == "suppress": return obj.NumElements = len(plms) shapes = [] markerSize = obj.MarkerSize if markerSize < DistConfusion: markerSize = getMarkerSizeEstimate(plms, obj) marker = lattice2Markers.getPlacementMarker( scale=markerSize, markerID=obj.MarkerShape) bExposing = False if obj.ExposePlacement: if len(plms) == 1: bExposing = True else: lattice2Executer.warning( obj, "Multiple placements are being fed, can't expose placements. Placement property will be forced to zero." ) obj.Placement = App.Placement() if bExposing: obj.Shape = shallowCopy(marker) obj.Placement = plms[0] else: for plm in plms: sh = shallowCopy(marker) sh.Placement = plm shapes.append(sh) if len(shapes) == 0: obj.Shape = lattice2Markers.getNullShapeShape(markerSize) raise ValueError('Lattice object is null') sh = Part.makeCompound(shapes) sh.Placement = obj.Placement obj.Shape = sh if obj.isLattice == 'Auto-Off': obj.isLattice = 'Auto-On' else: # DerivedExecute didn't return anything. Thus we assume it # has assigned the shape, and thus we don't do anything. # Moreover, we assume that it is no longer a lattice object, so: if obj.isLattice == 'Auto-On': obj.isLattice = 'Auto-Off' if obj.ExposePlacement: if obj.Shape.ShapeType == "Compound": children = obj.Shape.childShapes() if len(children) == 1: obj.Placement = children[0].Placement obj.Shape = children[0] else: obj.Placement = App.Placement() else: #nothing to do - FreeCAD will take care to make obj.Placement and obj.Shape.Placement synchronized. pass return
def derivedExecute(self,obj): # cache stuff base = screen(obj.Base).Shape if not lattice2BaseFeature.isObjectLattice(screen(obj.Base)): lattice2Executer.warning(obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n") input = [leaf.Placement for leaf in LCE.AllLeaves(base)] if len(input) < 2: raise ValueError("At least 2 placements ar needed to interpolate; there are just "+str(len(input))+" in base array.") if obj.NumberSamples < 2: raise ValueError("Can output no less than 2 samples; "+str(obj.NumberSamples)+" was requested.") #cache mode comparisons, for speed posIsInterpolate = obj.TranslateMode == 'interpolate' posIsReset = obj.TranslateMode == 'reset' oriIsInterpolate = obj.OrientMode == 'interpolate' oriIsReset = obj.OrientMode == 'reset' # construct interpolation functions # prepare lists of input samples IArray = [float(i) for i in range(0,len(input))] XArray = [plm.Base.x for plm in input] YArray = [plm.Base.y for plm in input] ZArray = [plm.Base.z for plm in input] QArrays = [[],[],[],[]] prevQ = [0.0]*4 for plm in input: Q = plm.Rotation.Q #test if quaernion has changed sign compared to previous one. # Quaternions of opposite sign are equivalent in terms of rotation, # but sign changes confuse interpolation, so we are detecting sign # changes and discarding them if dotProduct(Q,prevQ) < -ParaConfusion: Q = [-v for v in Q] for iQ in [0,1,2,3]: QArrays[iQ].append( Q[iQ] ) prevQ = Q # construct function objects if posIsInterpolate: FX = LIU.InterpolateF(IArray,XArray) FY = LIU.InterpolateF(IArray,YArray) FZ = LIU.InterpolateF(IArray,ZArray) if oriIsInterpolate: FQs = [] for iQ in [0,1,2,3]: FQs.append(LIU.InterpolateF(IArray,QArrays[iQ])) # initialize output containers and loop variables outputPlms = [] #list of placements for i_output in range(0,math.trunc(obj.NumberSamples+ParaConfusion)): i_input = float(i_output) / (obj.NumberSamples-1) * (len(input)-1) pos = App.Vector() ori = App.Rotation() if posIsInterpolate: pos = App.Vector(FX.value(i_input), FY.value(i_input), FZ.value(i_input)) if oriIsInterpolate: ori = App.Rotation(FQs[0].value(i_input), FQs[1].value(i_input), FQs[2].value(i_input), FQs[3].value(i_input)) plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms
def execute(self, obj): #validity check if isObjectLattice(screen(obj.Base)): import lattice2Executer lattice2Executer.warning( obj, "A generic shape is expected, but an array of placements was supplied. It will be treated as a generic shape." ) rst = [] #variable to receive the final list of shapes shps = screen(obj.Base).Shape.childShapes() if obj.FilterType == 'bypass': rst = shps elif obj.FilterType == 'specific items': rst = [] flags = [False] * len(shps) ranges = obj.items.split(';') for r in ranges: r_v = r.split(':') if len(r_v) == 1: i = int(r_v[0]) rst.append(shps[i]) flags[i] = True elif len(r_v) == 2 or len(r_v) == 3: if len(r_v) == 2: r_v.append( "" ) # fix issue #1: instead of checking length here and there, simply add the missing field =) ifrom = None if len(r_v[0].strip()) == 0 else int(r_v[0]) ito = None if len(r_v[1].strip()) == 0 else int(r_v[1]) istep = None if len(r_v[2].strip()) == 0 else int(r_v[2]) rst = rst + shps[ifrom:ito:istep] for b in flags[ifrom:ito:istep]: b = True else: raise ValueError('index range cannot be parsed:' + r) if obj.Invert: rst = [] for i in range(0, len(shps)): if not flags[i]: rst.append(shps[i]) elif obj.FilterType == 'collision-pass': stencil = screen(obj.Stencil).Shape for s in shps: d = s.distToShape(stencil) if bool(d[0] < DistConfusion) ^ bool(obj.Invert): rst.append(s) elif obj.FilterType == 'window-volume' or obj.FilterType == 'window-area' or obj.FilterType == 'window-length' or obj.FilterType == 'window-distance': vals = [0.0] * len(shps) for i in range(0, len(shps)): if obj.FilterType == 'window-volume': vals[i] = shps[i].Volume elif obj.FilterType == 'window-area': vals[i] = shps[i].Area elif obj.FilterType == 'window-length': vals[i] = shps[i].Length elif obj.FilterType == 'window-distance': vals[i] = shps[i].distToShape(obj.Stencil.Shape)[0] maxval = max(vals) if obj.Stencil: if obj.FilterType == 'window-volume': maxval = obj.Stencil.Shape.Volume elif obj.FilterType == 'window-area': maxval = obj.Stencil.Shape.Area elif obj.FilterType == 'window-length': maxval = obj.Stencil.Shape.Length if obj.OverrideMaxVal: maxval = obj.OverrideMaxVal valFrom = obj.WindowFrom / 100.0 * maxval valTo = obj.WindowTo / 100.0 * maxval for i in range(0, len(shps)): if bool(vals[i] >= valFrom and vals[i] <= valTo) ^ obj.Invert: rst.append(shps[i]) else: raise ValueError('Filter mode not implemented:' + obj.FilterType) if len(rst) == 0: scale = 1.0 if not screen(obj.Base).Shape.isNull(): scale = screen( obj.Base).Shape.BoundBox.DiagonalLength / math.sqrt( 3) / math.sqrt(len(shps)) if scale < DistConfusion * 100: scale = 1.0 obj.Shape = markers.getNullShapeShape(scale) raise ValueError( 'Nothing passes through the filter' ) #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: obj.Shape = Part.makeCompound(rst) else: # don't make compound of one shape, output it directly sh = rst[0] sh = ShapeCopy.transformCopy(sh) sh.Placement = obj.Placement obj.Shape = sh return
def derivedExecute(self, selfobj): # values generator should be functional even if recomputing is disabled, so do it first self.assureGenerator(selfobj) self.generator.updateReadonlyness() self.generator.execute() if selfobj.Recomputing == "Disabled": raise ValueError( selfobj.Name + ": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it." ) try: #test parameter references and read out their current values refstr = selfobj.ParameterRef #dict(selfobj.ExpressionEngine)["ParameterRef"] refstrs = refstr.replace(";", "\t").split("\t") defvalues = [] for refstr in refstrs: refstr = refstr.strip() val = None try: val = getParameter(selfobj.Document, refstr) except Exception as err: App.Console.PrintError( "{obj}: failed to read out parameter '{param}': {err}\n" .format(obj=selfobj.Name, param=refstr, err=str(err))) defvalues.append(val) N_params = len(defvalues) if N_params == 0: raise ValueError(selfobj.Name + ": ParameterRef is not set. It is required.") #parse values values = [] for strrow in selfobj.Values: if len(strrow) == 0: break row = strrow.split(";") row = [ (strv.strip() if len(strv.strip()) > 0 else None) for strv in row ] # clean out spaces and replace empty strings with None if len(row) < N_params: row += [None] * (N_params - len(row)) values.append(row) # convert values to type, filling in defaults where values are missing for row in values: for icol in range(N_params): strv = row[icol] val = None if strv is None: val = defvalues[icol] elif selfobj.ParameterType == 'float' or selfobj.ParameterType == 'int': val = float(strv.replace(",", ".")) if selfobj.ParameterType == 'int': val = int(round(val)) elif selfobj.ParameterType == 'string': val = strv.strip() else: raise ValueError( selfobj.Name + ": ParameterType option not implemented: " + selfobj.ParameterType) row[icol] = val if len(values) == 0: scale = 1.0 try: if not screen(selfobj.Object).Shape.isNull(): scale = screen( selfobj.Object ).Shape.BoundBox.DiagonalLength / math.sqrt(3) except Exception: pass if scale < DistConfusion * 100: scale = 1.0 selfobj.Shape = markers.getNullShapeShape(scale) raise ValueError(selfobj.Name + ": list of values is empty.") bGui = False #bool(App.GuiUp) #disabled temporarily, 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, len(values) + 1) progress.setModal(True) progress.show() doc1 = selfobj.Document doc2 = App.newDocument( ) #create temporary doc to do the computations # assign doc's filename before copying objects, otherwise we get errors with xlinks try: doc2.FileName = doc1.FileName except Exception as err: pass #in old FreeCADs, FileName property is read-only, we can safely ignore that object_in_doc2 = None # define the variable, to prevent del() in finally block from raising another error try: doc2.copyObject(screen(selfobj.Object), True) #if there are nested paraseries 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: lattice2Executer.warning( selfobj, "Failed to enable recomputing of " + objd2.Name) object_in_doc2 = doc2.getObject(screen(selfobj.Object).Name) if bGui: progress.setValue(1) output_shapes = [] for row in values: for icol in range(len(row)): setParameter(doc2, refstrs[icol].strip(), row[icol]) #recompute doc2.recompute() #get shape shape = None for obj in doc2.Objects: if 'Invalid' in obj.State: lattice2Executer.error( obj, "Recomputing shape for parameter value of " + repr(row) + " failed.") scale = 1.0 try: if not screen(selfobj.Object).Shape.isNull(): scale = screen( selfobj.Object ).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_in_doc2.Shape.copy() output_shapes.append(shape) #update progress if bGui: progress.setValue(progress.value() + 1) if progress.wasCanceled(): raise lattice2Executer.CancelError() finally: #delete all references, before destroying the document. Probably not required, but to be sure... del (object_in_doc2) doc2_name = doc2.Name del (doc2) App.closeDocument(doc2_name) if bGui: progress.setValue(len(values) + 1) selfobj.Shape = Part.makeCompound(output_shapes) output_is_lattice = lattice2BaseFeature.isObjectLattice( screen(selfobj.Object)) 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.
def derivedExecute(self, obj): self.assureGenerator(obj) self.assureProperties(obj) self.updateReadonlyness(obj) # Apply links if screen(obj.AxisLink): if lattice2BaseFeature.isObjectLattice(screen(obj.AxisLink)): lattice2Executer.warning( obj, "For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape." ) #resolve the link if len(obj.AxisLinkSubelement) > 0: linkedShape = screen(obj.AxisLink).Shape.getElement( obj.AxisLinkSubelement) else: linkedShape = screen(obj.AxisLink).Shape #Type check if linkedShape.ShapeType != 'Edge': raise ValueError('Axis link must be an edge; it is ' + linkedShape.ShapeType + ' instead.') #prepare dir = App.Vector() point = App.Vector() if isinstance(linkedShape.Curve, Part.Line): start_point = linkedShape.valueAt(linkedShape.FirstParameter) end_point = linkedShape.valueAt(linkedShape.LastParameter) dir = end_point - start_point point = start_point elif isinstance(linkedShape.Curve, Part.Circle): dir = linkedShape.Curve.Axis point = linkedShape.Curve.Center else: raise ValueError( "Edge " + repr(linkedShape) + " can't be used to derive an axis. It must be either a line or a circle/arc." ) #apply if obj.AxisDirIsDriven: obj.AxisDir = dir if obj.AxisPointIsDriven: obj.AxisPoint = point self.generator.execute() # cache properties into variables radius = float(obj.Radius) values = [float(strv) for strv in obj.Values] # compute initial vector. It is to be perpendicular to Axis rot_ini = lattice2GeomUtils.makeOrientationFromLocalAxes( ZAx=obj.AxisDir) overallPlacement = App.Placement(obj.AxisPoint, rot_ini) # Make the array output = [] # list of placements for ang in values: 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) resultplm = overallPlacement.multiply(localplm) if obj.OrientMode == 'None': resultplm.Rotation = App.Rotation() output.append(resultplm) return output
def derivedExecute(self,selfobj): # values generator should be functional even if recomputing is disabled, so do it first self.assureGenerator(selfobj) self.generator.updateReadonlyness() self.generator.execute() if selfobj.Recomputing == "Disabled": raise ValueError(selfobj.Name+": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it.") try: #test parameter references and read out their current values refstr = selfobj.ParameterRef #dict(selfobj.ExpressionEngine)["ParameterRef"] refstrs = refstr.replace(";","\t").split("\t") defvalues = [] for refstr in refstrs: refstr = refstr.strip(); val = None; try: val = getParameter(selfobj.Document,refstr) except Exception as err: App.Console.PrintError("{obj}: failed to read out parameter '{param}': {err}\n" .format(obj= selfobj.Name, param= refstr, err= err.message)) defvalues.append(val) N_params = len(defvalues) if N_params == 0: raise ValueError(selfobj.Name+": ParameterRef is not set. It is required.") #parse values values = [] for strrow in selfobj.Values: if len(strrow) == 0: break; row = strrow.split(";") row = [(strv.strip() if len(strv.strip())>0 else None) for strv in row] # clean out spaces and replace empty strings with None if len(row) < N_params: row += [None]*(N_params - len(row)) values.append(row) # convert values to type, filling in defaults where values are missing for row in values: for icol in range(N_params): strv = row[icol] val = None if strv is None: val = defvalues[icol] elif selfobj.ParameterType == 'float' or selfobj.ParameterType == 'int': val = float(strv.replace(",",".")) if selfobj.ParameterType == 'int': val = int(round(val)) elif selfobj.ParameterType == 'string': val = strv.strip() else: raise ValueError(selfobj.Name + ": ParameterType option not implemented: "+selfobj.ParameterType) row[icol] = val if len(values) == 0: scale = 1.0 try: if not selfobj.Object.Shape.isNull(): scale = selfobj.Object.Shape.BoundBox.DiagonalLength/math.sqrt(3) except Exception: pass if scale < DistConfusion * 100: scale = 1.0 selfobj.Shape = markers.getNullShapeShape(scale) raise ValueError(selfobj.Name + ": list of values is empty.") bGui = False #bool(App.GuiUp) #disabled temporarily, 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, len(values)+1) progress.setModal(True) progress.show() doc1 = selfobj.Document doc2 = App.newDocument() object_in_doc2 = None # define the variable, to prevent del() in finally block from raising another error try: doc2.copyObject(selfobj.Object, True) #if there are nested paraseries 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: lattice2Executer.warning(selfobj,"Failed to enable recomputing of "+objd2.Name) object_in_doc2 = doc2.getObject(selfobj.Object.Name) if bGui: progress.setValue(1) output_shapes = [] for row in values: for icol in range(len(row)): setParameter(doc2, refstrs[icol].strip(), row[icol]) #recompute doc2.recompute() #get shape shape = None for obj in doc2.Objects: if 'Invalid' in obj.State: lattice2Executer.error(obj,"Recomputing shape for parameter value of "+repr(row)+" failed.") scale = 1.0 try: if not selfobj.Object.Shape.isNull(): scale = selfobj.Object.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_in_doc2.Shape.copy() output_shapes.append(shape) #update progress if bGui: progress.setValue(progress.value()+1) if progress.wasCanceled(): raise lattice2Executer.CancelError() finally: #delete all references, before destroying the document. Probably not required, but to be sure... del(object_in_doc2) doc2_name = doc2.Name del(doc2) App.closeDocument(doc2_name) if bGui: progress.setValue(len(values)+1) selfobj.Shape = Part.makeCompound(output_shapes) output_is_lattice = lattice2BaseFeature.isObjectLattice(selfobj.Object) 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.
def execute(self,obj): nOfStrings = len(obj.Strings) lattice = obj.ArrayLink if lattice is None: plms = [App.Placement() for i in range(0,nOfStrings)] else: if not lattice2BaseFeature.isObjectLattice(lattice): lattice2Executer.warning(obj,"ShapeString's link to array must point to a lattice. It points to a generic shape. Results may be unexpected.") leaves = LCE.AllLeaves(lattice.Shape) plms = [leaf.Placement for leaf in leaves] #update foolObj's properties self.makeFoolObj(obj) #make sure we have one - fixes defunct Lattice ShapeString after save-load for (proptype, propname, group, hint) in self.foolObj.properties: if propname != "String": #ignore "String", that will be taken care of in the following loop setattr(self.foolObj, propname, getattr(obj, propname)) self.foolObj.FontFile = findFont(obj.FontFile) obj.FullPathToFont = self.foolObj.FontFile shapes = [] for i in range( 0 , min(len(plms),len(obj.Strings)) ): if len(obj.Strings[i]) > 0: #generate shapestring using Draft self.foolObj.String = obj.Strings[i] self.foolObj.Shape = None self.draft_shape_string.execute(self.foolObj) shape = self.foolObj.Shape #calculate alignment point if obj.XAlign == 'None' and obj.YAlign == 'None': pass #need not calculate boundbox else: if obj.AlignPrecisionBoundBox: bb = getPrecisionBoundBox(shape) else: bb = shape.BoundBox alignPnt = App.Vector() if obj.XAlign == 'Left': alignPnt.x = bb.XMin elif obj.XAlign == 'Right': alignPnt.x = bb.XMax elif obj.XAlign == 'Middle': alignPnt.x = bb.Center.x if obj.YAlign == 'Bottom': alignPnt.y = bb.YMin elif obj.YAlign == 'Top': alignPnt.y = bb.YMax elif obj.YAlign == 'Middle': alignPnt.y = bb.Center.y #Apply alignment shape.Placement = App.Placement(alignPnt*(-1.0), App.Rotation()).multiply(shape.Placement) #Apply placement from array shape.Placement = plms[i].multiply(shape.Placement) shapes.append(shape.copy()) if len(shapes) == 0: scale = 1.0 if lattice is not None: scale = lattice.Shape.BoundBox.DiagonalLength/math.sqrt(3)/math.sqrt(len(shps)) if scale < DistConfusion * 100: scale = 1.0 obj.Shape = markers.getNullShapeShape(scale) raise ValueError('No strings were converted into shapes') #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)
def derivedExecute(self,obj): self.assureGenerator(obj) self.updateReadonlyness(obj) # Apply links if obj.Link: if lattice2BaseFeature.isObjectLattice(obj.Link): lattice2Executer.warning(obj,"For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape.") #resolve the link if len(obj.LinkSubelement) > 0: linkedShape = obj.Link.Shape.getElement(obj.LinkSubelement) else: linkedShape = obj.Link.Shape #Type check if linkedShape.ShapeType != 'Edge': raise ValueError('Axis link must be an edge; it is '+linkedShape.ShapeType+' instead.') if type(linkedShape.Curve) is not Part.Line: raise ValueError('Axis link must be a line; it is '+type(linkedShape.Curve)+' instead.') #obtain dir = linkedShape.Curve.EndPoint - linkedShape.Curve.StartPoint point = linkedShape.Curve.StartPoint if not obj.Reverse else linkedShape.Curve.EndPoint if obj.DirIsDriven: obj.Dir = dir if obj.PointIsDriven: obj.Point = point if obj.DrivenProperty != 'None': if obj.DrivenProperty == 'Span': propname = "SpanEnd" obj.SpanEnd = obj.SpanStart + App.Units.Quantity('mm')*dir.Length else: propname = obj.DrivenProperty setattr(obj, propname, dir.Length) if self.generator.isPropertyControlledByGenerator(propname): lattice2Executer.warning(obj, "Property "+propname+" is driven by both generator and link. Generator has priority.") # Generate series of values self.generator.execute() values = [float(strv) for strv in obj.Values] #Apply reversal if obj.Reverse: obj.Dir = obj.Dir*(-1.0) if not(obj.DirIsDriven and obj.Link): obj.Reverse = False # precompute orientation if obj.OrientMode == 'Along axis': ori = lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= obj.Dir).multiply( lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= App.Vector(1,0,0), XAx= App.Vector(0,0,1)) ) else: ori = App.Rotation() dir = obj.Dir dir.normalize() # Make the array output = [] # list of placements for v in values: output.append( App.Placement(obj.Point + obj.Dir*v, ori) ) return output
def execute(self,obj): #validity check if isObjectLattice(obj.Base): import lattice2Executer lattice2Executer.warning(obj,"A generic shape is expected, but an array of placements was supplied. It will be treated as a generic shape.") rst = [] #variable to receive the final list of shapes shps = obj.Base.Shape.childShapes() if obj.FilterType == 'bypass': rst = shps elif obj.FilterType == 'specific items': rst = [] flags = [False] * len(shps) ranges = obj.items.split(';') for r in ranges: r_v = r.split(':') if len(r_v) == 1: i = int(r_v[0]) rst.append(shps[i]) flags[i] = True elif len(r_v) == 2 or len(r_v) == 3: if len(r_v) == 2: r_v.append("") # fix issue #1: instead of checking length here and there, simply add the missing field =) ifrom = None if len(r_v[0].strip()) == 0 else int(r_v[0]) ito = None if len(r_v[1].strip()) == 0 else int(r_v[1]) istep = None if len(r_v[2].strip()) == 0 else int(r_v[2]) rst=rst+shps[ifrom:ito:istep] for b in flags[ifrom:ito:istep]: b = True else: raise ValueError('index range cannot be parsed:'+r) if obj.Invert : rst = [] for i in xrange(0,len(shps)): if not flags[i]: rst.append(shps[i]) elif obj.FilterType == 'collision-pass': stencil = obj.Stencil.Shape for s in shps: d = s.distToShape(stencil) if bool(d[0] < DistConfusion) ^ bool(obj.Invert): rst.append(s) elif obj.FilterType == 'window-volume' or obj.FilterType == 'window-area' or obj.FilterType == 'window-length' or obj.FilterType == 'window-distance': vals = [0.0] * len(shps) for i in xrange(0,len(shps)): if obj.FilterType == 'window-volume': vals[i] = shps[i].Volume elif obj.FilterType == 'window-area': vals[i] = shps[i].Area elif obj.FilterType == 'window-length': vals[i] = shps[i].Length elif obj.FilterType == 'window-distance': vals[i] = shps[i].distToShape(obj.Stencil.Shape)[0] maxval = max(vals) if obj.Stencil: if obj.FilterType == 'window-volume': maxval = obj.Stencil.Shape.Volume elif obj.FilterType == 'window-area': maxval = obj.Stencil.Shape.Area elif obj.FilterType == 'window-length': maxval = obj.Stencil.Shape.Length if obj.OverrideMaxVal: maxval = obj.OverrideMaxVal valFrom = obj.WindowFrom / 100.0 * maxval valTo = obj.WindowTo / 100.0 * maxval for i in xrange(0,len(shps)): if bool(vals[i] >= valFrom and vals[i] <= valTo) ^ obj.Invert: rst.append(shps[i]) else: raise ValueError('Filter mode not implemented:'+obj.FilterType) if len(rst) == 0: scale = 1.0 if not obj.Base.Shape.isNull(): scale = obj.Base.Shape.BoundBox.DiagonalLength/math.sqrt(3)/math.sqrt(len(shps)) if scale < DistConfusion * 100: scale = 1.0 print scale obj.Shape = markers.getNullShapeShape(scale) raise ValueError('Nothing passes through the filter') #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: obj.Shape = Part.makeCompound(rst) else: # don't make compound of one shape, output it directly sh = rst[0] sh = ShapeCopy.transformCopy(sh) sh.Placement = obj.Placement obj.Shape = sh return
def derivedExecute(self, obj): base_is_lattice = LBF.isObjectLattice(obj.Object) pivot_is_lattice = LBF.isObjectLattice( obj.Pivot[0]) if obj.Pivot else True flipX = obj.FlipX flipY = obj.FlipY flipZ = obj.FlipZ # collect mirror pivot placements pivots = None em = 0 #editormode of PivotPlacement property. 0 = editable, 1 = read-only, 2 = hidden if obj.Pivot: em = 1 #read-only if pivot_is_lattice: pivots = LBF.getPlacementsList(obj.Pivot[0]) else: pivot_shape = resolveSingleSublink(obj.Pivot) if pivot_shape.ShapeType == 'Edge' and type( pivot_shape.Curve) is Part.Line: dir = pivot_shape.Curve.Direction base = pivot_shape.CenterOfMass if flipX != flipY: raise ValueError( "Unsupported combination of flips for mirroring against line. FlipX and FlipY must either be both on or both off." ) rot = makeOrientationFromLocalAxes(dir) pivots = [App.Placement(base, rot)] elif pivot_shape.ShapeType == 'Face' and type( pivot_shape.Surface) is Part.Plane: dir = pivot_shape.Surface.Axis base = pivot_shape.CenterOfMass if flipX != flipY: raise ValueError( "Unsupported combination of flips for mirroring against line. FlipX and FlipY must either be both on or both off." ) rot = makeOrientationFromLocalAxes(dir) pivots = [App.Placement(base, rot)] elif pivot_shape.ShapeType == 'Vertex': base = pivot_shape.Point pivots = [App.Placement(base, obj.PivotPlacement.Rotation)] em = 0 #editable else: raise TypeError("Unsupported geometry for use as mirror") if len(pivots) == 1: obj.PivotPlacement = pivots[0] else: em = 2 #hidden else: pivots = [obj.PivotPlacement] em = 0 obj.setEditorMode('PivotPlacement', em) # collect objects to be mirrored loop = False whole = obj.ObjectTraversal == 'Use whole' children = [] if base_is_lattice: children = LBF.getPlacementsList(obj.Object) else: if obj.ObjectTraversal == 'Use whole': children = [obj.Object.Shape] loop = True elif obj.ObjectTraversal == 'Direct children only': children = obj.Object.Shape.childShapes() elif obj.ObjectTraversal == 'Use whole': children = LCE.AllLeaves(obj.Object.Shape) else: raise ValueError( "Traversal mode not implemented: {mode}".format( mode=obj.ObjectTraversal)) if len(pivots) != len(children) and not loop and not whole: lattice2Executer.warning( obj, "{label}: Number of children ({nch}) doesn't match the number of pivot placements ({npiv})" .format(label=obj.Label, nch=len(children), npiv=len(pivots))) n = min(len(pivots), len(children)) else: n = len(pivots) # actual mirroring! result = [] for i in range(n): piv = pivots[i] ichild = i % len(children) if base_is_lattice: if whole: for plm in children: result.append( mirrorPlacement(plm, piv, flipX, flipY, flipZ)) else: result.append( mirrorPlacement(children[ichild], piv, flipX, flipY, flipZ)) else: result.append( mirrorShape(children[ichild], piv, flipX, flipY, flipZ)) # write out the result if base_is_lattice: return result else: if n == 1: result = ShapeCopy.transformCopy(result[0]) else: result = Part.Compound(result) obj.Shape = result return None
def derivedExecute(self,obj): #validity check if not lattice2BaseFeature.isObjectLattice(obj.Base): lattice2Executer.warning(obj,"A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected.") toolShape = obj.Tool.Shape if lattice2BaseFeature.isObjectLattice(obj.Tool): lattice2Executer.warning(obj,"A lattice object was provided as Tool. It will be converted into points; orientations will be ignored.") leaves = LCE.AllLeaves(toolShape) points = [Part.Vertex(leaf.Placement.Base) for leaf in leaves] toolShape = Part.makeCompound(points) leaves = LCE.AllLeaves(obj.Base.Shape) input = [leaf.Placement for leaf in leaves] output = [] #variable to receive the final list of placements #cache settings elev = float(obj.PosElevation) posIsKeep = obj.TranslateMode == 'keep' posIsProjected = obj.TranslateMode == 'projected' posIsMixed = obj.TranslateMode == 'mixed' mixF = float(obj.PosMixFraction) oriIsKeep = obj.OrientMode == 'keep' oriIsAlongGap = obj.OrientMode == 'along gap' oriIsTangentPlane = obj.OrientMode == 'tangent plane' oriIsAlongU = obj.OrientMode == 'along u' oriIsAlongV = obj.OrientMode == 'along v' isMultiSol = obj.Multisolution == 'use all' for plm in input: v = Part.Vertex(plm.Base) projection = v.distToShape(toolShape) (dist, gaps, infos) = projection for iSol in range(0,len(gaps)): (posKeep, posPrj) = gaps[iSol] (dummy, dummy, dummy, el_topo, el_index, el_params) = infos[iSol] # Fetch all possible parameters (some may not be required, depending on modes) normal = posKeep - posPrj if normal.Length < DistConfusion: normal = None tangU = None tangV = None if el_topo == 'Face': face = toolShape.Faces[el_index] if normal is None: normal = face.normalAt(*el_params) (tangU, tangV) = face.tangentAt(*el_params) elif el_topo == "Edge": edge = toolShape.Edges[el_index] tangU = edge.tangentAt(el_params) if normal is not None: normal.normalize() #mode logic - compute new placement if posIsKeep: pos = plm.Base elif posIsProjected: pos = posPrj elif posIsMixed: pos = posKeep*mixF + posPrj*(1-mixF) else: raise ValueError("Positioning mode not implemented: " + obj.TranslateMode ) if abs(elev) > DistConfusion: if normal is None: raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for nonzero position elevation.") pos += normal * elev if oriIsKeep: ori = plm.Rotation elif oriIsAlongGap: if normal is None: raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'") ori = Utils.makeOrientationFromLocalAxesUni("X",XAx= normal*(-1.0)) elif oriIsTangentPlane: if normal is None: raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'") ori = Utils.makeOrientationFromLocalAxesUni("Z",ZAx= normal) elif oriIsAlongU: if normal is None: raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'") if tangU is None: raise ValueError("TangentU vector not available for point on " + el_topo +". TangentU vector is required for orientation mode '"+obj.OrientMode+"'") ori = Utils.makeOrientationFromLocalAxesUni("ZX",ZAx= normal, XAx= tangU) elif oriIsAlongV: if normal is None: raise ValueError("Normal vector not available for a placement resting on " + el_topo +". Normal vector is required for orientation mode '"+obj.OrientMode+"'") if tangV is None: raise ValueError("TangentV vector not available for point on " + el_topo +". TangentV vector is required for orientation mode '"+obj.OrientMode+"'") ori = Utils.makeOrientationFromLocalAxesUni("ZX",ZAx= normal, XAx= tangV) else: raise ValueError("Orientation mode not implemented: " + obj.OrientMode ) output.append(App.Placement(pos,ori)) if not isMultiSol: break return output
def derivedExecute(self,obj): self.initNewProperties(obj) outputIsLattice = lattice2BaseFeature.isObjectLattice(obj.Object) if not lattice2BaseFeature.isObjectLattice(obj.Object): if obj.ObjectTraversal == "Direct children only": objectShapes = obj.Object.Shape.childShapes() if obj.Object.Shape.ShapeType != "Compound": lattice2Executer.warning(obj,"shape supplied as object is not a compound. It is going to be downgraded one level down (e.g, if it is a wire, the edges are going to be enumerated as children).") elif obj.ObjectTraversal == "Recursive": objectShapes = LCE.AllLeaves(obj.Object.Shape) else: raise ValueError("Traversal mode not implemented: "+obj.ObjectTraversal) else: objectPlms = lattice2BaseFeature.getPlacementsList(obj.Object, obj) placements = lattice2BaseFeature.getPlacementsList(obj.PlacementsTo, obj) # Precompute referencing placements = DereferenceArray(obj, placements, obj.PlacementsFrom, obj.Referencing) # initialize output containers and loop variables outputShapes = [] #output list of shapes outputPlms = [] #list of placements iChild = 0 numChildren = len(objectPlms) if outputIsLattice else len(objectShapes) copy_method_index = ShapeCopy.getCopyTypeIndex(obj.Copying) # the essence for iPlm in range(len(placements)): if iChild == numChildren: if obj.LoopObjectSequence: iChild = 0 else: break plm = placements[iPlm] if outputIsLattice: objectPlm = objectPlms[iChild] outputPlms.append(plm.multiply(objectPlm)) else: outputShape = ShapeCopy.copyShape(objectShapes[iChild], copy_method_index, plm) # outputShape.Placement = plm.multiply(outputShape.Placement) #now done by shape copy routine outputShapes.append(outputShape) iChild += 1 if len(placements) > numChildren and not obj.LoopObjectSequence: lattice2Executer.warning(obj,"There are fewer children to populate, than placements to be populated (%1, %2). Extra placements will be dropped.".replace("%1", str(numChildren)).replace("%2",str(len(placements)))) if len(placements) < numChildren: lattice2Executer.warning(obj,"There are more children to populate, than placements to be populated (%1, %2). Extra children will be dropped.".replace("%1", str(numChildren)).replace("%2",str(len(placements)))) if outputIsLattice: return outputPlms else: obj.Shape = Part.makeCompound(outputShapes) return None
def execute(self,obj): base = obj.ShapeLink.Shape if obj.CompoundTraversal == "Use as a whole": baseChildren = [base] else: if base.ShapeType != 'Compound': base = Part.makeCompound([base]) if obj.CompoundTraversal == "Recursive": baseChildren = LCE.AllLeaves(base) else: baseChildren = base.childShapes() N = len(baseChildren) orients = [] if obj.OrientMode == "global": orients = [App.Placement()]*N elif obj.OrientMode == "local of compound": orients = [obj.ShapeLink.Placement]*N elif obj.OrientMode == "local of child": orients = [child.Placement for child in baseChildren] elif obj.OrientMode == "use OrientLink": orients = LBF.getPlacementsList(obj.OrientLink, context= obj) if len(orients) == N: pass elif len(orients)>N: Executer.warning(obj, "Array of placements linked in OrientLink has more placements ("+str(len(orients))+") than bounding boxes to be constructed ("+str(len(baseChildren))+"). Extra placements will be dropped.") elif len(orients)==1: orients = [orients[0]]*N else: raise ValueError(obj.Name+": Array of placements linked in OrientLink has not enough placements ("+str(len(orients))+") than bounding boxes to be constructed ("+str(len(baseChildren))+").") else: raise ValueError(obj.Name+": OrientMode "+obj.OrientMode+" not implemented =(") # mark placements with no rotation for i in range(N): Q = orients[i].Rotation.Q # Quaternions for zero rotation are either (0,0,0,1) or (0,0,0,-1). For non-zero # rotations, some of first three values will be nonzero, and fourth value will # not be equal to 1. While it's enough to compare absolute value of fourth value # to 1, precision is seriously lost in such comparison, so we are checking if # fisrt three values are zero instead. if abs(Q[0])+abs(Q[1])+abs(Q[2]) < ParaConfusion: orients[i] = None boxes_shapes = [] for i in range(N): child = baseChildren[i] if orients[i] is not None: child = child.copy() child.transformShape(orients[i].inverse().toMatrix()) if obj.Precision: bb = getPrecisionBoundBox(child) else: bb = child.BoundBox bb = scaledBoundBox(bb, obj.ScaleFactor) bb.enlarge(obj.Padding) bb_shape = boundBox2RealBox(bb) if orients[i] is not None: bb_shape.transformShape(orients[i].toMatrix()) boxes_shapes.append(bb_shape) #Fill in read-only properties if N == 1: obj.Size = App.Vector(bb.XLength,bb.YLength,bb.ZLength) cnt = bb.Center if orients[0] is not None: cnt = orients[0].multVec(cnt) obj.Center = cnt else: obj.Size = App.Vector() obj.Center = App.Vector() if obj.CompoundTraversal == "Use as a whole": assert(N==1) obj.Shape = boxes_shapes[0] else: obj.Shape = Part.makeCompound(boxes_shapes)
def derivedExecute(self, obj): self.assureGenerator(obj) self.assureProperties(obj) self.updateReadonlyness(obj) # Apply links if screen(obj.Link): if lattice2BaseFeature.isObjectLattice(screen(obj.Link)): lattice2Executer.warning( obj, "For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape." ) #resolve the link if len(obj.LinkSubelement) > 0: linkedShape = screen(obj.Link).Shape.getElement( obj.LinkSubelement) else: linkedShape = screen(obj.Link).Shape #Type check if linkedShape.ShapeType != 'Edge': raise ValueError('Axis link must be an edge; it is ' + linkedShape.ShapeType + ' instead.') if type(linkedShape.Curve) is not Part.Line: raise ValueError('Axis link must be a line; it is ' + type(linkedShape.Curve) + ' instead.') #obtain start_point = linkedShape.valueAt(linkedShape.FirstParameter) end_point = linkedShape.valueAt(linkedShape.LastParameter) dir = end_point - start_point point = start_point if not obj.Reverse else end_point if obj.DirIsDriven: obj.Dir = dir if obj.PointIsDriven: obj.Point = point if obj.DrivenProperty != 'None': if obj.DrivenProperty == 'Span': propname = "SpanEnd" obj.SpanEnd = obj.SpanStart + App.Units.Quantity( 'mm') * dir.Length else: propname = obj.DrivenProperty setattr(obj, propname, dir.Length) if self.generator.isPropertyControlledByGenerator(propname): lattice2Executer.warning( obj, "Property " + propname + " is driven by both generator and link. Generator has priority." ) # Generate series of values self.generator.execute() values = [float(strv) for strv in obj.Values] #Apply reversal if obj.Reverse: obj.Dir = obj.Dir * (-1.0) if not (obj.DirIsDriven and screen(obj.Link)): obj.Reverse = False # precompute orientation if obj.OrientMode == 'Along axis': ori = lattice2GeomUtils.makeOrientationFromLocalAxes( ZAx=obj.Dir).multiply( lattice2GeomUtils.makeOrientationFromLocalAxes( ZAx=App.Vector(1, 0, 0), XAx=App.Vector(0, 0, 1))) else: ori = App.Rotation() dir = obj.Dir dir.normalize() # Make the array output = [] # list of placements for v in values: output.append(App.Placement(obj.Point + obj.Dir * v, ori)) return output
def derivedExecute(self,obj): # cache stuff if lattice2BaseFeature.isObjectLattice(obj.ShapeLink): lattice2Executer.warning(obj,"ShapeLink points to a placement/array of placements. The placement/array will be reinterpreted as a generic shape; the results may be unexpected.") base = obj.ShapeLink.Shape if obj.CompoundTraversal == "Use as a whole": baseChildren = [base] else: if base.ShapeType != 'Compound': base = Part.makeCompound([base]) if obj.CompoundTraversal == "Recursive": baseChildren = LCE.AllLeaves(base) else: baseChildren = base.childShapes() #cache mode comparisons, for speed posIsNone = obj.TranslateMode == '(none)' posIsParent = obj.TranslateMode == 'parent' posIsChild = obj.TranslateMode == 'child' posIsCenterM = obj.TranslateMode == 'child.CenterOfMass' posIsCenterBB = obj.TranslateMode == 'child.CenterOfBoundBox' posIsVertex = obj.TranslateMode == 'child.Vertex' oriIsNone = obj.OrientMode == '(none)' oriIsParent = obj.OrientMode == 'parent' oriIsChild = obj.OrientMode == 'child' oriIsInertial = obj.OrientMode == 'child.InertiaAxes' oriIsEdge = obj.OrientMode == 'child.Edge' oriIsFace = obj.OrientMode == 'child.FaceAxis' # initialize output containers and loop variables outputPlms = [] #list of placements # the essence for child in baseChildren: pos = App.Vector() ori = App.Rotation() if posIsNone: pass elif posIsParent: pos = base.Placement.Base elif posIsChild: pos = child.Placement.Base elif posIsCenterM: leaves = LCE.AllLeaves(child) totalW = 0 weightAttrib = {"Vertex":"", "Edge":"Length", "Wire":"Length", "Face":"Area", "Shell":"Area", "Solid":"Volume", "CompSolid":""}[leaves[0].ShapeType] #Center of mass of a compound is a weghted average of centers # of mass of individual objects. for leaf in leaves: w = 1.0 if not weightAttrib else (getattr(leaf, weightAttrib)) if leaf.ShapeType == 'Vertex': leafCM = leaf.Point #elif child.ShapeType == 'CompSolid': #todo else: leafCM = leaf.CenterOfMass pos += leafCM * w totalW += w pos = pos * (1.0/totalW) elif posIsCenterBB: import lattice2BoundBox bb = lattice2BoundBox.getPrecisionBoundBox(child) pos = bb.Center elif posIsVertex: v = child.Vertexes[obj.TranslateElementIndex - 1] pos = v.Point else: raise ValueError(obj.Name + ": translation mode not implemented: "+obj.TranslateMode) if oriIsNone: pass elif oriIsParent: ori = base.Placement.Rotation elif oriIsChild: ori = child.Placement.Rotation elif oriIsInertial: leaves = LCE.AllLeaves(child) if len(leaves)>1: raise ValueError(obj.Name + ": calculation of principal axes of compounds is not supported yet") props = leaves[0].PrincipalProperties XAx = props['FirstAxisOfInertia'] ZAx = props['ThirdAxisOfInertia'] ori = Utils.makeOrientationFromLocalAxes(ZAx, XAx) elif oriIsEdge: edge = child.Edges[obj.OrientElementIndex - 1] XAx = edge.Curve.tangent(edge.Curve.FirstParameter)[0] ori1 = Utils.makeOrientationFromLocalAxes(ZAx= XAx) ori2 = Utils.makeOrientationFromLocalAxes(ZAx= App.Vector(1,0,0),XAx= App.Vector(0,0,1)) ori = ori1.multiply(ori2) elif oriIsFace: face = child.Faces[obj.OrientElementIndex - 1] ZAx = face.Surface.Axis else: raise ValueError(obj.Name + ": orientation mode not implemented: "+obj.OrientMode) plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms
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, screen(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(screen(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( screen(selfobj.ObjectToTake).Name) object_to_loop_in_doc2 = doc2.getObject( screen(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 screen( selfobj.ObjectToTake).Shape.isNull(): scale = screen( 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( screen(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.
def execute(self, obj): # please, don't override. Override derivedExecute instead. plms = self.derivedExecute(obj) if plms is not None: if plms == "suppress": return obj.NumElements = len(plms) shapes = [] markerSize = obj.MarkerSize if markerSize < DistConfusion: markerSize = getMarkerSizeEstimate(plms) marker = lattice2Markers.getPlacementMarker(scale=markerSize, markerID=obj.MarkerShape) bExposing = False if obj.ExposePlacement: if len(plms) == 1: bExposing = True else: lattice2Executer.warning( obj, "Multiple placements are being fed, can't expose placements. Placement property will be forced to zero.", ) obj.Placement = App.Placement() if bExposing: obj.Shape = shallowCopy(marker) obj.Placement = plms[0] else: for plm in plms: sh = shallowCopy(marker) sh.Placement = plm shapes.append(sh) if len(shapes) == 0: obj.Shape = lattice2Markers.getNullShapeShape(markerSize) raise ValueError("Lattice object is null") sh = Part.makeCompound(shapes) obj.Shape = sh if obj.isLattice == "Auto-Off": obj.isLattice = "Auto-On" else: # DerivedExecute didn't return anything. Thus we assume it # has assigned the shape, and thus we don't do anything. # Moreover, we assume that it is no longer a lattice object, so: if obj.isLattice == "Auto-On": obj.isLattice = "Auto-Off" if obj.ExposePlacement: if obj.Shape.ShapeType == "Compound": children = obj.Shape.childShapes() if len(children) == 1: obj.Placement = children[0].Placement obj.Shape = children[0] else: obj.Placement = App.Placement() else: # nothing to do - FreeCAD will take care to make obj.Placement and obj.Shape.Placement synchronized. pass return
def derivedExecute(self, obj): #validity check if not lattice2BaseFeature.isObjectLattice(screen(obj.Base)): lattice2Executer.warning( obj, "A lattice object is expected as Base, but a generic shape was provided. It will be treated as a lattice object; results may be unexpected." ) output = [] #variable to receive the final list of placements leaves = LCE.AllLeaves(screen(obj.Base).Shape) input = [leaf.Placement for leaf in leaves] if obj.FilterType == 'bypass': output = input elif obj.FilterType == 'specific items': flags = [False] * len(input) ranges = obj.items.split(';') for r in ranges: r_v = r.split(':') if len(r_v) == 1: i = int(r_v[0]) output.append(input[i]) flags[i] = True elif len(r_v) == 2 or len(r_v) == 3: if len(r_v) == 2: r_v.append( "" ) # fix issue #1: instead of checking length here and there, simply add the missing field =) ifrom = None if len(r_v[0].strip()) == 0 else int(r_v[0]) ito = None if len(r_v[1].strip()) == 0 else int(r_v[1]) istep = None if len(r_v[2].strip()) == 0 else int(r_v[2]) output = output + input[ifrom:ito:istep] for b in flags[ifrom:ito:istep]: b = True else: raise ValueError('index range cannot be parsed:' + r) if obj.Invert: output = [] for i in range(0, len(input)): if not flags[i]: output.append(input[i]) elif obj.FilterType == 'collision-pass': stencil = screen(obj.Stencil).Shape for plm in input: pnt = Part.Vertex(plm.Base) d = pnt.distToShape(stencil) if bool(d[0] < DistConfusion) ^ bool(obj.Invert): output.append(plm) elif obj.FilterType == 'window-distance': vals = [0.0] * len(input) for i in range(0, len(input)): if obj.FilterType == 'window-distance': pnt = Part.Vertex(input[i].Base) vals[i] = pnt.distToShape(screen(obj.Stencil).Shape)[0] valFrom = obj.WindowFrom valTo = obj.WindowTo for i in range(0, len(input)): if bool(vals[i] >= valFrom and vals[i] <= valTo) ^ obj.Invert: output.append(input[i]) else: raise ValueError('Filter mode not implemented:' + obj.FilterType) return output
def derivedExecute(self,obj): # cache stuff if lattice2BaseFeature.isObjectLattice(screen(obj.ShapeLink)): lattice2Executer.warning(obj,"ShapeLink points to a placement/array of placements. The placement/array will be reinterpreted as a generic shape; the results may be unexpected.") base = screen(obj.ShapeLink).Shape if obj.CompoundTraversal == "Use as a whole": baseChildren = [base] else: if base.ShapeType != 'Compound': base = Part.makeCompound([base]) if obj.CompoundTraversal == "Recursive": baseChildren = LCE.AllLeaves(base) else: baseChildren = base.childShapes() #cache mode comparisons, for speed posIsNone = obj.TranslateMode == '(none)' posIsParent = obj.TranslateMode == 'parent' posIsChild = obj.TranslateMode == 'child' posIsCenterM = obj.TranslateMode == 'child.CenterOfMass' posIsCenterBB = obj.TranslateMode == 'child.CenterOfBoundBox' posIsVertex = obj.TranslateMode == 'child.Vertex' oriIsNone = obj.OrientMode == '(none)' oriIsParent = obj.OrientMode == 'parent' oriIsChild = obj.OrientMode == 'child' oriIsInertial = obj.OrientMode == 'child.InertiaAxes' oriIsEdge = obj.OrientMode == 'child.Edge' oriIsFace = obj.OrientMode == 'child.FaceAxis' # initialize output containers and loop variables outputPlms = [] #list of placements # the essence for child in baseChildren: pos = App.Vector() ori = App.Rotation() if posIsNone: pass elif posIsParent: pos = base.Placement.Base elif posIsChild: pos = child.Placement.Base elif posIsCenterM: leaves = LCE.AllLeaves(child) totalW = 0 weightAttrib = {"Vertex":"", "Edge":"Length", "Wire":"Length", "Face":"Area", "Shell":"Area", "Solid":"Volume", "CompSolid":""}[leaves[0].ShapeType] #Center of mass of a compound is a weghted average of centers # of mass of individual objects. for leaf in leaves: w = 1.0 if not weightAttrib else (getattr(leaf, weightAttrib)) if leaf.ShapeType == 'Vertex': leafCM = leaf.Point #elif child.ShapeType == 'CompSolid': #todo else: leafCM = leaf.CenterOfMass pos += leafCM * w totalW += w pos = pos * (1.0/totalW) elif posIsCenterBB: import lattice2BoundBox bb = lattice2BoundBox.getPrecisionBoundBox(child) pos = bb.Center elif posIsVertex: v = child.Vertexes[obj.TranslateElementIndex - 1] pos = v.Point else: raise ValueError(obj.Name + ": translation mode not implemented: "+obj.TranslateMode) if oriIsNone: pass elif oriIsParent: ori = base.Placement.Rotation elif oriIsChild: ori = child.Placement.Rotation elif oriIsInertial: leaves = LCE.AllLeaves(child) if len(leaves)>1: raise ValueError(obj.Name + ": calculation of principal axes of compounds is not supported yet") props = leaves[0].PrincipalProperties XAx = props['FirstAxisOfInertia'] ZAx = props['ThirdAxisOfInertia'] ori = Utils.makeOrientationFromLocalAxes(ZAx, XAx) elif oriIsEdge: edge = child.Edges[obj.OrientElementIndex - 1] XAx = edge.Curve.tangent(edge.Curve.FirstParameter)[0] ori1 = Utils.makeOrientationFromLocalAxes(ZAx= XAx) ori2 = Utils.makeOrientationFromLocalAxes(ZAx= App.Vector(1,0,0),XAx= App.Vector(0,0,1)) ori = ori1.multiply(ori2) elif oriIsFace: face = child.Faces[obj.OrientElementIndex - 1] ZAx = face.Surface.Axis else: raise ValueError(obj.Name + ": orientation mode not implemented: "+obj.OrientMode) plm = App.Placement(pos, ori) outputPlms.append(plm) return outputPlms