def onDelete(self, obj, arg2=None): '''Called by the view provider, there doesn't seem to be a callback on the obj itself.''' PathLog.track(obj.Label, arg2) doc = obj.Document # the first to tear down are the ops, they depend on other resources PathLog.debug('taking down ops: %s' % [o.Name for o in self.allOperations()]) while obj.Operations.Group: op = obj.Operations.Group[0] if not op.ViewObject or not hasattr(op.ViewObject.Proxy, 'onDelete') or op.ViewObject.Proxy.onDelete(op.ViewObject, ()): doc.removeObject(op.Name) obj.Operations.Group = [] doc.removeObject(obj.Operations.Name) obj.Operations = None # stock could depend on Base if obj.Stock: PathLog.debug('taking down stock') doc.removeObject(obj.Stock.Name) obj.Stock = None # base doesn't depend on anything inside job if obj.Base: PathLog.debug('taking down base') doc.removeObject(obj.Base.Name) obj.Base = None # Tool controllers don't depend on anything PathLog.debug('taking down tool controller') for tc in obj.ToolController: doc.removeObject(tc.Name) obj.ToolController = []
def setFromTemplate(self, obj, template): '''setFromTemplate(obj, xmlItem) ... extract properties from xmlItem and assign to receiver.''' PathLog.track(obj.Name, template) if template.get(ToolControllerTemplate.Version) and 1 == int(template.get(ToolControllerTemplate.Version)): if template.get(ToolControllerTemplate.Label): obj.Label = template.get(ToolControllerTemplate.Label) if template.get(ToolControllerTemplate.VertFeed): obj.VertFeed = template.get(ToolControllerTemplate.VertFeed) if template.get(ToolControllerTemplate.HorizFeed): obj.HorizFeed = template.get(ToolControllerTemplate.HorizFeed) if template.get(ToolControllerTemplate.VertRapid): obj.VertRapid = template.get(ToolControllerTemplate.VertRapid) if template.get(ToolControllerTemplate.HorizRapid): obj.HorizRapid = template.get(ToolControllerTemplate.HorizRapid) if template.get(ToolControllerTemplate.SpindleSpeed): obj.SpindleSpeed = float(template.get(ToolControllerTemplate.SpindleSpeed)) if template.get(ToolControllerTemplate.SpindleDir): obj.SpindleDir = template.get(ToolControllerTemplate.SpindleDir) if template.get(ToolControllerTemplate.ToolNumber): obj.ToolNumber = int(template.get(ToolControllerTemplate.ToolNumber)) if template.get(ToolControllerTemplate.Tool): obj.Tool.setFromTemplate(template.get(ToolControllerTemplate.Tool)) if template.get(ToolControllerTemplate.Expressions): for exprDef in template.get(ToolControllerTemplate.Expressions): obj.setExpression(exprDef[ToolControllerTemplate.ExprProp], exprDef[ToolControllerTemplate.ExprExpr]) else: PathLog.error(translate('PathToolController', "Unsupported PathToolController template version %s") % template.get(ToolControllerTemplate.Version))
def setup(self, obj, initial): PathLog.info("Here we go ... ") if initial: if hasattr(obj.Base, "BoneBlacklist"): # dressing up a bone dressup obj.Side = obj.Base.Side else: # otherwise dogbones are opposite of the base path's side side = Side.Right if hasattr(obj.Base, 'Side') and obj.Base.Side == 'Inside': side = Side.Left if hasattr(obj.Base, 'Directin') and obj.Base.Direction == 'CCW': side = Side.oppositeOf(side) obj.Side = side self.toolRadius = 5 tc = PathDressup.toolController(obj.Base) if tc is None or tc.ToolNumber == 0: self.toolRadius = 5 else: tool = tc.Proxy.getTool(tc) # PathUtils.getTool(obj, tc.ToolNumber) if not tool or tool.Diameter == 0: self.toolRadius = 5 else: self.toolRadius = tool.Diameter / 2 self.shapes = {} self.dbg = []
def deleteBase(self): PathLog.track() selected = self.form.baseList.selectedItems() for item in selected: self.form.baseList.takeItem(self.form.baseList.row(item)) self.setDirty() self.updateBase()
def assignTemplate(self, obj, template): '''assignTemplate(obj, template) ... extract the properties from the given template file and assign to receiver. This will also create any TCs stored in the template.''' tcs = [] if template: tree = xml.parse(template) for job in tree.getroot().iter(JobTemplate.Job): if job.get(JobTemplate.GeometryTolerance): obj.GeometryTolerance = float(job.get(JobTemplate.GeometryTolerance)) if job.get(JobTemplate.PostProcessor): obj.PostProcessor = job.get(JobTemplate.PostProcessor) if job.get(JobTemplate.PostProcessorArgs): obj.PostProcessorArgs = job.get(JobTemplate.PostProcessorArgs) else: obj.PostProcessorArgs = '' if job.get(JobTemplate.PostProcessorOutputFile): obj.PostProcessorOutputFile = job.get(JobTemplate.PostProcessorOutputFile) if job.get(JobTemplate.Description): obj.Description = job.get(JobTemplate.Description) for tc in tree.getroot().iter(JobTemplate.ToolController): tcs.append(PathToolController.FromTemplate(tc)) for stock in tree.getroot().iter(JobTemplate.Stock): obj.Stock = PathStock.CreateFromTemplate(self, stock) else: tcs.append(PathToolController.Create()) PathLog.debug("setting tool controllers (%d)" % len(tcs)) obj.ToolController = tcs
def inOutBoneCommands(self, bone, boneAngle, fixedLength): corner = bone.corner(self.toolRadius) bone.tip = bone.inChord.End # in case there is no bone PathLog.debug("corner = (%.2f, %.2f)" % (corner.x, corner.y)) # debugMarker(corner, 'corner', (1., 0., 1.), self.toolRadius) length = fixedLength if bone.obj.Incision == Incision.Custom: length = bone.obj.Custom if bone.obj.Incision == Incision.Adaptive: length = bone.adaptiveLength(boneAngle, self.toolRadius) if length == 0: PathLog.info("no bone after all ..") return [bone.lastCommand, bone.outChord.g1Command(bone.F)] boneInChord = bone.inChord.move(length, boneAngle) boneOutChord = boneInChord.moveTo(bone.outChord.Start) # debugCircle(boneInChord.Start, self.toolRadius, 'boneStart') # debugCircle(boneInChord.End, self.toolRadius, 'boneEnd') bone.tip = boneInChord.End if bone.smooth == 0: return [bone.lastCommand, boneInChord.g1Command(bone.F), boneOutChord.g1Command(bone.F), bone.outChord.g1Command(bone.F)] # reconstruct the corner and convert to an edge offset = corner - bone.inChord.End iChord = Chord(bone.inChord.Start + offset, bone.inChord.End + offset) oChord = Chord(bone.outChord.Start + offset, bone.outChord.End + offset) iLine = iChord.asLine() oLine = oChord.asLine() cornerShape = Part.Shape([iLine, oLine]) # construct a shape representing the cut made by the bone vt0 = FreeCAD.Vector(0, self.toolRadius, 0) vt1 = FreeCAD.Vector(length, self.toolRadius, 0) vb0 = FreeCAD.Vector(0, -self.toolRadius, 0) vb1 = FreeCAD.Vector(length, -self.toolRadius, 0) vm2 = FreeCAD.Vector(length + self.toolRadius, 0, 0) boneBot = Part.LineSegment(vb1, vb0) boneLid = Part.LineSegment(vb0, vt0) boneTop = Part.LineSegment(vt0, vt1) # what we actually want is an Arc - but findIntersect only returns the coincident if one exists # which really sucks because that's the one we're probably not interested in .... boneArc = Part.Arc(vt1, vm2, vb1) # boneArc = Part.Circle(FreeCAD.Vector(length, 0, 0), FreeCAD.Vector(0,0,1), self.toolRadius) boneWire = Part.Shape([boneTop, boneArc, boneBot, boneLid]) boneWire.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), boneAngle * 180 / math.pi) boneWire.translate(bone.inChord.End) self.boneShapes = [cornerShape, boneWire] bone.inCommands = self.smoothChordCommands(bone, bone.inChord, boneInChord, Part.Edge(iLine), boneWire, corner, bone.smooth & Smooth.In, (1., 0., 0.)) bone.outCommands = self.smoothChordCommands(bone, boneOutChord, bone.outChord, Part.Edge(oLine), boneWire, corner, bone.smooth & Smooth.Out, (0., 1., 0.)) return bone.inCommands + bone.outCommands
def setupContextMenu(self, vobj, menu): PathLog.track() for action in menu.actions(): menu.removeAction(action) action = QtGui.QAction(translate('Path', 'Edit'), menu) action.triggered.connect(self.setEdit) menu.addAction(action)
def selectionSupportedAsBaseGeometry(self, selection, ignoreErrors): if len(selection) != 1: if not ignoreErrors: PathLog.error(translate("PathProject", "Please select %s from a single solid" % self.featureName())) return False sel = selection[0] if sel.HasSubObjects: if not self.supportsVertexes() and selection[0].SubObjects[0].ShapeType == "Vertex": if not ignoreErrors: PathLog.error(translate("PathProject", "Vertexes are not supported")) return False if not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge": if not ignoreErrors: PathLog.error(translate("PathProject", "Edges are not supported")) return False if not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face": if not ignoreErrors: PathLog.error(translate("PathProject", "Faces are not supported")) return False else: if not self.supportsPanels() or not 'Panel' in sel.Object.Name: if not ignoreErrors: PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName())) return False return True
def cleanup(self, resetEdit): PathLog.track() self.vobj.Proxy.resetTaskPanel() FreeCADGui.Control.closeDialog() if resetEdit: FreeCADGui.ActiveDocument.resetEdit() FreeCAD.ActiveDocument.recompute()
def rotateSel(sel, n): p = sel.Object.Placement loc = sel.Object.Placement.Base r = axis.cross(n) # rotation axis a = DraftVecUtils.angle(n, axis, r) * 180 / math.pi PathLog.debug("oh boy: (%.2f, %.2f, %.2f) -> %.2f" % (r.x, r.y, r.z, a)) Draft.rotate(sel.Object, a, axis=r)
def setupContextMenu(self, vobj, menu): PathLog.track() from PySide import QtCore, QtGui edit = QtCore.QCoreApplication.translate('Path', 'Edit', None) action = QtGui.QAction(edit, menu) action.triggered.connect(self.setEdit) menu.addAction(action)
def updateData(self, obj, prop): PathLog.track(obj.Label, prop) # make sure the resource view providers are setup properly if prop == 'Base' and self.obj.Base and self.obj.Base.ViewObject and self.obj.Base.ViewObject.Proxy: self.obj.Base.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) if prop == 'Stock' and self.obj.Stock and self.obj.Stock.ViewObject and self.obj.Stock.ViewObject.Proxy: self.obj.Stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor)
def cleanedges(splines, precision): '''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths. Returns Lines as is. Filters Circle and Arcs for over 180 degrees. Discretizes Ellipses. Ignores other geometry. ''' PathLog.track() edges = [] for spline in splines: if geomType(spline) == "BSplineCurve": arcs = spline.Curve.toBiArcs(precision) for i in arcs: edges.append(Part.Edge(i)) elif geomType(spline) == "BezierCurve": newspline = spline.Curve.toBSpline() arcs = newspline.toBiArcs(precision) for i in arcs: edges.append(Part.Edge(i)) elif geomType(spline) == "Ellipse": edges = curvetowire(spline, 1.0) # fixme hardcoded value elif geomType(spline) == "Circle": arcs = filterArcs(spline) for i in arcs: edges.append(Part.Edge(i)) elif geomType(spline) == "Line": edges.append(spline) elif geomType(spline) == "LineSegment": edges.append(spline) else: pass return edges
def setupTemplate(self): templateFiles = [] for path in PathPreferences.searchPaths(): templateFiles.extend(self.templateFilesIn(path)) template = {} for tFile in templateFiles: name = os.path.split(os.path.splitext(tFile)[0])[1][4:] if name in template: basename = name i = 0 while name in template: i = i + 1 name = basename + " (%s)" % i PathLog.track(name, tFile) template[name] = tFile selectTemplate = PathPreferences.defaultJobTemplate() index = 0 self.dialog.jobTemplate.addItem('<none>', '') for name in sorted(template.keys()): if template[name] == selectTemplate: index = self.dialog.jobTemplate.count() self.dialog.jobTemplate.addItem(name, template[name]) self.dialog.jobTemplate.setCurrentIndex(index) self.dialog.templateGroup.show()
def RegisterViewProvider(name, provider): '''RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name an instance of provider is used instead.''' PathLog.track(name) global _factory _factory[name] = provider
def setup(self, obj): PathLog.info("Here we go ... ") if hasattr(obj.Base, "BoneBlacklist"): # dressing up a bone dressup obj.Side = obj.Base.Side else: # otherwise dogbones are opposite of the base path's side if obj.Base.Side == Side.Left: obj.Side = Side.Right elif obj.Base.Side == Side.Right: obj.Side = Side.Left else: # This will cause an error, which is fine for now 'cause I don't know what to do here obj.Side = 'On' self.toolRadius = 5 toolLoad = obj.ToolController if toolLoad is None or toolLoad.ToolNumber == 0: self.toolRadius = 5 else: tool = toolLoad.Proxy.getTool(toolLoad) #PathUtils.getTool(obj, toolLoad.ToolNumber) if not tool or tool.Diameter == 0: self.toolRadius = 5 else: self.toolRadius = tool.Diameter / 2 self.shapes = {} self.dbg = []
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() if obj.UseComp: self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) shapes = [] if obj.Base: basewires = [] for b in obj.Base: edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) basewires.append((b[0], findWires(edgelist))) for base,wires in basewires: for wire in wires: f = Part.makeFace(wire, 'Part::FaceMakerSimple') # shift the compound to the bottom of the base object for # proper sectioning zShift = b[0].Shape.BoundBox.ZMin - f.BoundBox.ZMin newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) f.Placement = newPlace env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, False)) return shapes
def isHorizontal(obj): '''isHorizontal(obj) ... answer True if obj points into X or Y''' if type(obj) == FreeCAD.Vector: return isRoughly(obj.z, 0) if obj.ShapeType == 'Face': if type(obj.Surface) == Part.Plane: return isVertical(obj.Surface.Axis) if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone: return isHorizontal(obj.Surface.Axis) if type(obj.Surface) == Part.Sphere: return True if type(obj.Surface) == Part.SurfaceOfExtrusion: return isHorizontal(obj.Surface.Direction) if type(obj.Surface) == Part.SurfaceOfRevolution: return isVertical(obj.Surface.Direction) return isRoughly(obj.BoundBox.ZLength, 0.0) if obj.ShapeType == 'Edge': if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment: return isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point) if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve: return isVertical(obj.Curve.Axis) return isRoughly(obj.BoundBox.ZLength, 0.0) PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj) return None
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] for b in obj.Base: for sub in b[1]: shape = getattr(b[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) else: PathLog.debug('The base subobject is not a face') return planeshape = Part.makeCompound(faces) PathLog.debug("Working on a collection of faces {}".format(faces)) # If no base object, do planing of top surface of entire model else: planeshape = self.baseobject.Shape PathLog.debug("Working on a shape {}".format(self.baseobject.Name)) # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) elif obj.BoundaryShape == 'Stock': stock = PathUtils.findParentJob(obj).Stock.Shape env = stock else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) return [(env, False)]
def opExecute(self, obj): '''opExecute(obj) ... processes all Base features and Locations and collects them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). If no Base geometries and no Locations are present, the job's Base is inspected and all drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' PathLog.track() def haveLocations(self, obj): if PathOp.FeatureLocations & self.opFeatures(obj): return len(obj.Locations) != 0 return False holes = [] for base, subs in obj.Base: for sub in subs: if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)}) if haveLocations(self, obj): for location in obj.Locations: holes.append({'x': location.x, 'y': location.y, 'r': 0}) if len(holes) > 0: self.circularHoleExecute(obj, holes)
def __init__(self, obj): PathLog.track(obj.Base.Name) self.obj = obj self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) self.rapid = _RapidEdges(rapid) self.edges = self.wire.Edges self.baseWire = self.findBottomWire(self.edges)
def orderAndFlipEdges(self, inputEdges): PathLog.track("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) self.edgesOrder = [] outputEdges = [] p0 = self.entry lastP = p0 edges = copy.copy(inputEdges) while edges: # print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in copy.copy(edges): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if PathGeom.pointsCoincide(p1, p0): outputEdges.append((e, False)) edges.remove(e) lastP = None p0 = p2 debugEdge(e, ">>>>> no flip") break elif PathGeom.pointsCoincide(p2, p0): flipped = PathGeom.flipEdge(e) if not flipped is None: outputEdges.append((flipped, True)) else: p0 = None cnt = 0 for p in reversed(e.discretize(Deflection=0.01)): if not p0 is None: outputEdges.append((Part.Edge(Part.LineSegment(p0, p)), True)) cnt = cnt + 1 p0 = p PathLog.info("replaced edge with %d straight segments" % cnt) edges.remove(e) lastP = None p0 = p1 debugEdge(e, ">>>>> flip") break else: debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) if lastP == p0: self.edgesOrder.append(outputEdges) self.edgesOrder.append(edges) print('input edges:') for e in inputEdges: debugEdge(e, ' ', False) print('ordered edges:') for e, flip in outputEdges: debugEdge(e, ' %c ' % ('<' if flip else '>'), False) print('remaining edges:') for e in edges: debugEdge(e, ' ', False) raise ValueError("No connection to %s" % (p0)) elif lastP: PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) else: PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) lastP = p0 PathLog.track("-") return outputEdges
def initOperation(self, obj): PathLog.track(obj.Label) obj.addProperty('App::PropertyDistance', 'Width', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The desired width of the chamfer')) obj.addProperty('App::PropertyDistance', 'ExtraDepth', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The additional depth of the tool path')) obj.addProperty('App::PropertyEnumeration', 'Join', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'How to join chamfer segments')) obj.Join = ['Round', 'Miter'] obj.setEditorMode('Join', 2) # hide for now
def __init__(self, edge, tag, i, segm, maxZ): debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) self.tag = tag self.segm = segm self.maxZ = maxZ if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): tail = edge self.commands = [] debugEdge(tail, '.........=') elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): debugEdge(edge, '++++++++ .') self.commands = PathGeom.cmdsForEdge(edge, segm=segm) tail = None else: e, tail = PathGeom.splitEdgeAt(edge, i) debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e, segm=segm) debugEdge(tail, '.........-') self.initialEdge = edge self.tail = tail self.edges = [] self.entry = i if tail: PathLog.debug("MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter))) else: PathLog.debug("MapWireToTag(%s - )" % i) self.complete = False self.haveProblem = False
def adjustWirePlacement(obj, shape, wires): job = PathUtils.findParentJob(obj) if hasattr(shape, 'MapMode') and 'Deactivated' != shape.MapMode: if hasattr(shape, 'Support') and 1 == len(shape.Support) and 1 == len(shape.Support[0][1]): pmntShape = shape.Placement pmntSupport = shape.Support[0][0].getGlobalPlacement() #pmntSupport = shape.Support[0][0].Placement pmntBase = job.Base.Placement pmnt = pmntBase.multiply(pmntSupport.inverse().multiply(pmntShape)) #PathLog.debug("pmnt = %s" % pmnt) newWires = [] for w in wires: edges = [] for e in w.Edges: e = e.copy() e.Placement = FreeCAD.Placement() edges.append(e) w = Part.Wire(edges) w.Placement = pmnt newWires.append(w) wires = newWires else: PathLog.warning(translate("PathEngrave", "Attachment not supported by engraver")) else: PathLog.debug("MapMode: %s" % (shape.MapMode if hasattr(shape, 'MapMode') else 'None')) return wires
def opSetDefaultValues(self, obj, job): PathLog.track(obj.Label, job.Label) obj.Width = '1 mm' obj.ExtraDepth = '0.1 mm' obj.Join = 'Round' obj.setExpression('StepDown', '0 mm') obj.StepDown = '0 mm'
def commandsForEdges(self): global failures if self.edges: try: shape = self.shell().common(self.tag.solid) commands = [] rapid = None for e, flip in self.orderAndFlipEdges(self.cleanupEdges(shape.Edges)): debugEdge(e, '++++++++ %s' % ('<' if flip else '>'), False) p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if self.tag.isSquare and (PathGeom.isRoughly(p1.z, self.maxZ) or p1.z > self.maxZ) and (PathGeom.isRoughly(p2.z, self.maxZ) or p2.z > self.maxZ): rapid = p1 if flip else p2 else: if rapid: commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z})) rapid = None commands.extend(PathGeom.cmdsForEdge(e, False, False, self.segm)) if rapid: commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z})) rapid = None return commands except Exception as e: PathLog.error("Exception during processing tag @(%.2f, %.2f) (%s) - disabling the tag" % (self.tag.x, self.tag.y, e.args[0])) #traceback.print_exc(e) self.tag.enabled = False commands = [] for e in self.edges: commands.extend(PathGeom.cmdsForEdge(e)) failures.append(self) return commands return []
def areaOpOnChanged(self, obj, prop): '''areaOpOnChanged(obj, prop) ... facing specific depths calculation.''' PathLog.track(prop) if prop == "StepOver" and obj.StepOver == 0: obj.StepOver = 1 # default depths calculation not correct for facing if prop == "Base": job = PathUtils.findParentJob(obj) obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax if len(obj.Base) >= 1: print('processing') sublist = [] for i in obj.Base: o = i[0] for s in i[1]: sublist.append(o.Shape.getElement(s)) # If the operation has a geometry identified the Finaldepth # is the top of the bboundbox which includes all features. # Otherwise, top of part. obj.OpFinalDepth = Part.makeCompound(sublist).BoundBox.ZMax else: obj.OpFinalDepth = job.Base.Shape.BoundBox.ZMax
def addToJob(obj, jobname=None): '''adds a path object to a job obj = obj jobname = None''' PathLog.track(jobname) if jobname is not None: jobs = GetJobs(jobname) if len(jobs) == 1: job = jobs[0] else: PathLog.error(translate("Path", "Didn't find job %s") % jobname) return None else: jobs = GetJobs() if len(jobs) == 0: job = PathJobCmd.CommandJobCreate().Activated() elif len(jobs) == 1: job = jobs[0] else: # form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgJobChooser.ui") form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui") mylist = [i.Label for i in jobs] form.cboProject.addItems(mylist) r = form.exec_() if r is False: return None else: print(form.cboProject.currentText()) job = [i for i in jobs if i.Label == form.cboProject.currentText()][0] if obj and job: job.Proxy.addOperation(obj) return job
def __getstate__(self): PathLog.track() return None
def libPaths(self): lib = PathPreferences.lastFileToolLibrary() loc = PathPreferences.lastPathToolLibrary() PathLog.track("lib: {} loc: {}".format(lib, loc)) return lib, loc
# * USA * # * * # *************************************************************************** import FreeCAD import Path import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathStock as PathStock import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils from PySide import QtCore PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) def _vstr(v): if v: return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z) return '-' class DressupPathBoundary(object):
def CreateFromTemplate(job, template): if template.get('version') and 1 == int(template['version']): stockType = template.get('create') if stockType: placement = None posX = template.get('posX') posY = template.get('posY') posZ = template.get('posZ') rotX = template.get('rotX') rotY = template.get('rotY') rotZ = template.get('rotZ') rotW = template.get('rotW') if posX is not None and posY is not None and posZ is not None and rotX is not None and rotY is not None and rotZ is not None and rotW is not None: pos = FreeCAD.Vector(float(posX), float(posY), float(posZ)) rot = FreeCAD.Rotation(float(rotX), float(rotY), float(rotZ), float(rotW)) placement = FreeCAD.Placement(pos, rot) elif posX is not None or posY is not None or posZ is not None or rotX is not None or rotY is not None or rotZ is not None or rotW is not None: PathLog.warning( translate( 'PathStock', 'Corrupted or incomplete placement information in template - ignoring' )) if stockType == StockType.FromBase: xneg = template.get('xneg') xpos = template.get('xpos') yneg = template.get('yneg') ypos = template.get('ypos') zneg = template.get('zneg') zpos = template.get('zpos') neg = None pos = None if xneg is not None and xpos is not None and yneg is not None and ypos is not None and zneg is not None and zpos is not None: neg = FreeCAD.Vector( FreeCAD.Units.Quantity(xneg).Value, FreeCAD.Units.Quantity(yneg).Value, FreeCAD.Units.Quantity(zneg).Value) pos = FreeCAD.Vector( FreeCAD.Units.Quantity(xpos).Value, FreeCAD.Units.Quantity(ypos).Value, FreeCAD.Units.Quantity(zpos).Value) elif xneg is not None or xpos is not None or yneg is not None or ypos is not None or zneg is not None or zpos is not None: PathLog.error( translate( 'PathStock', 'Corrupted or incomplete specification for creating stock from base - ignoring extent' )) return CreateFromBase(job, neg, pos, placement) if stockType == StockType.CreateBox: PathLog.track(' create box') length = template.get('length') width = template.get('width') height = template.get('height') extent = None if length is not None and width is not None and height is not None: PathLog.track(' have extent') extent = FreeCAD.Vector( FreeCAD.Units.Quantity(length).Value, FreeCAD.Units.Quantity(width).Value, FreeCAD.Units.Quantity(height).Value) elif length is not None or width is not None or height is not None: PathLog.error( translate( 'PathStock', 'Corrupted or incomplete size for creating a stock box - ignoring size' )) else: PathLog.track( " take placement (%s) and extent (%s) from model" % (placement, extent)) return CreateBox(job, extent, placement) if stockType == StockType.CreateCylinder: radius = template.get('radius') height = template.get('height') if radius is not None and height is not None: pass elif radius is not None or height is not None: radius = None height = None PathLog.error( translate( 'PathStock', 'Corrupted or incomplete size for creating a stock cylinder - ignoring size' )) return CreateCylinder(job, radius, height, placement) PathLog.error( translate('PathStock', 'Unsupported stock type named {}').format(stockType)) else: PathLog.error( translate('PathStock', 'Unsupported PathStock template version {}').format( template.get('version'))) return None
def groupConnectedEdges(self, holding): """groupConnectedEdges(self, holding) Take edges and determine which are connected. Group connected chains/loops into: low and high""" holds = [] grps = [] searched = [] stop = False attachments = [] loops = 1 def updateAttachments(grps): atchmnts = [] lenGrps = len(grps) if lenGrps > 0: lenG0 = len(grps[0]) if lenG0 < 2: atchmnts.append((0, 0)) else: atchmnts.append((0, 0)) atchmnts.append((0, lenG0 - 1)) if lenGrps == 2: lenG1 = len(grps[1]) if lenG1 < 2: atchmnts.append((1, 0)) else: atchmnts.append((1, 0)) atchmnts.append((1, lenG1 - 1)) return atchmnts def isSameVertex(o, t): if o.X == t.X: if o.Y == t.Y: if o.Z == t.Z: return True return False for hi in range(0, len(holding)): holds.append(hi) # Place initial edge in first group and update attachments h0 = holds.pop() grps.append([h0]) attachments = updateAttachments(grps) while len(holds) > 0: if loops > 500: PathLog.error("BREAK --- LOOPS LIMIT of 500 ---") break save = False h2 = holds.pop() (sub2, face2, ei2) = holding[h2] # Cycle through attachments for connection to existing for (g, t) in attachments: h1 = grps[g][t] (sub1, face1, ei1) = holding[h1] edg1 = face1.Edges[ei1] edg2 = face2.Edges[ei2] # CV = self.hasCommonVertex(edg1, edg2, show=False) # Check attachment based on attachments order if t == 0: # is last vertex of h2 == first vertex of h1 e2lv = len(edg2.Vertexes) - 1 one = edg2.Vertexes[e2lv] two = edg1.Vertexes[0] if isSameVertex(one, two) is True: # Connected, insert h1 in front of h2 grps[g].insert(0, h2) stop = True else: # is last vertex of h1 == first vertex of h2 e1lv = len(edg1.Vertexes) - 1 one = edg1.Vertexes[e1lv] two = edg2.Vertexes[0] if isSameVertex(one, two) is True: # Connected, append h1 after h2 grps[g].append(h2) stop = True if stop is True: # attachment was found attachments = updateAttachments(grps) holds.extend(searched) stop = False break else: # no attachment found save = True # Efor if save is True: searched.append(h2) if len(holds) == 0: if len(grps) == 1: h0 = searched.pop(0) grps.append([h0]) attachments = updateAttachments(grps) holds.extend(searched) # Eif loops += 1 # Ewhile low = [] high = [] if len(grps) == 1: grps.append([]) grp0 = [] grp1 = [] com0 = FreeCAD.Vector(0, 0, 0) com1 = FreeCAD.Vector(0, 0, 0) if len(grps[0]) > 0: for g in grps[0]: grp0.append(holding[g]) (sub, face, ei) = holding[g] com0 = com0.add(face.Edges[ei].CenterOfMass) com0z = com0.z / len(grps[0]) if len(grps[1]) > 0: for g in grps[1]: grp1.append(holding[g]) (sub, face, ei) = holding[g] com1 = com1.add(face.Edges[ei].CenterOfMass) com1z = com1.z / len(grps[1]) if len(grps[1]) > 0: if com0z > com1z: low = grp1 high = grp0 else: low = grp0 high = grp1 else: low = grp0 high = grp0 return (low, high)
def calculateAdaptivePocket(self, obj, base, subObjTups): """calculateAdaptivePocket(obj, base, subObjTups) Orient multiple faces around common facial center of mass. Identify edges that are connections for adjacent faces. Attempt to separate unconnected edges into top and bottom loops of the pocket. Trim the top and bottom of the pocket if available and requested. return: tuple with pocket shape information""" low = [] high = [] removeList = [] Faces = [] allEdges = [] makeHighFace = 0 tryNonPlanar = False isHighFacePlanar = True isLowFacePlanar = True for (sub, face) in subObjTups: Faces.append(face) # identify max and min face heights for top loop (zmin, zmax) = self.getMinMaxOfFaces(Faces) # Order faces around common center of mass subObjTups = self.orderFacesAroundCenterOfMass(subObjTups) # find connected edges and map to edge names of base (connectedEdges, touching) = self.findSharedEdges(subObjTups) (low, high) = self.identifyUnconnectedEdges(subObjTups, touching) if len(high) > 0 and obj.AdaptivePocketStart is True: # attempt planar face with top edges of pocket allEdges = [] makeHighFace = 0 tryNonPlanar = False for (sub, face, ei) in high: allEdges.append(face.Edges[ei]) (hzmin, hzmax) = self.getMinMaxOfFaces(allEdges) try: highFaceShape = Part.Face( Part.Wire(Part.__sortEdges__(allEdges))) except Exception as ee: PathLog.warning(ee) PathLog.error( translate( "Path", "A planar adaptive start is unavailable. The non-planar will be attempted.", )) tryNonPlanar = True else: makeHighFace = 1 if tryNonPlanar is True: try: highFaceShape = Part.makeFilledFace( Part.__sortEdges__(allEdges)) # NON-planar face method except Exception as eee: PathLog.warning(eee) PathLog.error( translate( "Path", "The non-planar adaptive start is also unavailable." ) + "(1)") isHighFacePlanar = False else: makeHighFace = 2 if makeHighFace > 0: FreeCAD.ActiveDocument.addObject("Part::Feature", "topEdgeFace") highFace = FreeCAD.ActiveDocument.ActiveObject highFace.Shape = highFaceShape removeList.append(highFace.Name) # verify non-planar face is within high edge loop Z-boundaries if makeHighFace == 2: mx = hzmax + obj.StepDown.Value mn = hzmin - obj.StepDown.Value if (highFace.Shape.BoundBox.ZMax > mx or highFace.Shape.BoundBox.ZMin < mn): PathLog.warning("ZMaxDiff: {}; ZMinDiff: {}".format( highFace.Shape.BoundBox.ZMax - mx, highFace.Shape.BoundBox.ZMin - mn, )) PathLog.error( translate( "Path", "The non-planar adaptive start is also unavailable." ) + "(2)") isHighFacePlanar = False makeHighFace = 0 else: isHighFacePlanar = False if len(low) > 0 and obj.AdaptivePocketFinish is True: # attempt planar face with bottom edges of pocket allEdges = [] for (sub, face, ei) in low: allEdges.append(face.Edges[ei]) # (lzmin, lzmax) = self.getMinMaxOfFaces(allEdges) try: lowFaceShape = Part.Face( Part.Wire(Part.__sortEdges__(allEdges))) # lowFaceShape = Part.makeFilledFace(Part.__sortEdges__(allEdges)) # NON-planar face method except Exception as ee: PathLog.error(ee) PathLog.error("An adaptive finish is unavailable.") isLowFacePlanar = False else: FreeCAD.ActiveDocument.addObject("Part::Feature", "bottomEdgeFace") lowFace = FreeCAD.ActiveDocument.ActiveObject lowFace.Shape = lowFaceShape removeList.append(lowFace.Name) else: isLowFacePlanar = False # Start with a regular pocket envelope strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value cuts = [] starts = [] finals = [] starts.append(obj.StartDepth.Value) finals.append(zmin) if obj.AdaptivePocketStart is True or len(subObjTups) == 1: strDep = zmax + obj.StepDown.Value starts.append(zmax + obj.StepDown.Value) finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 depthparams = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, user_depths=None, ) shape = Part.makeCompound(Faces) env = PathUtils.getEnvelope(base[0].Shape, subshape=shape, depthparams=depthparams) cuts.append(env.cut(base[0].Shape)) # Might need to change to .cut(job.Stock.Shape) if pocket has no bottom # job = PathUtils.findParentJob(obj) # envBody = env.cut(job.Stock.Shape) if isHighFacePlanar is True and len(subObjTups) > 1: starts.append(hzmax + obj.StepDown.Value) # make shape to trim top of reg pocket strDep1 = obj.StartDepth.Value + (hzmax - hzmin) if makeHighFace == 1: # Planar face finDep1 = highFace.Shape.BoundBox.ZMin + obj.StepDown.Value else: # Non-Planar face finDep1 = hzmin + obj.StepDown.Value depthparams1 = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep1, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep1, user_depths=None, ) envTop = PathUtils.getEnvelope(base[0].Shape, subshape=highFace.Shape, depthparams=depthparams1) cbi = len(cuts) - 1 cuts.append(cuts[cbi].cut(envTop)) if isLowFacePlanar is True and len(subObjTups) > 1: # make shape to trim top of pocket if makeHighFace == 1: # Planar face strDep2 = lowFace.Shape.BoundBox.ZMax else: # Non-Planar face strDep2 = hzmax finDep2 = obj.FinalDepth.Value depthparams2 = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep2, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep2, user_depths=None, ) envBottom = PathUtils.getEnvelope(base[0].Shape, subshape=lowFace.Shape, depthparams=depthparams2) cbi = len(cuts) - 1 cuts.append(cuts[cbi].cut(envBottom)) # package pocket details into tuple cbi = len(cuts) - 1 pocket = (cuts[cbi], False, "3DPocket") if FreeCAD.GuiUp: import FreeCADGui for rn in removeList: FreeCADGui.ActiveDocument.getObject(rn).Visibility = False for rn in removeList: FreeCAD.ActiveDocument.getObject(rn).purgeTouched() self.tempObjectNames.append(rn) return pocket
def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" PathLog.track() subObjTups = [] removalshapes = [] if obj.Base: PathLog.debug("base items exist. Processing... ") for base in obj.Base: PathLog.debug("obj.Base item: {}".format(base)) # Check if all subs are faces allSubsFaceType = True Faces = [] for sub in base[1]: if "Face" in sub: face = getattr(base[0].Shape, sub) Faces.append(face) subObjTups.append((sub, face)) else: allSubsFaceType = False break if len(Faces) == 0: allSubsFaceType = False if (allSubsFaceType is True and obj.HandleMultipleFeatures == "Collectively"): (fzmin, fzmax) = self.getMinMaxOfFaces(Faces) if obj.FinalDepth.Value < fzmin: PathLog.warning( translate( "PathPocket", "Final depth set below ZMin of face(s) selected.", )) if (obj.AdaptivePocketStart is True or obj.AdaptivePocketFinish is True): pocketTup = self.calculateAdaptivePocket( obj, base, subObjTups) if pocketTup is not False: obj.removalshape = pocketTup[0] removalshapes.append( pocketTup) # (shape, isHole, detail) else: shape = Part.makeCompound(Faces) env = PathUtils.getEnvelope( base[0].Shape, subshape=shape, depthparams=self.depthparams) rawRemovalShape = env.cut(base[0].Shape) faceExtrusions = [ f.extrude(FreeCAD.Vector(0.0, 0.0, 1.0)) for f in Faces ] obj.removalshape = _identifyRemovalSolids( rawRemovalShape, faceExtrusions) removalshapes.append( (obj.removalshape, False, "3DPocket")) # (shape, isHole, detail) else: for sub in base[1]: if "Face" in sub: shape = Part.makeCompound( [getattr(base[0].Shape, sub)]) else: edges = [ getattr(base[0].Shape, sub) for sub in base[1] ] shape = Part.makeFace(edges, "Part::FaceMakerSimple") env = PathUtils.getEnvelope( base[0].Shape, subshape=shape, depthparams=self.depthparams) rawRemovalShape = env.cut(base[0].Shape) faceExtrusions = [ shape.extrude(FreeCAD.Vector(0.0, 0.0, 1.0)) ] obj.removalshape = _identifyRemovalSolids( rawRemovalShape, faceExtrusions) removalshapes.append( (obj.removalshape, False, "3DPocket")) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") for base in self.model: if obj.ProcessStockArea is True: job = PathUtils.findParentJob(obj) stockEnvShape = PathUtils.getEnvelope( job.Stock.Shape, subshape=None, depthparams=self.depthparams) rawRemovalShape = stockEnvShape.cut(base.Shape) else: env = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthparams) rawRemovalShape = env.cut(base.Shape) # Identify target removal shapes after cutting envelope with base shape removalSolids = [ s for s in rawRemovalShape.Solids if PathGeom.isRoughly( s.BoundBox.ZMax, rawRemovalShape.BoundBox.ZMax) ] # Fuse multiple solids if len(removalSolids) > 1: seed = removalSolids[0] for tt in removalSolids[1:]: fusion = seed.fuse(tt) seed = fusion removalShape = seed else: removalShape = removalSolids[0] obj.removalshape = removalShape removalshapes.append((obj.removalshape, False, "3DPocket")) return removalshapes
import PathScripts.PathUtils as PathUtils # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader PathGeom = LazyLoader("PathScripts.PathGeom", globals(), "PathScripts.PathGeom") __title__ = "Path 3D Pocket Operation" __author__ = "Yorik van Havre <*****@*****.**>" __url__ = "https://www.freecadweb.org" __doc__ = "Class and implementation of the 3D Pocket operation." __created__ = "2014" if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) translate = FreeCAD.Qt.translate class ObjectPocket(PathPocketBase.ObjectPocket): """Proxy object for Pocket operation.""" def pocketOpFeatures(self, obj): return PathOp.FeatureNoFinalDepth def initPocketOp(self, obj): """initPocketOp(obj) ... setup receiver""" if not hasattr(obj, "HandleMultipleFeatures"):
def open(self): PathLog.track() return self.form.exec_()
def checkWorkingDir(): # users shouldn't use the example toolbits and libraries. # working directory should be writable PathLog.track() workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary()) defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath()) PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir)) dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK)) if dirOK(): return True qm = PySide.QtGui.QMessageBox ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No) if ret == qm.No: return False msg = translate("Path", "Choose a writable location for your toolbits", None) while not dirOK(): workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg, PathPreferences.filePath()) if workingdir[-8:] == os.path.sep + 'Library': workingdir = workingdir[:-8] # trim off trailing /Library if user chose it PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep)) PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep)) PathLog.debug('setting workingdir to: {}'.format(workingdir)) # Copy only files of default Path\Tools folder to working directory (targeting the README.md help file) src_toolfiles = os.listdir(defaultdir) for file_name in src_toolfiles: if file_name in ["README.md"]: full_file_name = os.path.join(defaultdir, file_name) if os.path.isfile(full_file_name): shutil.copy(full_file_name, workingdir) # Determine which subdirectories are missing subdirlist = ['Bit', 'Library', 'Shape'] mode = 0o777 for dir in subdirlist.copy(): subdir = "{}{}{}".format(workingdir, os.path.sep, dir) if os.path.exists(subdir): subdirlist.remove(dir) # Query user for creation permission of any missing subdirectories if len(subdirlist) >= 1: needed = ', '.join([str(d) for d in subdirlist]) qm = PySide.QtGui.QMessageBox ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No) if ret == qm.No: return False else: # Create missing subdirectories if user agrees to creation for dir in subdirlist: subdir = "{}{}{}".format(workingdir, os.path.sep, dir) os.mkdir(subdir, mode) # Query user to copy example files into subdirectories created if dir != 'Shape': qm = PySide.QtGui.QMessageBox ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No) if ret == qm.Yes: src="{}{}{}".format(defaultdir, os.path.sep, dir) src_files = os.listdir(src) for file_name in src_files: full_file_name = os.path.join(src, file_name) if os.path.isfile(full_file_name): shutil.copy(full_file_name, subdir) # if no library is set, choose the first one in the Library directory if PathPreferences.lastFileToolLibrary() is None: libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')] PathPreferences.setLastFileToolLibrary(libFiles[0]) return True
def __init__(self, path=None): PathLog.track() self.path = ""
def toolDelete(self): PathLog.track() selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()]) for row in sorted(list(selectedRows), key=lambda r: -r): self.toolModel.removeRows(row, 1)
def areaOpRetractTool(self, obj): PathLog.debug("retracting tool: %d" % (not obj.KeepToolDown)) return not obj.KeepToolDown
def offsetWire(wire, base, offset, forward): '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. ''' PathLog.track('offsetWire') if 1 == len(wire.Edges): edge = wire.Edges[0] curve = edge.Curve if Part.Circle == type(curve) and wire.isClosed(): # it's a full circle and there are some problems with that, see # http://www.freecadweb.org/wiki/Part%20Offset2D # it's easy to construct them manually though z = -1 if forward else 1 edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) if base.isInside(edge.Vertexes[0].Point, offset / 2, True): if offset > curve.Radius or PathGeom.isRoughly( offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) w = Part.Wire([edge]) return w if Part.Line == type(curve) or Part.LineSegment == type(curve): # offsetting a single edge doesn't work because there is an infinite # possible planes into which the edge could be offset # luckily, the plane here must be the XY-plane ... p0 = edge.Vertexes[0].Point v0 = edge.Vertexes[1].Point - p0 n = v0.cross(FreeCAD.Vector(0, 0, 1)) o = n.normalize() * offset edge.translate(o) # offset edde the other way if the result is inside if base.isInside( edge.valueAt( (edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True): edge.translate(-2 * o) # flip the edge if it's not on the right side of the original edge if forward is not None: v1 = edge.Vertexes[1].Point - p0 left = PathGeom.Side.Left == PathGeom.Side.of(v0, v1) if left != forward: edge = PathGeom.flipEdge(edge) return Part.Wire([edge]) # if we get to this point the assumption is that makeOffset2D can deal with the edge pass # pylint: disable=unnecessary-pass owire = orientWire(wire.makeOffset2D(offset), True) debugWire('makeOffset2D_%d' % len(wire.Edges), owire) if wire.isClosed(): if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True): PathLog.track('closed - outside') return orientWire(owire, forward) PathLog.track('closed - inside') try: owire = wire.makeOffset2D(-offset) except Exception: # pylint: disable=broad-except # most likely offsetting didn't work because the wire is a hole # and the offset is too big - making the hole vanish return None # For negative offsets (holes) 'forward' is the other way if forward is None: return orientWire(owire, None) return orientWire(owire, not forward) # An edge is considered to be inside of shape if the mid point is inside # Of the remaining edges we take the longest wire to be the engraving side # Looking for a circle with the start vertex as center marks and end # starting from there follow the edges until a circle with the end vertex as center is found # if the traversed edges include any oof the remainig from above, all those edges are remaining # this is to also include edges which might partially be inside shape # if they need to be discarded, split, that should happen in a post process # Depending on the Axis of the circle, and which side remains we know if the wire needs to be flipped # first, let's make sure all edges are oriented the proper way edges = _orientEdges(wire.Edges) # determine the start and end point start = edges[0].firstVertex().Point end = edges[-1].lastVertex().Point debugWire('wire', wire) debugWire('wedges', Part.Wire(edges)) # find edges that are not inside the shape common = base.common(owire) insideEndpoints = [e.lastVertex().Point for e in common.Edges] insideEndpoints.append(common.Edges[0].firstVertex().Point) def isInside(edge): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide( p, p1, 0.01): return True return False outside = [e for e in owire.Edges if not isInside(e)] # discard all edges that are not part of the longest wire longestWire = None for w in [Part.Wire(el) for el in Part.sortEdges(outside)]: if not longestWire or longestWire.Length < w.Length: longestWire = w debugWire('outside', Part.Wire(outside)) debugWire('longest', longestWire) def isCircleAt(edge, center): '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.''' if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type( edge.Curve): return PathGeom.pointsCoincide(edge.Curve.Center, center) return False # split offset wire into edges to the left side and edges to the right side collectLeft = False collectRight = False leftSideEdges = [] rightSideEdges = [] # traverse through all edges in order and start collecting them when we encounter # an end point (circle centered at one of the end points of the original wire). # should we come to an end point and determine that we've already collected the # next side, we're done for e in (owire.Edges + owire.Edges): if isCircleAt(e, start): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectLeft and leftSideEdges: break collectLeft = True collectRight = False else: if not collectRight and rightSideEdges: break collectLeft = False collectRight = True elif isCircleAt(e, end): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectRight and rightSideEdges: break collectLeft = False collectRight = True else: if not collectLeft and leftSideEdges: break collectLeft = True collectRight = False elif collectLeft: leftSideEdges.append(e) elif collectRight: rightSideEdges.append(e) debugWire('left', Part.Wire(leftSideEdges)) debugWire('right', Part.Wire(rightSideEdges)) # figure out if all the left sided edges or the right sided edges are the ones # that are 'outside'. However, we return the full side. edges = leftSideEdges for e in longestWire.Edges: for e0 in rightSideEdges: if PathGeom.edgesMatch(e, e0): edges = rightSideEdges PathLog.debug("#use right side edges") if not forward: PathLog.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None) # at this point we have the correct edges and they are in the order for forward # traversal (climb milling). If that's not what we want just reverse the order, # orientWire takes care of orienting the edges appropriately. PathLog.debug("#use left side edges") if not forward: PathLog.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None)
def execute(self): if not self.baseOp or not self.baseOp.isDerivedFrom('Path::Feature') or not self.baseOp.Path: return None if len(self.baseOp.Path.Commands) == 0: PathLog.warning("No Path Commands for %s" % self.baseOp.Label) return [] tc = PathDressup.toolController(self.baseOp) self.safeHeight = float(PathUtil.opProperty(self.baseOp, 'SafeHeight')) self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, 'ClearanceHeight')) self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value}) self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight}) cmd = self.baseOp.Path.Commands[0] pos = cmd.Placement.Base # bogus m/c position to create first edge bogusX = True bogusY = True commands = [cmd] lastExit = None for cmd in self.baseOp.Path.Commands[1:]: if cmd.Name in PathGeom.CmdMoveAll: if bogusX == True : bogusX = ( 'X' not in cmd.Parameters ) if bogusY : bogusY = ( 'Y' not in cmd.Parameters ) edge = PathGeom.edgeForCmd(cmd, pos) if edge: inside = edge.common(self.boundary).Edges outside = edge.cut(self.boundary).Edges if not self.inside: # UI "inside boundary" param tmp = inside inside = outside outside = tmp # it's really a shame that one cannot trust the sequence and/or # orientation of edges if 1 == len(inside) and 0 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) # cmd fully included by boundary if lastExit: if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) lastExit = None commands.append(cmd) pos = PathGeom.commandEndPoint(cmd, pos) elif 0 == len(inside) and 1 == len(outside): PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) # cmd fully excluded by boundary if not lastExit: lastExit = pos pos = PathGeom.commandEndPoint(cmd, pos) else: PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) # cmd pierces boundary while inside or outside: ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] PathLog.track(ie) if ie: e = ie[0] LastPt = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, LastPt) newPos = e.valueAt(e.FirstParameter) if flip else LastPt # inside edges are taken at this point (see swap of inside/outside # above - so we can just connect the dots ... if lastExit: if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) lastExit = None PathLog.track(e, flip) if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value)) inside.remove(e) pos = newPos lastExit = newPos else: oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] PathLog.track(oe) if oe: e = oe[0] ptL = e.valueAt(e.LastParameter) flip = PathGeom.pointsCoincide(pos, ptL) newPos = e.valueAt(e.FirstParameter) if flip else ptL # outside edges are never taken at this point (see swap of # inside/outside above) - so just move along ... outside.remove(e) pos = newPos else: PathLog.error('huh?') import Part Part.show(Part.Vertex(pos), 'pos') for e in inside: Part.show(e, 'ei') for e in outside: Part.show(e, 'eo') raise Exception('This is not supposed to happen') # Eif # Eif # Ewhile # Eif # pos = PathGeom.commandEndPoint(cmd, pos) # Eif else: PathLog.track('no-move', cmd) commands.append(cmd) if lastExit: commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) lastExit = None PathLog.track(commands) return Path.Path(commands)
def __setstate__(self, state): PathLog.track() return None
def _extractPathWire(self, obj, base, fWire, cutShp): PathLog.debug('_extractPathWire()') subLoops = list() rtnWIRES = list() osWrIdxs = list() subDistFactor = 1.0 # Raise to include sub wires at greater distance from original tmpGrp = self.tmpGrp fdv = obj.FinalDepth.Value wire = fWire.Shape lstVrtIdx = len(wire.Vertexes) - 1 lstVrt = wire.Vertexes[lstVrtIdx] frstVrt = wire.Vertexes[0] cent0 = FreeCAD.Vector(frstVrt.X, frstVrt.Y, fdv) cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv) pl = FreeCAD.Placement() pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) pl.Base = FreeCAD.Vector(0, 0, 0) # Calculate offset shape, containing cut region ofstShp = self._extractFaceOffset(obj, cutShp, False) # CHECK for ZERO area of offset shape try: osArea = ofstShp.Area except Exception as ee: PathLog.error('No area to offset shape returned.') tmpGrp.purgeTouched() return False os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOffsetShape') os.Shape = ofstShp os.recompute() os.purgeTouched() tmpGrp.addObject(os) numOSWires = len(ofstShp.Wires) for w in range(0, numOSWires): osWrIdxs.append(w) # Identify two vertexes for dividing offset loop NEAR0 = self._findNearestVertex(ofstShp, cent0) min0i = 0 min0 = NEAR0[0][4] for n in range(0, len(NEAR0)): N = NEAR0[n] if N[4] < min0: min0 = N[4] min0i = n (w0, vi0, pnt0, vrt0, d0) = NEAR0[0] # min0i near0 = Draft.makeWire([cent0, pnt0], placement=pl, closed=False, face=False, support=None) near0.recompute() near0.purgeTouched() tmpGrp.addObject(near0) NEAR1 = self._findNearestVertex(ofstShp, cent1) min1i = 0 min1 = NEAR1[0][4] for n in range(0, len(NEAR1)): N = NEAR1[n] if N[4] < min1: min1 = N[4] min1i = n (w1, vi1, pnt1, vrt1, d1) = NEAR1[0] # min1i near1 = Draft.makeWire([cent1, pnt1], placement=pl, closed=False, face=False, support=None) near1.recompute() near1.purgeTouched() tmpGrp.addObject(near1) if w0 != w1: PathLog.debug('w0 is {}.'.format(w0)) PathLog.debug('w1 is {}.'.format(w1)) PathLog.warning( 'Offset wire endpoint indexes are not equal: {}, {}'.format( w0, w1)) ''' PathLog.debug('min0i is {}.'.format(min0i)) PathLog.debug('min1i is {}.'.format(min1i)) PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0])) PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) PathLog.debug('NEAR0 is {}.'.format(NEAR0)) PathLog.debug('NEAR1 is {}.'.format(NEAR1)) ''' mainWire = ofstShp.Wires[w0] # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements if numOSWires > 1: # check all wires for proximity(children) to intersection tags tagsComList = list() for T in self.cutSideTags.Faces: tcom = T.CenterOfMass tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0) tagsComList.append(tv) subDist = self.ofstRadius * subDistFactor for w in osWrIdxs: if w != w0: cutSub = False VTXS = ofstShp.Wires[w].Vertexes for V in VTXS: v = FreeCAD.Vector(V.X, V.Y, 0.0) for t in tagsComList: if t.sub(v).Length < subDist: cutSub = True break if cutSub is True: break if cutSub is True: sub = Part.Wire( Part.__sortEdges__(ofstShp.Wires[w].Edges)) subLoops.append(sub) # Eif # Break offset loop into two wires - one of which is the desired profile path wire. # (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, ofstShp.Vertexes[vi0], ofstShp.Vertexes[vi1]) (edgeIdxs0, edgeIdxs1) = self._separateWireAtVertexes(mainWire, mainWire.Vertexes[vi0], mainWire.Vertexes[vi1]) edgs0 = list() edgs1 = list() for e in edgeIdxs0: edgs0.append(mainWire.Edges[e]) for e in edgeIdxs1: edgs1.append(mainWire.Edges[e]) part0 = Part.Wire(Part.__sortEdges__(edgs0)) part1 = Part.Wire(Part.__sortEdges__(edgs1)) # Determine which part is nearest original edge(s) distToPart0 = self._distMidToMid(wire.Wires[0], part0.Wires[0]) distToPart1 = self._distMidToMid(wire.Wires[0], part1.Wires[0]) if distToPart0 < distToPart1: rtnWIRES.append(part0) else: rtnWIRES.append(part1) rtnWIRES.extend(subLoops) tmpGrp.purgeTouched() return rtnWIRES
def attach(self, vobj): PathLog.track() self.Object = vobj.Object return
def execute(self, obj, getsim=False): PathLog.track() self.endVector = None if not obj.Active: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False return commandlist = [] toolLoad = obj.ToolController self.depthparams = depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=0.0, final_depth=obj.FinalDepth.Value, user_depths=None) if toolLoad is None or toolLoad.ToolNumber == 0: FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.") return else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = toolLoad.Proxy.getTool(toolLoad) if not tool or tool.Diameter == 0: FreeCAD.Console.PrintError("No Tool found or diameter is zero. We need a tool to build a Path.") return else: self.radius = tool.Diameter/2 commandlist.append(Path.Command("(" + obj.Label + ")")) if obj.UseComp: commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: commandlist.append(Path.Command("(Uncompensated Tool Path)")) parentJob = PathUtils.findParentJob(obj) if parentJob is None: return baseobject = parentJob.Base if baseobject is None: return isPanel = False if hasattr(baseobject, "Proxy"): if isinstance(baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet isPanel = True baseobject.Proxy.execute(baseobject) shapes = baseobject.Proxy.getOutlines(baseobject, transform=True) for shape in shapes: f = Part.makeFace([shape], 'Part::FaceMakerSimple') thickness = baseobject.Group[0].Source.Thickness contourshape = f.extrude(FreeCAD.Vector(0, 0, thickness)) try: (pp, sim) = self._buildPathArea(obj, contourshape, start=obj.StartPoint, getsim=getsim) commandlist.extend(pp.Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") if hasattr(baseobject, "Shape") and not isPanel: env = PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams) try: (pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint, getsim=getsim) commandlist.extend(pp.Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") # Let's finish by rapid to clearance...just for safety commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) PathLog.track() path = Path.Path(commandlist) obj.Path = path return sim
def deleteObjectsOnReject(self): PathLog.track() return hasattr(self, 'deleteOnReject') and self.deleteOnReject
def setMinimum(self, quantity): """setMinimum(quantity) ... set the minimum""" PathLog.track(self.prop, self.valid) if self.valid: value = quantity.Value if hasattr(quantity, "Value") else quantity self.widget.setProperty("setMinimum", value)
def __init__(self, vobj): PathLog.track() vobj.Proxy = self
import PathTurnScripts.PathTurnBaseGui as PathTurnBaseGui import PathTurnScripts.PathTurnProfile as PathTurnProfile import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui from PySide import QtCore __title__ = "Path Turn Profile Gui" __author__ = "dubstar-04 (Daniel Wood)" __url__ = "http://www.freecadweb.org" __doc__ = "Gui implementation for turning profiling operations." LOGLEVEL = False if LOGLEVEL: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule()) class TaskPanelOpPage(PathTurnBaseGui.TaskPanelTurnBase): '''Page controller class for Turning operations.''' def setOpFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' pass Command = PathOpGui.SetupOperation( 'TurnProfile', PathTurnProfile.Create, TaskPanelOpPage, 'Path-TurnProfile', QtCore.QT_TRANSLATE_NOOP("PathTurnProfile", "Turn Profile"),
def onChanged(self, obj, prop): PathLog.track('prop: {} state: {}'.format(prop, obj.State)) if prop in ['AreaParams', 'PathParams', 'removalshape']: obj.setEditorMode(prop, 2)
def _separateWireAtVertexes(self, wire, VV1, VV2): PathLog.debug('_separateWireAtVertexes()') tolerance = self.JOB.GeometryTolerance.Value grps = [[], []] wireIdxs = [[], []] V1 = FreeCAD.Vector(VV1.X, VV1.Y, VV1.Z) V2 = FreeCAD.Vector(VV2.X, VV2.Y, VV2.Z) lenE = len(wire.Edges) FLGS = list() for e in range(0, lenE): FLGS.append(0) chk4 = False for e in range(0, lenE): v = 0 E = wire.Edges[e] fv0 = FreeCAD.Vector(E.Vertexes[0].X, E.Vertexes[0].Y, E.Vertexes[0].Z) fv1 = FreeCAD.Vector(E.Vertexes[1].X, E.Vertexes[1].Y, E.Vertexes[1].Z) if fv0.sub(V1).Length < tolerance: v = 1 if fv1.sub(V2).Length < tolerance: v += 3 chk4 = True elif fv1.sub(V1).Length < tolerance: v = 1 if fv0.sub(V2).Length < tolerance: v += 3 chk4 = True if fv0.sub(V2).Length < tolerance: v = 3 if fv1.sub(V1).Length < tolerance: v += 1 chk4 = True elif fv1.sub(V2).Length < tolerance: v = 3 if fv0.sub(V1).Length < tolerance: v += 1 chk4 = True FLGS[e] += v # Efor PathLog.debug('_separateWireAtVertexes() FLGS: \n{}'.format(FLGS)) PRE = list() POST = list() IDXS = list() IDX1 = list() IDX2 = list() for e in range(0, lenE): f = FLGS[e] PRE.append(f) POST.append(f) IDXS.append(e) IDX1.append(e) IDX2.append(e) PRE.extend(FLGS) PRE.extend(POST) lenFULL = len(PRE) IDXS.extend(IDX1) IDXS.extend(IDX2) if chk4 is True: # find beginning 1 edge begIdx = None begFlg = False for e in range(0, lenFULL): f = PRE[e] i = IDXS[e] if f == 4: begIdx = e grps[0].append(f) wireIdxs[0].append(i) break # find first 3 edge endIdx = None for e in range(begIdx + 1, lenE + begIdx): f = PRE[e] i = IDXS[e] grps[1].append(f) wireIdxs[1].append(i) else: # find beginning 1 edge begIdx = None begFlg = False for e in range(0, lenFULL): f = PRE[e] if f == 1: if begFlg is False: begFlg = True else: begIdx = e break # find first 3 edge and group all first wire edges endIdx = None for e in range(begIdx, lenE + begIdx): f = PRE[e] i = IDXS[e] if f == 3: grps[0].append(f) wireIdxs[0].append(i) endIdx = e break else: grps[0].append(f) wireIdxs[0].append(i) # Collect remaining edges for e in range(endIdx + 1, lenFULL): f = PRE[e] i = IDXS[e] if f == 1: grps[1].append(f) wireIdxs[1].append(i) break else: wireIdxs[1].append(i) grps[1].append(f) # Efor # Eif if PathLog.getLevel(PathLog.thisModule()) != 4: PathLog.debug('grps[0]: {}'.format(grps[0])) PathLog.debug('grps[1]: {}'.format(grps[1])) PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0])) PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) PathLog.debug('PRE: {}'.format(PRE)) PathLog.debug('IDXS: {}'.format(IDXS)) return (wireIdxs[0], wireIdxs[1])
def expression(self): """expression() ... returns the expression if one is bound to the property""" PathLog.track(self.prop, self.valid) if self.valid: return self.widget.property("expression") return ""
def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj): PathLog.debug('_getCutAreaCrossSection()') tmpGrp = self.tmpGrp FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: bbBfr = minBfr fwBB = flatWireObj.Shape.BoundBox wBB = origWire.Shape.BoundBox minArea = (self.ofstRadius - tolerance)**2 * math.pi useWire = origWire.Shape.Wires[0] numOrigEdges = len(useWire.Edges) sdv = wBB.ZMax fdv = obj.FinalDepth.Value extLenFwd = sdv - fdv WIRE = flatWireObj.Shape.Wires[0] numEdges = len(WIRE.Edges) # Identify first/last edges and first/last vertex on wire begE = WIRE.Edges[0] # beginning edge endE = WIRE.Edges[numEdges - 1] # ending edge blen = begE.Length elen = endE.Length Vb = begE.Vertexes[0] # first vertex of wire Ve = endE.Vertexes[1] # last vertex of wire pb = FreeCAD.Vector(Vb.X, Vb.Y, fdv) pe = FreeCAD.Vector(Ve.X, Ve.Y, fdv) # Identify endpoints connecting circle center and diameter vectDist = pe.sub(pb) diam = vectDist.Length cntr = vectDist.multiply(0.5).add(pb) R = diam / 2 pl = FreeCAD.Placement() pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) pl.Base = FreeCAD.Vector(0, 0, 0) # Obtain beginning point perpendicular points if blen > 0.1: bcp = begE.valueAt(begE.getParameterByLength( 0.1)) # point returned 0.1 mm along edge else: bcp = FreeCAD.Vector(begE.Vertexes[1].X, begE.Vertexes[1].Y, fdv) if elen > 0.1: ecp = endE.valueAt(endE.getParameterByLength( elen - 0.1)) # point returned 0.1 mm along edge else: ecp = FreeCAD.Vector(endE.Vertexes[1].X, endE.Vertexes[1].Y, fdv) # Create intersection tags for determining which side of wire to cut (begInt, begExt, iTAG, eTAG) = self._makeIntersectionTags(useWire, numOrigEdges, fdv) self.iTAG = iTAG self.eTAG = eTAG # Create extended wire boundbox, and extrude extBndbox = self._makeExtendedBoundBox(wBB, bbBfr, fdv) extBndboxEXT = self._extrudeObject(extBndbox, extLenFwd) # (objToExt, extFwdLen) # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.Shape.cut(base.Shape) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() botFc = list() bbZMax = cutArea.BoundBox.ZMax bbZMin = cutArea.BoundBox.ZMin for f in range(0, len(cutArea.Faces)): FcBB = cutArea.Faces[f].BoundBox if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: topFc.append(f) if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: botFc.append(f) if len(topFc) == 0: PathLog.error('Failed to identify top faces of cut area.') return False topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate(FreeCAD.Vector( 0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth if len(botFc) > 1: PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Shape.Wires[0]) tmpFace = Part.Face(extBndbox.Shape.Wires[0]) for f in botFc: Q = tmpFace.cut(cutArea.Faces[f]) tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: botComp = Part.makeCompound([ cutArea.Faces[f] for f in botFc ]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) botComp.translate(FreeCAD.Vector( 0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth # Convert compound shapes to FC objects for use in multicommon operation TP = FCAD.addObject('Part::Feature', 'tmpTopCompound') TP.Shape = topComp TP.recompute() TP.purgeTouched() tmpGrp.addObject(TP) BT = FCAD.addObject('Part::Feature', 'tmpBotCompound') BT.Shape = botComp BT.recompute() BT.purgeTouched() tmpGrp.addObject(BT) # Make common of the two comFC = FCAD.addObject('Part::MultiCommon', 'tmpCommonTopBotFaces') comFC.Shapes = [TP, BT] comFC.recompute() TP.purgeTouched() BT.purgeTouched() comFC.purgeTouched() tmpGrp.addObject(comFC) # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) if cmnExtArea > cmnIntArea: PathLog.debug('Cutting on Ext side.') self.cutSide = 'E' self.cutSideTags = eTAG.Shape tagCOM = begExt.CenterOfMass else: PathLog.debug('Cutting on Int side.') self.cutSide = 'I' self.cutSideTags = iTAG.Shape tagCOM = begInt.CenterOfMass # Make two beginning style(oriented) 'L' shape stops begStop = self._makeStop('BEG', bcp, pb, 'BegStop') altBegStop = self._makeStop('END', bcp, pb, 'BegStop') # Identify to which style 'L' stop the beginning intersection tag is closest, # and create partner end 'L' stop geometry, and save for application later lenBS_extETag = begStop.CenterOfMass.sub(tagCOM).Length lenABS_extETag = altBegStop.CenterOfMass.sub(tagCOM).Length if lenBS_extETag < lenABS_extETag: endStop = self._makeStop('END', ecp, pe, 'EndStop') pathStops = Part.makeCompound([begStop, endStop]) else: altEndStop = self._makeStop('BEG', ecp, pe, 'EndStop') pathStops = Part.makeCompound([altBegStop, altEndStop]) pathStops.translate(FreeCAD.Vector(0, 0, fdv - pathStops.BoundBox.ZMin)) # Identify closed wire in cross-section that corresponds to user-selected edge(s) workShp = comFC.Shape fcShp = workShp wire = origWire.Shape # flatWireObj.Shape WS = workShp.Wires lenWS = len(WS) if lenWS < 3: wi = 0 else: wi = None for wvt in wire.Vertexes: for w in range(0, lenWS): twr = WS[w] for v in range(0, len(twr.Vertexes)): V = twr.Vertexes[v] if abs(V.X - wvt.X) < tolerance: if abs(V.Y - wvt.Y) < tolerance: # Same vertex found. This wire to be used for offset wi = w break # Efor if wi is None: PathLog.error( 'The cut area cross-section wire does not coincide with selected edge. Wires[] index is None.' ) tmpGrp.purgeTouched() return False else: PathLog.debug('Cross-section Wires[] index is {}.'.format(wi)) nWire = Part.Wire(Part.__sortEdges__(workShp.Wires[wi].Edges)) fcShp = Part.Face(nWire) fcShp.translate(FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) # Eif # verify that wire chosen is not inside the physical model if wi > 0: # and isInterior is False: PathLog.debug( 'Multiple wires in cut area. First choice is not 0. Testing.') testArea = fcShp.cut(base.Shape) # testArea = fcShp TA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutFaceTest') TA.Shape = testArea TA.purgeTouched() tmpGrp.addObject(TA) isReady = self._checkTagIntersection(iTAG, eTAG, self.cutSide, TA) PathLog.debug('isReady {}.'.format(isReady)) if isReady is False: PathLog.debug('Using wire index {}.'.format(wi - 1)) pWire = Part.Wire( Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate( FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) if testArea.Area < minArea: PathLog.debug( 'offset area is less than minArea of {}.'.format(minArea)) PathLog.debug('Using wire index {}.'.format(wi - 1)) pWire = Part.Wire( Part.__sortEdges__(workShp.Wires[wi - 1].Edges)) pfcShp = Part.Face(pWire) pfcShp.translate( FreeCAD.Vector(0, 0, fdv - workShp.BoundBox.ZMin)) workShp = pfcShp.cut(fcShp) # Eif # Add path stops at ends of wire cutShp = workShp.cut(pathStops) CF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutFace') CF.Shape = cutShp CF.recompute() CF.purgeTouched() tmpGrp.addObject(CF) tmpGrp.purgeTouched() return cutShp # CF.Shape
def __init__(self, widget, obj, prop, onBeforeChange=None): PathLog.track(widget) self.widget = widget self.onBeforeChange = onBeforeChange self.attachTo(obj, prop)
def flipWire(wire): '''Flip the entire wire and all its edges so it is being processed the other way around.''' edges = [flipEdge(e) for e in wire.Edges] edges.reverse() PathLog.debug(edges) return Part.Wire(edges)
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() self.tmpGrp = FreeCAD.ActiveDocument.addObject( 'App::DocumentObjectGroup', 'tmpDebugGrp') tmpGrpNm = self.tmpGrp.Name self.JOB = PathUtils.findParentJob(obj) self.offsetExtra = abs(obj.OffsetExtra.Value) if obj.UseComp: self.useComp = True self.ofstRadius = self.radius + self.offsetExtra self.commandlist.append( Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: self.useComp = False self.ofstRadius = self.offsetExtra self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) shapes = [] if obj.Base: basewires = [] zMin = None for b in obj.Base: edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) basewires.append((b[0], DraftGeomUtils.findWires(edgelist))) if zMin is None or b[0].Shape.BoundBox.ZMin < zMin: zMin = b[0].Shape.BoundBox.ZMin PathLog.debug( 'PathProfileEdges areaOpShapes():: len(basewires) is {}'. format(len(basewires))) for base, wires in basewires: for wire in wires: if wire.isClosed() is True: # f = Part.makeFace(wire, 'Part::FaceMakerSimple') # if planar error, Comment out previous line, uncomment the next two (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) f = origWire.Shape.Wires[0] if f is not False: # shift the compound to the bottom of the base object for proper sectioning zShift = zMin - f.BoundBox.ZMin newPlace = FreeCAD.Placement( FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) f.Placement = newPlace env = PathUtils.getEnvelope( base.Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, False)) else: PathLog.error( translate( 'PathProfileEdges', 'The selected edge(s) are inaccessible.')) else: if self.JOB.GeometryTolerance.Value == 0.0: msg = self.JOB.Label + '.GeometryTolerance = 0.0.' msg += translate( 'PathProfileEdges', 'Please set to an acceptable value greater than zero.' ) PathLog.error(msg) else: cutWireObjs = False (origWire, flatWire) = self._flattenWire( obj, wire, obj.FinalDepth.Value) cutShp = self._getCutAreaCrossSection( obj, base, origWire, flatWire) if cutShp is not False: cutWireObjs = self._extractPathWire( obj, base, flatWire, cutShp) if cutWireObjs is not False: for cW in cutWireObjs: shapes.append((cW, False)) self.profileEdgesIsOpen = True else: PathLog.error( translate( 'PathProfileEdges', 'The selected edge(s) are inaccessible.' )) # Delete the temporary objects if PathLog.getLevel(PathLog.thisModule()) != 4: for to in self.tmpGrp.Group: FreeCAD.ActiveDocument.removeObject(to.Name) FreeCAD.ActiveDocument.removeObject(tmpGrpNm) else: if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.ActiveDocument.getObject( tmpGrpNm).Visibility = False return shapes
def deleteObjectsOnReject(self): '''deleteObjectsOnReject() ... return true if all objects should be created if the user hits cancel. This is used during the initial edit session, if the user does not press OK, it is assumed they've changed their mind about creating the operation.''' PathLog.track() return hasattr(self, 'deleteOnReject') and self.deleteOnReject