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
def __init__(self, obj, base): "Make stock" obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("PathStock", "The base object this stock is derived from")) obj.addProperty("App::PropertyLength", "ExtXneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative X direction")) obj.addProperty("App::PropertyLength", "ExtXpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive X direction")) obj.addProperty("App::PropertyLength", "ExtYneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Y direction")) obj.addProperty("App::PropertyLength", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Y direction")) obj.addProperty("App::PropertyLength", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Z direction")) obj.addProperty("App::PropertyLength", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Z direction")) obj.Base = base obj.ExtXneg= 1.0 obj.ExtXpos= 1.0 obj.ExtYneg= 1.0 obj.ExtYpos= 1.0 obj.ExtZneg= 1.0 obj.ExtZpos= 1.0 # placement is only tracked on creation bb = shapeBoundBox(base.Group) if base else None if bb: obj.Placement = FreeCAD.Placement(FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation()) else: PathLog.track(obj.Label, base.Label) obj.Proxy = self
def opExecute(self, obj): PathLog.track(obj.Label) (depth, offset) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool) PathLog.track(obj.Label, depth, offset) self.basewires = [] self.adjusted_basewires = [] wires = [] for base, subs in obj.Base: edges = [] basewires = [] for f in subs: sub = base.Shape.getElement(f) if type(sub) == Part.Edge: edges.append(sub) elif sub.Wires: basewires.extend(sub.Wires) else: basewires.append(Part.Wire(sub.Edges)) self.edges = edges for edgelist in Part.sortEdges(edges): basewires.append(Part.Wire(edgelist)) self.basewires.extend(basewires) for w in self.adjustWirePlacement(obj, base, basewires): self.adjusted_basewires.append(w) wire = PathOpTools.offsetWire(w, base.Shape, offset, True) if wire: wires.append(wire) self.wires = wires self.buildpathocc(obj, wires, [depth], True)
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 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 cleanup(self, resetEdit): PathLog.track() self.vobj.Proxy.resetTaskPanel() FreeCADGui.Control.closeDialog() if resetEdit: FreeCADGui.ActiveDocument.resetEdit() FreeCAD.ActiveDocument.recompute()
def updateSelection(self): sel = FreeCADGui.Selection.getSelectionEx() PathLog.track(len(sel)) if len(sel) == 1 and len(sel[0].SubObjects) == 1: if 'Vertex' == sel[0].SubObjects[0].ShapeType: self.form.orientGroup.setEnabled(False) self.form.setOrigin.setEnabled(True) self.form.moveToOrigin.setEnabled(True) else: self.form.orientGroup.setEnabled(True) self.form.setOrigin.setEnabled(False) self.form.moveToOrigin.setEnabled(False) else: self.form.orientGroup.setEnabled(False) self.form.setOrigin.setEnabled(False) self.form.moveToOrigin.setEnabled(False) if len(sel) == 1 and sel[0].Object == self.obj.Base: self.form.centerInStock.setEnabled(True) self.form.centerInStockXY.setEnabled(True) else: if len(sel) == 1 and self.obj.Base: PathLog.debug("sel = %s / %s" % (sel[0].Object.Label, self.obj.Base.Label)) else: PathLog.debug("sel len = %d" % len(sel)) self.form.centerInStock.setEnabled(False) self.form.centerInStockXY.setEnabled(False)
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 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 areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") removalshapes = [] for b in obj.Base: PathLog.debug("Base item: {}".format(b)) for sub in b[1]: if "Face" in sub: shape = Part.makeCompound([getattr(b[0].Shape, sub)]) else: edges = [getattr(b[0].Shape, sub) for sub in b[1]] shape = Part.makeFace(edges, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(self.baseobject.Shape, subshape=shape, depthparams=self.depthparams) obj.removalshape = env.cut(self.baseobject.Shape) removalshapes.append((obj.removalshape, False)) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") env = PathUtils.getEnvelope(self.baseobject.Shape, subshape=None, depthparams=self.depthparams) obj.removalshape = env.cut(self.baseobject.Shape) removalshapes = [(obj.removalshape, False)] return removalshapes
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 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 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 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 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 __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 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 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 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 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 getFields(self): PathLog.track() if self.obj: if hasattr(self.obj, "StartDepth"): self.obj.StartDepth = FreeCAD.Units.Quantity(self.form.startDepth.text()).Value if hasattr(self.obj, "FinalDepth"): self.obj.FinalDepth = FreeCAD.Units.Quantity(self.form.finalDepth.text()).Value if hasattr(self.obj, "SafeHeight"): self.obj.SafeHeight = FreeCAD.Units.Quantity(self.form.safeHeight.text()).Value if hasattr(self.obj, "ClearanceHeight"): self.obj.ClearanceHeight = FreeCAD.Units.Quantity(self.form.clearanceHeight.text()).Value if hasattr(self.obj, "StepDown"): self.obj.StepDown = FreeCAD.Units.Quantity(self.form.stepDown.text()).Value if hasattr(self.obj, "OffsetExtra"): self.obj.OffsetExtra = FreeCAD.Units.Quantity(self.form.extraOffset.text()).Value if hasattr(self.obj, "RollRadius"): self.obj.RollRadius = FreeCAD.Units.Quantity(self.form.rollRadius.text()).Value if hasattr(self.obj, "UseComp"): self.obj.UseComp = self.form.useCompensation.isChecked() if hasattr(self.obj, "UseStartPoint"): self.obj.UseStartPoint = self.form.useStartPoint.isChecked() if hasattr(self.obj, "UseEndPoint"): self.obj.UseEndPoint = self.form.useEndPoint.isChecked() if hasattr(self.obj, "Direction"): self.obj.Direction = str(self.form.direction.currentText()) if hasattr(self.obj, "ToolController"): tc = PathUtils.findToolController(self.obj, self.form.uiToolController.currentText()) self.obj.ToolController = tc self.obj.Proxy.execute(self.obj)
def setFields(self, obj): '''setFields(obj) ... fill form with values from obj''' PathLog.track() self.form.baseList.blockSignals(True) self.form.baseList.clearContents() self.form.baseList.setRowCount(0) for i, (base, subs) in enumerate(obj.Base): for sub in subs: self.form.baseList.insertRow(self.form.baseList.rowCount()) item = QtGui.QTableWidgetItem("%s.%s" % (base.Label, sub)) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) if obj.Proxy.isHoleEnabled(obj, base, sub): item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) name = "%s.%s" % (base.Name, sub) item.setData(self.DataFeatureName, name) item.setData(self.DataObject, base) item.setData(self.DataObjectSub, sub) self.form.baseList.setItem(self.form.baseList.rowCount()-1, 0, item) item = QtGui.QTableWidgetItem("{:.3f}".format(obj.Proxy.holeDiameter(obj, base, sub))) item.setData(self.DataFeatureName, name) item.setData(self.DataObject, base) item.setData(self.DataObjectSub, sub) item.setTextAlignment(QtCore.Qt.AlignHCenter) self.form.baseList.setItem(self.form.baseList.rowCount()-1, 1, item) self.form.baseList.resizeColumnToContents(0) self.form.baseList.blockSignals(False) self.itemActivated()
def setJobDefaults(cls, filePath, jobTemplate, geometryTolerance, curveAccuracy): PathLog.track("(%s='%s', %s, %s, %s)" % (cls.DefaultFilePath, filePath, jobTemplate, geometryTolerance, curveAccuracy)) pref = cls.preferences() pref.SetString(cls.DefaultFilePath, filePath) pref.SetString(cls.DefaultJobTemplate, jobTemplate) pref.SetFloat(cls.GeometryTolerance, geometryTolerance) pref.SetFloat(cls.LibAreaCurveAccuracy, curveAccuracy)
def findHoles(self, obj, shape): import DraftGeomUtils as dgu PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] tooldiameter = obj.ToolController.Proxy.getTool(obj.ToolController).Diameter PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter)) if dgu.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode())) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): PathLog.debug('face candidate: {} is drillable '.format(f)) x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) PathLog.debug("holes found: {}".format(holelist)) return holelist
def setupUi(self): PathLog.track() # Connect Signals and Slots self.form.startDepth.editingFinished.connect(self.getFields) self.form.finalDepth.editingFinished.connect(self.getFields) self.form.safeHeight.editingFinished.connect(self.getFields) self.form.retractHeight.editingFinished.connect(self.getFields) self.form.peckDepth.editingFinished.connect(self.getFields) self.form.clearanceHeight.editingFinished.connect(self.getFields) self.form.dwellTime.editingFinished.connect(self.getFields) self.form.dwellEnabled.stateChanged.connect(self.getFields) self.form.peckEnabled.stateChanged.connect(self.getFields) # buttons self.form.uiEnableSelected.clicked.connect(self.enableSelected) self.form.uiDisableSelected.clicked.connect(self.disableSelected) self.form.uiFindAllHoles.clicked.connect(self.findAll) self.form.uiAddSelected.clicked.connect(self.addSelected) self.form.baseList.itemSelectionChanged.connect(self.itemActivated) self.form.baseList.itemChanged.connect(self.checkedChanged) self.form.uiToolController.currentIndexChanged.connect(self.getFields) self.setFields()
def __init__(self, vobj): PathLog.track() vobj.Proxy = self self.vobj = vobj self.panel = None self.debugDisplay()
def updateToolType(self): PathLog.track() self.form.blockSignals(True) self.tool.ToolType = self.getType(self.form.toolType.currentIndex()) self.setupToolType(self.tool.ToolType) self.updateUI() self.form.blockSignals(False)
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 setModelData(self, widget, model, index): PathLog.track(index.row(), index.column()) editor = index.data(self.EditorRole) editor.setModelData(widget) index.model().setData(index, editor.prop.displayString(), QtCore.Qt.DisplayRole)
def generateTags(self, obj, count, width=None, height=None, angle=None, radius=None, spacing=None): # pylint: disable=unused-argument PathLog.track(count, width, height, angle, spacing) # for e in self.baseWire.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) if spacing: tagDistance = spacing else: tagDistance = self.baseWire.Length / (count if count else 4) W = width if width else self.defaultTagWidth() H = height if height else self.defaultTagHeight() A = angle if angle else self.defaultTagAngle() R = radius if radius else self.defaultTagRadius() # start assigning tags on the longest segment (shortestEdge, longestEdge) = self.shortestAndLongestPathEdge() startIndex = 0 for i in range(0, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] PathLog.debug(' %d: %.2f' % (i, edge.Length)) if PathGeom.isRoughly(edge.Length, longestEdge.Length): startIndex = i break startEdge = self.baseWire.Edges[startIndex] startCount = int(startEdge.Length / tagDistance) if (longestEdge.Length - shortestEdge.Length) > shortestEdge.Length: startCount = int(startEdge.Length / tagDistance) + 1 lastTagLength = (startEdge.Length + (startCount - 1) * tagDistance) / 2 currentLength = startEdge.Length minLength = min(2. * W, longestEdge.Length) PathLog.debug("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.baseWire.Length, shortestEdge.Length, shortestEdge.Length/self.baseWire.Length, longestEdge.Length, longestEdge.Length / self.baseWire.Length, minLength)) PathLog.debug(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) PathLog.debug(" -> lastTagLength=%.2f)" % lastTagLength) PathLog.debug(" -> currentLength=%.2f)" % currentLength) edgeDict = {startIndex: startCount} for i in range(startIndex + 1, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) for i in range(0, startIndex): edge = self.baseWire.Edges[i] (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) tags = [] for (i, count) in PathUtil.keyValueIter(edgeDict): edge = self.baseWire.Edges[i] PathLog.debug(" %d: %d" % (i, count)) # debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) # debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) if 0 != count: distance = (edge.LastParameter - edge.FirstParameter) / count for j in range(0, count): tag = edge.Curve.value((j+0.5) * distance) tags.append(Tag(j, tag.x, tag.y, W, H, A, R, True)) return tags
def cleanupEdges(self, edges): # want to remove all edges from the wire itself, and all internal struts PathLog.track("+cleanupEdges") PathLog.debug(" edges:") if not edges: return edges for e in edges: debugEdge(e, ' ') PathLog.debug(":") self.edgesCleanup = [copy.copy(edges)] # remove any edge that has a point inside the tag solid # and collect all edges that are connected to the entry and/or exit self.entryEdges = [] self.exitEdges = [] self.edgePoints = [] for e in copy.copy(edges): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) self.edgePoints.append(p1) self.edgePoints.append(p2) if self.tag.solid.isInside(p1, PathGeom.Tolerance, False) or self.tag.solid.isInside(p2, PathGeom.Tolerance, False): edges.remove(e) debugEdge(e, '......... X0', False) else: if PathGeom.pointsCoincide(p1, self.entry) or PathGeom.pointsCoincide(p2, self.entry): self.entryEdges.append(e) if PathGeom.pointsCoincide(p1, self.exit) or PathGeom.pointsCoincide(p2, self.exit): self.exitEdges.append(e) self.edgesCleanup.append(copy.copy(edges)) # if there are no edges connected to entry/exit, it means the plunge in/out is vertical # we need to add in the missing segment and collect the new entry/exit edges. if not self.entryEdges: PathLog.debug("fill entryEdges ...") self.realEntry = sorted(self.edgePoints, key=lambda p: (p - self.entry).Length)[0] self.entryEdges = list([e for e in edges if PathGeom.edgeConnectsTo(e, self.realEntry)]) edges.append(Part.Edge(Part.LineSegment(self.entry, self.realEntry))) else: self.realEntry = None if not self.exitEdges: PathLog.debug("fill exitEdges ...") self.realExit = sorted(self.edgePoints, key=lambda p: (p - self.exit).Length)[0] self.exitEdges = list([e for e in edges if PathGeom.edgeConnectsTo(e, self.realExit)]) edges.append(Part.Edge(Part.LineSegment(self.realExit, self.exit))) else: self.realExit = None self.edgesCleanup.append(copy.copy(edges)) # if there are 2 edges attached to entry/exit, throw away the one that is "lower" if len(self.entryEdges) > 1: debugEdge(self.entryEdges[0], ' entry[0]', False) debugEdge(self.entryEdges[1], ' entry[1]', False) if self.entryEdges[0].BoundBox.ZMax < self.entryEdges[1].BoundBox.ZMax: edges.remove(self.entryEdges[0]) debugEdge(e, '......... X1', False) else: edges.remove(self.entryEdges[1]) debugEdge(e, '......... X2', False) if len(self.exitEdges) > 1: debugEdge(self.exitEdges[0], ' exit[0]', False) debugEdge(self.exitEdges[1], ' exit[1]', False) if self.exitEdges[0].BoundBox.ZMax < self.exitEdges[1].BoundBox.ZMax: if self.exitEdges[0] in edges: edges.remove(self.exitEdges[0]) debugEdge(e, '......... X3', False) else: if self.exitEdges[1] in edges: edges.remove(self.exitEdges[1]) debugEdge(e, '......... X4', False) self.edgesCleanup.append(copy.copy(edges)) return edges
def attach(self, vobj): PathLog.track() self.vobj = vobj self.obj = vobj.Object
def __init__(self, vobj, name): PathLog.track(name) vobj.Proxy = self self.icon = name mode = 2
def updateModel(self, recomp=True): PathLog.track() self.getFields() self.updateUI() if recomp: FreeCAD.ActiveDocument.recompute()
def accept(self): if any([op.accept() for op in self.ops]): PathLog.track()
def templateFilesIn(self, path): '''templateFilesIn(path) ... answer all file in the given directory which fit the job template naming convention. PathJob template files are name job_*.xml''' PathLog.track(path) return glob.glob(path + '/job_*.xml')
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() # Instantiate class variables for operation reference self.endVector = None # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones( 'Delete') # Clear temporary group and recreate for temp job clones if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (self.clrOfset, self.safOfst) = opHeights[1] # pylint: disable=attribute-defined-outside-init # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst #if self.initWithRotation is False: # if obj.FinalDepth.Value == obj.OpFinalDepth.Value: # obj.FinalDepth.Value = finDep # if obj.StartDepth.Value == obj.OpStartDepth.Value: # obj.StartDepth.Value = strDep # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() else: strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for rotational operation finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 self.depthparams = PathUtils.depth_params( # pylint: disable=attribute-defined-outside-init clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=obj.FinalDepth.Value, user_depths=None) # Set start point if PathOp.FeatureStartPoint & self.opFeatures( obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations beside PathPocketShape shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub, angle, axis, strtDep, finDep tup = fc, iH, 'otherOp', 0.0, 'S', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: jobs = [{ 'x': s[0].BoundBox.XMax, 'y': s[0].BoundBox.YMax, 'shape': s } for s in shapes] jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] sims = [] numShapes = len(shapes) for ns in range(0, numShapes): (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if ns < numShapes - 1: nextAxis = shapes[ns + 1][4] else: nextAxis = 'L' finish_step = obj.FinishDepth.Value if hasattr( obj, "FinishDepth") else 0.0 self.depthparams = PathUtils.depth_params( # pylint: disable=attribute-defined-outside-init clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep, # obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, # obj.FinalDepth.Value, user_depths=None) try: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError( "Something unexpected happened. Check project and tool config." ) else: ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': axisOfRot = 'A' elif axis == 'Y': axisOfRot = 'B' # Reverse angle temporarily to match model. Error in FreeCAD render of B axis rotations if obj.B_AxisErrorOverride is True: angle = -1 * angle elif axis == 'Z': axisOfRot = 'C' else: axisOfRot = 'A' # Rotate Model to correct angle ppCmds.insert( 0, Path.Command('G1', { axisOfRot: angle, 'F': self.axialFeed })) ppCmds.insert(0, Path.Command('N100', {})) # Raise cutter to safe depth and return index to starting position ppCmds.append(Path.Command('N200', {})) ppCmds.append( Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) if axis != nextAxis: ppCmds.append( Path.Command('G0', { axisOfRot: 0.0, 'F': self.axialRapid })) # Eif # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if self.areaOpRetractTool(obj): self.endVector = None # pylint: disable=attribute-defined-outside-init # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: self.commandlist.append( Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) self.commandlist.append( Path.Command('G0', { 'A': 0.0, 'F': self.axialRapid })) self.commandlist.append( Path.Command('G0', { 'B': 0.0, 'F': self.axialRapid })) self.useTempJobClones( 'Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user for ton in self.tempObjectNames: # remove temporary objects by name FreeCAD.ActiveDocument.removeObject(ton) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
def setEditorData(self, widget, index): PathLog.track(index.row(), index.column()) index.data(self.EditorRole).setEditorData(widget)
def lastPathToolLibrary(): PathLog.track() return preferences().GetString(LastPathToolLibrary, pathDefaultToolsPath('Library'))
def faceRotationAnalysis(self, obj, norm, surf): '''faceRotationAnalysis(obj, norm, surf) Determine X and Y independent rotation necessary to make normalAt = Z=1 (0,0,1) ''' PathLog.track() praInfo = "faceRotationAnalysis()" rtn = True orientation = 'X' angle = 500.0 precision = 6 for i in range(0, 13): if PathGeom.Tolerance * (i * 10) == 1.0: precision = i break def roundRoughValues(precision, val): # Convert VALxe-15 numbers to zero if PathGeom.isRoughly(0.0, val) is True: return 0.0 # Convert VAL.99999999 to next integer elif math.fabs(val % 1) > 1.0 - PathGeom.Tolerance: return round(val) else: return round(val, precision) nX = roundRoughValues(precision, norm.x) nY = roundRoughValues(precision, norm.y) nZ = roundRoughValues(precision, norm.z) praInfo += "\n -normalAt(0,0): " + str(nX) + ", " + str( nY) + ", " + str(nZ) saX = roundRoughValues(precision, surf.x) saY = roundRoughValues(precision, surf.y) saZ = roundRoughValues(precision, surf.z) praInfo += "\n -Surface.Axis: " + str(saX) + ", " + str( saY) + ", " + str(saZ) # Determine rotation needed and current orientation if saX == 0.0: if saY == 0.0: orientation = "Z" if saZ == 1.0: angle = 0.0 elif saZ == -1.0: angle = -180.0 else: praInfo += "_else_X" + str(saZ) elif saY == 1.0: orientation = "Y" angle = 90.0 elif saY == -1.0: orientation = "Y" angle = -90.0 else: if saZ != 0.0: angle = math.degrees(math.atan(saY / saZ)) orientation = "Y" elif saY == 0.0: if saZ == 0.0: orientation = "X" if saX == 1.0: angle = -90.0 elif saX == -1.0: angle = 90.0 else: praInfo += "_else_X" + str(saX) else: orientation = "X" ratio = saX / saZ angle = math.degrees(math.atan(ratio)) if ratio < 0.0: praInfo += " NEG-ratio" # angle -= 90 else: praInfo += " POS-ratio" angle = -1 * angle if saX < 0.0: angle = angle + 180.0 elif saZ == 0.0: # if saY != 0.0: angle = math.degrees(math.atan(saX / saY)) orientation = "Y" if saX + nX == 0.0: angle = -1 * angle if saY + nY == 0.0: angle = -1 * angle if saZ + nZ == 0.0: angle = -1 * angle if saY == -1.0 or saY == 1.0: if nX != 0.0: angle = -1 * angle # Enforce enabled rotation in settings praInfo += "\n -Initial orientation: {}".format(orientation) if orientation == 'Y': axis = 'X' if obj.EnableRotation == 'B(y)': # Required axis disabled if angle == 180.0 or angle == -180.0: axis = 'Y' else: rtn = False elif orientation == 'X': axis = 'Y' if obj.EnableRotation == 'A(x)': # Required axis disabled if angle == 180.0 or angle == -180.0: axis = 'X' else: rtn = False elif orientation == 'Z': axis = 'X' if math.fabs(angle) == 0.0: angle = 0.0 rtn = False if angle == 500.0: angle = 0.0 rtn = False if rtn is False: if orientation == 'Z' and angle == 0.0 and obj.ReverseDirection is True: if obj.EnableRotation == 'B(y)': axis = 'Y' rtn = True if rtn is True: self.rotateFlag = True # pylint: disable=attribute-defined-outside-init if obj.ReverseDirection is True: if angle < 180.0: angle = angle + 180.0 else: angle = angle - 180.0 angle = round(angle, precision) praInfo += "\n -Rotation analysis: angle: " + str( angle) + ", axis: " + str(axis) if rtn is True: praInfo += "\n - ... rotation triggered" else: praInfo += "\n - ... NO rotation triggered" PathLog.debug("\n" + str(praInfo)) return (rtn, angle, axis, praInfo)
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 _buildPathArea(self, obj, baseobject, isHole, start, getsim): '''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.''' # pylint: disable=unused-argument PathLog.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return heights = [i for i in self.depthparams] PathLog.debug('depths: {}'.format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) PathLog.debug("Area with params: {}".format(area.getParams())) sections = area.makeSections(mode=0, project=self.areaOpUseProjection(obj), heights=heights) PathLog.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) pathParams = self.areaOpPathParams(obj, isHole) # pylint: disable=assignment-from-no-return pathParams['shapes'] = shapelist pathParams['feedrate'] = self.horizFeed pathParams['feedrate_v'] = self.vertFeed pathParams['verbose'] = True pathParams['resume_height'] = obj.SafeHeight.Value pathParams['retraction'] = obj.ClearanceHeight.Value pathParams['return_end'] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams['preamble'] = False if not self.areaOpRetractTool(obj): pathParams['threshold'] = 2.001 * self.radius if self.endVector is not None: pathParams['start'] = self.endVector elif PathOp.FeatureStartPoint & self.opFeatures( obj) and obj.UseStartPoint: pathParams['start'] = obj.StartPoint obj.PathParams = str({ key: value for key, value in pathParams.items() if key != 'shapes' }) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) self.endVector = end_vector # pylint: disable=attribute-defined-outside-init simobj = None if getsim: areaParams['Thicken'] = True areaParams['ToolRadius'] = self.radius - self.radius * .005 area.setParams(**areaParams) sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() # Instantiate class variables for operation reference self.endVector = None # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # Clear temporary group and recreate for temp job clones self.rotStartDepth = None # pylint: disable=attribute-defined-outside-init if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (self.clrOfset, self.safOfst) = opHeights[1] # pylint: disable=attribute-defined-outside-init # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep self.rotStartDepth = strDep obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() else: strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for rotational operation self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations beside PathPocketShape shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub, angle, axis, strtDep, finDep tup = fc, iH, 'otherOp', 0.0, 'S', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: jobs = list() for s in shapes: if s[2] == 'OpenEdge': shp = Part.makeCompound(s[0]) else: shp = s[0] jobs.append({ 'x': shp.BoundBox.XMax, 'y': shp.BoundBox.YMax, 'shape': s }) jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] sims = [] numShapes = len(shapes) for ns in range(0, numShapes): profileEdgesIsOpen = False (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if sub == 'OpenEdge': profileEdgesIsOpen = True if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: osp = obj.StartPoint self.commandlist.append(Path.Command('G0', {'X': osp.x, 'Y': osp.y, 'F': self.horizRapid})) if ns < numShapes - 1: nextAxis = shapes[ns + 1][4] else: nextAxis = 'L' self.depthparams = self._customDepthParams(obj, strDep, finDep) try: if profileEdgesIsOpen: (pp, sim) = self._buildProfileOpenEdges(obj, shape, isHole, start, getsim) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") else: if profileEdgesIsOpen: ppCmds = pp else: ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': axisOfRot = 'A' elif axis == 'Y': axisOfRot = 'B' elif axis == 'Z': axisOfRot = 'C' else: axisOfRot = 'A' # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid})) # Raise cutter to safe height ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Return index to starting position if axis of rotation changes. if numShapes > 1: if ns != numShapes - 1: if axis != nextAxis: ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if self.areaOpRetractTool(obj) and self.endVector is not None: self.endVector[2] = obj.ClearanceHeight.Value self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: resetAxis = False lastJobOp = None nextJobOp = None opIdx = 0 JOB = PathUtils.findParentJob(obj) jobOps = JOB.Operations.Group numJobOps = len(jobOps) for joi in range(0, numJobOps): jo = jobOps[joi] if jo.Name == obj.Name: opIdx = joi lastOpIdx = opIdx - 1 nextOpIdx = opIdx + 1 if lastOpIdx > -1: lastJobOp = jobOps[lastOpIdx] if nextOpIdx < numJobOps: nextJobOp = jobOps[nextOpIdx] if lastJobOp is not None: if hasattr(lastJobOp, 'EnableRotation'): PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation)) if lastJobOp.EnableRotation != obj.EnableRotation: resetAxis = True if ns == numShapes - 1: # If last shape, check next op EnableRotation setting if nextJobOp is not None: if hasattr(nextJobOp, 'EnableRotation'): PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation)) if nextJobOp.EnableRotation != obj.EnableRotation: resetAxis = True # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # reset rotational axes if necessary if resetAxis is True: self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user for ton in self.tempObjectNames: # remove temporary objects by name FreeCAD.ActiveDocument.removeObject(ton) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
def setLastFileToolLibrary(path): PathLog.track(path) if os.path.isfile(path): # keep the path and file in sync preferences().SetString(LastPathToolLibrary, os.path.split(path)[0]) return preferences().SetString(LastFileToolLibrary, path)
def createPath(self, obj, pathData, tags): PathLog.track() commands = [] lastEdge = 0 lastTag = 0 # sameTag = None t = 0 # inters = None edge = None segm = 50 if hasattr(obj, 'SegmentationFactor'): segm = obj.SegmentationFactor if segm <= 0: segm = 50 obj.SegmentationFactor = 50 self.mappers = [] mapper = None while edge or lastEdge < len(pathData.edges): PathLog.debug("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = pathData.edges[lastEdge] debugEdge( edge, "======= new edge: %d/%d" % (lastEdge, len(pathData.edges))) lastEdge += 1 # sameTag = None if mapper: mapper.add(edge) if mapper.mappingComplete(): commands.extend(mapper.commands) edge = mapper.tail mapper = None else: edge = None if edge: tIndex = (t + lastTag) % len(tags) t += 1 i = tags[tIndex].intersects(edge, edge.FirstParameter) if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i, segm, pathData.maxZ) self.mappers.append(mapper) edge = mapper.tail if not mapper and t >= len(tags): # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') if pathData.rapid.isRapid(edge): v = edge.Vertexes[1] if not commands and PathGeom.isRoughly( 0, v.X) and PathGeom.isRoughly( 0, v.Y) and not PathGeom.isRoughly(0, v.Z): # The very first move is just to move to ClearanceHeight commands.append(Path.Command('G0', {'Z': v.Z})) else: commands.append( Path.Command('G0', { 'X': v.X, 'Y': v.Y, 'Z': v.Z })) else: commands.extend(PathGeom.cmdsForEdge(edge, segm=segm)) edge = None t = 0 lastCmd = Path.Command('G0', {'X': 0.0, 'Y': 0.0, 'Z': 0.0}) outCommands = [] tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value horizRapid = tc.HorizRapid.Value vertRapid = tc.VertRapid.Value for cmd in commands: params = cmd.Parameters zVal = params.get('Z', None) zVal2 = lastCmd.Parameters.get('Z', None) zVal = zVal and round(zVal, 8) zVal2 = zVal2 and round(zVal2, 8) if cmd.Name in ['G1', 'G2', 'G3', 'G01', 'G02', 'G03']: if False and zVal is not None and zVal2 != zVal: params['F'] = vertFeed else: params['F'] = horizFeed lastCmd = cmd elif cmd.Name in ['G0', 'G00']: if zVal is not None and zVal2 != zVal: params['F'] = vertRapid else: params['F'] = horizRapid lastCmd = cmd outCommands.append(Path.Command(cmd.Name, params)) return Path.Path(outCommands)
def __init__(self, obj, base): "Make stock" obj.addProperty( "App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP( "PathStock", "The base object this stock is derived from")) obj.addProperty( "App::PropertyDistance", "ExtXneg", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in negative X direction")) obj.addProperty( "App::PropertyDistance", "ExtXpos", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in positive X direction")) obj.addProperty( "App::PropertyDistance", "ExtYneg", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in negative Y direction")) obj.addProperty( "App::PropertyDistance", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in positive Y direction")) obj.addProperty( "App::PropertyDistance", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in negative Z direction")) obj.addProperty( "App::PropertyDistance", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP( "PathStock", "Extra allowance from part bound box in positive Z direction")) obj.addProperty( "App::PropertyLink", "Material", "Component", QtCore.QT_TRANSLATE_NOOP("App::Property", "A material for this object")) obj.Base = base obj.ExtXneg = 1.0 obj.ExtXpos = 1.0 obj.ExtYneg = 1.0 obj.ExtYpos = 1.0 obj.ExtZneg = 1.0 obj.ExtZpos = 1.0 # placement is only tracked on creation bb = shapeBoundBox(base.Group) if base else None if bb: obj.Placement = FreeCAD.Placement( FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation()) else: PathLog.track(obj.Label, base.Label) obj.Proxy = self # debugging aids self.origin = None self.length = None self.width = None self.height = None
def Activated(self): PathLog.track() self.Create()
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' 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 = [] self.profileshape = [] if obj.Base: # The user has selected subobjects from the base. Process each. for base in obj.Base: holes = [] faces = [] for sub in base[1]: shape = getattr(base[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face for wire in shape.Wires[1:]: holes.append((base[0].Shape, wire)) else: FreeCAD.Console.PrintWarning( "found a base object which is not a face. Can't continue." ) return for shape, wire in holes: f = Part.makeFace(wire, 'Part::FaceMakerSimple') drillable = PathUtils.isDrillable(shape, wire) if (drillable and obj.processCircles) or (not drillable and obj.processHoles): env = PathUtils.getEnvelope( shape, subshape=f, depthparams=self.depthparams) PathLog.track() shapes.append((env, True)) if len(faces) > 0: profileshape = Part.makeCompound(faces) self.profileshape.append(profileshape) if obj.processPerimeter: env = PathUtils.getEnvelope(base[0].Shape, subshape=profileshape, depthparams=self.depthparams) PathLog.track() shapes.append((env, False)) else: # Try to build targets from the job base if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: for shape in self.model[0].Proxy.getHoles( self.model[0], transform=True): for wire in shape.Wires: drillable = PathUtils.isDrillable( self.model[0].Proxy, wire) if (drillable and obj.processCircles) or ( not drillable and obj.processHoles): f = Part.makeFace(wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope( self.model[0].Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, True)) if obj.processPerimeter: for shape in self.model[0].Proxy.getOutlines( self.model[0], transform=True): for wire in shape.Wires: f = Part.makeFace(wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope( self.model[0].Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, False)) self.removalshapes = shapes PathLog.debug("%d shapes" % len(shapes)) return shapes
def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" PathLog.track() self.removalshapes = [] # self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.removalshapes = [] avoidFeatures = list() # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) for e in extensions: if e.avoid: avoidFeatures.append(e.feature) if obj.Base: PathLog.debug("base items exist. Processing...") self.horiz = [] self.vert = [] for (base, subList) in obj.Base: for sub in subList: if "Face" in sub: if sub not in avoidFeatures and not self.clasifySub(base, sub): PathLog.error( translate( "PathPocket", "Pocket does not support shape %s.%s" ) % (base.Label, sub) ) # Convert horizontal faces to use outline only if requested if obj.UseOutline and self.horiz: horiz = [Part.Face(f.Wire1) for f in self.horiz] self.horiz = horiz # Check if selected vertical faces form a loop if len(self.vert) > 0: self.vertical = PathGeom.combineConnectedShapes(self.vert) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( "PathPocket", "Vertical faces do not form a loop - ignoring", ) ) else: self.horiz.append(face) # Add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in extensions: if not ext.avoid: wire = ext.getWire() if wire: faces = ext.getExtensionFaces(wire) for f in faces: self.horiz.append(f) self.exts.append(f) # check all faces and see if they are touching/overlapping and combine and simplify self.horizontal = PathGeom.combineHorizontalFaces(self.horiz) # Move all faces to final depth less buffer before extrusion # Small negative buffer is applied to compensate for internal significant digits/rounding issue if self.job.GeometryTolerance.Value == 0.0: buffer = 0.000001 else: buffer = self.job.GeometryTolerance.Value / 10.0 for h in self.horizontal: h.translate( FreeCAD.Vector( 0.0, 0.0, obj.FinalDepth.Value - h.BoundBox.ZMin - buffer ) ) # extrude all faces up to StartDepth plus buffer and those are the removal shapes extent = FreeCAD.Vector( 0, 0, obj.StartDepth.Value - obj.FinalDepth.Value + buffer ) self.removalshapes = [ (face.removeSplitter().extrude(extent), False) for face in self.horizontal ] else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1)) ) for base in self.model ] stockBB = self.stock.Shape.BoundBox self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False)) # Tessellate all working faces # for (shape, hole) in self.removalshapes: # shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = Part.makeCompound([tup[0] for tup in self.removalshapes]) return self.removalshapes
def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest=None): PathLog.track(count, radii, majorDia, minorDia, toolDia, toolCrest) minor, major = radii(majorDia, minorDia, toolDia, toolCrest) dr = float(major - minor) / count return [major - dr * (count - (i + 1)) for i in range(count)]
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' PathLog.track() PathLog.debug("----- areaOpShapes() in PathProfileFaces.py") 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 = [] self.profileshape = [] finalDepths = [] startDepths = [] faceDepths = [] baseSubsTuples = [] subCount = 0 allTuples = [] if obj.Base: # The user has selected subobjects from the base. Process each. if obj.EnableRotation != 'Off': for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] for sub in subsList: subCount += 1 shape = getattr(base.Shape, sub) if isinstance(shape, Part.Face): rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis( obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) if rtn is True: PathLog.error( translate( "Path", "Face appears misaligned after initial rotation." )) if obj.AttemptInverseAngle is True and obj.InverseAngle is False: (clnBase, clnStock, angle) = self.applyInverseAngle( obj, clnBase, clnStock, axis, angle) else: msg = translate( "Path", "Consider toggling the 'InverseAngle' property and recomputing." ) PathLog.error(msg) # title = translate("Path", 'Rotation Warning') # self.guiMessage(title, msg, False) else: PathLog.debug( "Face appears to be oriented correctly." ) tup = clnBase, sub, tag, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: PathLog.debug( str(sub) + ": No rotation used") axis = 'X' angle = 0.0 tag = base.Name + '_' + axis + str( angle).replace('.', '_') stock = PathUtils.findParentJob(obj).Stock tup = base, sub, tag, angle, axis, stock # Eif allTuples.append(tup) # Eif # Efor # Efor if subCount > 1: msg = translate('Path', "Multiple faces in Base Geometry.") + " " msg += translate( 'Path', "Depth settings will be applied to all faces.") PathLog.warning(msg) # title = translate("Path", "Depth Warning") # self.guiMessage(title, msg) (Tags, Grps) = self.sortTuplesByIndex( allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor else: PathLog.info( translate("Path", "EnableRotation property is 'Off'.")) stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'X', stock)) # for base in obj.Base: finish_step = obj.FinishDepth.Value if hasattr( obj, "FinishDepth") else 0.0 for (base, subsList, angle, axis, stock) in baseSubsTuples: holes = [] faces = [] faceDepths = [] startDepths = [] for sub in subsList: shape = getattr(base.Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face for wire in shape.Wires[1:]: holes.append((base.Shape, wire)) # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: ignoreSub = base.Name + '.' + sub msg = translate( 'Path', "Found a selected object which is not a face. Ignoring: {}" .format(ignoreSub)) PathLog.error(msg) FreeCAD.Console.PrintWarning(msg) # Raise FinalDepth to lowest face in list on Inside profile ops finDep = obj.FinalDepth.Value if obj.Side == 'Inside': finDep = min(faceDepths) finalDepths.append(finDep) strDep = obj.StartDepth.Value if strDep > stock.Shape.BoundBox.ZMax: strDep = stock.Shape.BoundBox.ZMax startDepths.append(strDep) # Recalculate depthparams self.depthparams = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep, # obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=finDep, # obj.FinalDepth.Value, user_depths=None) for shape, wire in holes: f = Part.makeFace(wire, 'Part::FaceMakerSimple') drillable = PathUtils.isDrillable(shape, wire) if (drillable and obj.processCircles) or (not drillable and obj.processHoles): PathLog.track() env = PathUtils.getEnvelope( shape, subshape=f, depthparams=self.depthparams) # shapes.append((env, True)) tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) if len(faces) > 0: profileshape = Part.makeCompound(faces) self.profileshape.append(profileshape) if obj.processPerimeter: PathLog.track() try: env = PathUtils.getEnvelope( base.Shape, subshape=profileshape, depthparams=self.depthparams) except Exception: # PathUtils.getEnvelope() failed to return an object. PathLog.error( translate('Path', 'Unable to create path for face(s).')) else: # shapes.append((env, False)) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) else: for shape in faces: finalDep = finDep # Recalculate depthparams if obj.Side == 'Inside': if finalDep < shape.BoundBox.ZMin: custDepthparams = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=strDep, # obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=shape.BoundBox. ZMin, # obj.FinalDepth.Value, user_depths=None) env = PathUtils.getEnvelope( base.Shape, subshape=shape, depthparams=custDepthparams) finalDep = shape.BoundBox.ZMin else: env = PathUtils.getEnvelope( base.Shape, subshape=shape, depthparams=self.depthparams) else: env = PathUtils.getEnvelope( base.Shape, subshape=shape, depthparams=self.depthparams) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep shapes.append(tup) # Eif # adjust Start/Final Depths as needed # Raise existing Final Depth to level of lowest profile face if obj.Side == 'Inside': finalDepth = min(finalDepths) if obj.FinalDepth.Value < finalDepth: obj.FinalDepth.Value = finalDepth # Lower high Start Depth to top of Stock startDepth = max(startDepths) if obj.StartDepth.Value > startDepth: obj.StartDepth.Value = startDepth else: # Try to build targets from the job base if 1 == len(self.model): if hasattr(self.model[0], "Proxy"): PathLog.info("hasattr() Proxy") if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: for shape in self.model[0].Proxy.getHoles( self.model[0], transform=True): for wire in shape.Wires: drillable = PathUtils.isDrillable( self.model[0].Proxy, wire) if (drillable and obj.processCircles) or ( not drillable and obj.processHoles): f = Part.makeFace( wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope( self.model[0].Shape, subshape=f, depthparams=self.depthparams) # shapes.append((env, True)) tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) if obj.processPerimeter: for shape in self.model[0].Proxy.getOutlines( self.model[0], transform=True): for wire in shape.Wires: f = Part.makeFace(wire, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope( self.model[0].Shape, subshape=f, depthparams=self.depthparams) # shapes.append((env, False)) tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value shapes.append(tup) self.removalshapes = shapes PathLog.debug("%d shapes" % len(shapes)) return shapes
def getTool(self, obj): '''returns the tool associated with this tool controller''' PathLog.track() toolitem = obj.Tooltable.Tools.popitem() return toolitem[1]
def execute(self, obj): '''execute(obj) ... base implementation - do not overwrite! Verifies that the operation is assigned to a job and that the job also has a valid Base. It also sets the following instance variables that can and should be safely be used by implementation of opExecute(): self.baseobject ... Base object of the Job itself self.stock ... Stock object fo the Job itself self.vertFeed ... vertical feed rate of assigned tool self.vertRapid ... vertical rapid rate of assigned tool self.horizFeed ... horizontal feed rate of assigned tool self.horizRapid ... norizontal rapid rate of assigned tool self.tool ... the actual tool being used self.radius ... the main radius of the tool being used self.commandlist ... a list for collecting all commands produced by the operation Once everything is validated and above variables are set the implementation calls opExecute(obj) - which is expected to add the generated commands to self.commandlist Finally the base implementation adds a rapid move to clearance height and assigns the receiver's Path property from the command list. ''' PathLog.track() if not obj.Active: path = Path.Path("(inactive operation)") obj.Path = path if obj.ViewObject: obj.ViewObject.Visibility = False return if not self._setBaseAndStock(obj): return if FeatureTool & self.opFeatures(obj): tc = obj.ToolController if tc is None or tc.ToolNumber == 0: FreeCAD.Console.PrintError( "No Tool Controller is selected. We need a tool to build a Path." ) return else: self.vertFeed = tc.VertFeed.Value self.horizFeed = tc.HorizFeed.Value self.vertRapid = tc.VertRapid.Value self.horizRapid = tc.HorizRapid.Value tool = tc.Proxy.getTool(tc) 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 self.radius = tool.Diameter / 2 self.tool = tool obj.OpToolDiameter = tool.Diameter self.updateDepths(obj) # now that all op values are set make sure the user properties get updated accordingly, # in case they still have an expression referencing any op values obj.recompute() self.commandlist = [] self.commandlist.append(Path.Command("(%s)" % obj.Label)) if obj.Comment: self.commandlist.append(Path.Command("(%s)" % obj.Comment)) result = self.opExecute(obj) if FeatureHeights & self.opFeatures(obj): # Let's finish by rapid to clearance...just for safety self.commandlist.append( Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) path = Path.Path(self.commandlist) obj.Path = path return result
def buildpathocc(self, obj, wires, zValues, rel=False): '''buildpathocc(obj, wires, zValues, rel=False) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) for wire in wires: offset = wire # reorder the wire if hasattr(obj, 'StartVertex'): offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) edges = copy.copy(offset.Edges) last = None for z in zValues: if last: if rel: self.commandlist.append( Path.Command( 'G1', { 'X': last.x, 'Y': last.y, 'Z': last.z - z, 'F': self.vertFeed })) else: self.commandlist.append( Path.Command( 'G1', { 'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed })) for edge in edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point if len(offset.Edges) > 1: ve = edge.Vertexes[-1] e2 = offset.Edges[1] if not PathGeom.pointsCoincide( ve.Point, e2.Vertexes[0].Point ) and not PathGeom.pointsCoincide( ve.Point, e2.Vertexes[-1].Point): PathLog.debug("flip first edge") last = edge.Vertexes[-1].Point else: PathLog.debug("original first edge") else: PathLog.debug("not enough edges to flip") self.commandlist.append( Path.Command( 'G0', { 'X': last.x, 'Y': last.y, 'Z': obj.ClearanceHeight.Value, 'F': self.horizRapid })) self.commandlist.append( Path.Command( 'G0', { 'X': last.x, 'Y': last.y, 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) if rel: self.commandlist.append( Path.Command( 'G1', { 'X': last.x, 'Y': last.y, 'Z': last.z - z, 'F': self.vertFeed })) else: self.commandlist.append( Path.Command( 'G1', { 'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed })) if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): for cmd in PathGeom.cmdsForEdge(edge): self.appendCommand(cmd, z, rel) last = edge.Vertexes[-1].Point else: for cmd in PathGeom.cmdsForEdge(edge, True): self.appendCommand(cmd, z, rel) last = edge.Vertexes[0].Point self.commandlist.append( Path.Command('G0', { 'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid }))
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("----- areaOpShapes() in PathPocketShape.py") baseSubsTuples = [] subCount = 0 allTuples = [] def planarFaceFromExtrusionEdges(face, trans): useFace = 'useFaceName' minArea = 0.0 fCnt = 0 clsd = [] planar = False # Identify closed edges for edg in face.Edges: if edg.isClosed(): PathLog.debug(' -e.isClosed()') clsd.append(edg) planar = True # Attempt to create planar faces and select that with smallest area for use as pocket base if planar is True: planar = False for edg in clsd: fCnt += 1 fName = sub + '_face_' + str(fCnt) # Create planar face from edge mFF = Part.Face(Part.Wire(Part.__sortEdges__([edg]))) if mFF.isNull(): PathLog.debug('Face(Part.Wire()) failed') else: if trans is True: mFF.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin)) if FreeCAD.ActiveDocument.getObject(fName): FreeCAD.ActiveDocument.removeObject(fName) tmpFace = FreeCAD.ActiveDocument.addObject('Part::Feature', fName).Shape = mFF tmpFace = FreeCAD.ActiveDocument.getObject(fName) tmpFace.purgeTouched() if minArea == 0.0: minArea = tmpFace.Shape.Face1.Area useFace = fName planar = True elif tmpFace.Shape.Face1.Area < minArea: minArea = tmpFace.Shape.Face1.Area FreeCAD.ActiveDocument.removeObject(useFace) useFace = fName else: FreeCAD.ActiveDocument.removeObject(fName) if useFace != 'useFaceName': self.useTempJobClones(useFace) return (planar, useFace) def clasifySub(self, bs, sub): face = bs.Shape.getElement(sub) if type(face.Surface) == Part.Plane: PathLog.debug('type() == Part.Plane') if PathGeom.isVertical(face.Surface.Axis): PathLog.debug(' -isVertical()') # it's a flat horizontal face self.horiz.append(face) return True elif PathGeom.isHorizontal(face.Surface.Axis): PathLog.debug(' -isHorizontal()') self.vert.append(face) return True else: return False elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis): PathLog.debug('type() == Part.Cylinder') # vertical cylinder wall if any(e.isClosed() for e in face.Edges): PathLog.debug(' -e.isClosed()') # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) disk.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin)) self.horiz.append(disk) return True else: PathLog.debug(' -none isClosed()') # partial cylinder wall self.vert.append(face) return True elif type(face.Surface) == Part.SurfaceOfExtrusion: # extrusion wall PathLog.debug('type() == Part.SurfaceOfExtrusion') # Attempt to extract planar face from surface of extrusion (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=True) # Save face object to self.horiz for processing or display error if planar is True: uFace = FreeCAD.ActiveDocument.getObject(useFace) self.horiz.append(uFace.Shape.Faces[0]) msg = translate('Path', "<b>Verify depth of pocket for '{}'.</b>".format(sub)) msg += translate('Path', "\n<br>Pocket is based on extruded surface.") msg += translate('Path', "\n<br>Bottom of pocket might be non-planar and/or not normal to spindle axis.") msg += translate('Path', "\n<br>\n<br><i>3D pocket bottom is NOT available in this operation</i>.") PathLog.warning(msg) # title = translate('Path', 'Depth Warning') # self.guiMessage(title, msg, False) else: PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub))) else: PathLog.debug(' -type(face.Surface): {}'.format(type(face.Surface))) return False if obj.Base: PathLog.debug('Processing... obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'X', stock)) else: for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] isLoop = False # First, check all subs collectively for loop of faces if len(subsList) > 2: (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList) if isLoop is True: PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.") rtn = False subCount += 1 (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("angle: {}; axis: {}".format(angle, axis)) if rtn is True: faceNums = "" for f in subsList: faceNums += '_' + f.replace('Face', '') (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNums) # pylint: disable=unused-variable # Verify faces are correctly oriented - InverseAngle might be necessary PathLog.debug("Checking if faces are oriented correctly after rotation...") for sub in subsList: face = clnBase.Shape.getElement(sub) if type(face.Surface) == Part.Plane: if not PathGeom.isHorizontal(face.Surface.Axis): rtn = False PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied.")) break if rtn is False: PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) if angle < 0.0: angle += 360.0 tup = clnBase, subsList, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: PathLog.debug("No rotation used") axis = 'X' angle = 0.0 stock = PathUtils.findParentJob(obj).Stock tup = base, subsList, angle, axis, stock # Eif allTuples.append(tup) baseSubsTuples.append(tup) # Eif if isLoop is False: PathLog.debug(translate('Path', "Processing subs individually ...")) for sub in subsList: subCount += 1 if 'Face' in sub: rtn = False face = base.Shape.getElement(sub) if type(face.Surface) == Part.SurfaceOfExtrusion: # extrusion wall PathLog.debug('analyzing type() == Part.SurfaceOfExtrusion') # Attempt to extract planar face from surface of extrusion (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=False) # Save face object to self.horiz for processing or display error if planar is True: base = FreeCAD.ActiveDocument.getObject(useFace) sub = 'Face1' PathLog.debug(' -successful face created: {}'.format(useFace)) else: PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub))) (norm, surf) = self.getFaceNormAndSurf(face) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("initial {}".format(praInfo)) if rtn is True: faceNum = sub.replace('Face', '') (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNum) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = clnBase.Shape.getElement(sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("follow-up {}".format(praInfo2)) if abs(praAngle) == 180.0: rtn = False if self.isFaceUp(clnBase, faceIA) is False: PathLog.debug('isFaceUp is False') angle -= 180.0 if rtn is True: PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) if self.isFaceUp(clnBase, faceIA) is False: PathLog.debug('isFaceUp is False') angle += 180.0 else: PathLog.debug("Face appears to be oriented correctly.") if angle < 0.0: angle += 360.0 tup = clnBase, [sub], angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: PathLog.debug(str(sub) + ": No rotation used") axis = 'X' angle = 0.0 stock = PathUtils.findParentJob(obj).Stock tup = base, [sub], angle, axis, stock # Eif allTuples.append(tup) baseSubsTuples.append(tup) else: ignoreSub = base.Name + '.' + sub PathLog.error(translate('Path', "Selected feature is not a Face. Ignoring: {}".format(ignoreSub))) for o in baseSubsTuples: self.horiz = [] # pylint: disable=attribute-defined-outside-init self.vert = [] # pylint: disable=attribute-defined-outside-init subBase = o[0] subsList = o[1] angle = o[2] axis = o[3] stock = o[4] for sub in subsList: if 'Face' in sub: if clasifySub(self, subBase, sub) is False: PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub)) if obj.EnableRotation != 'Off': PathLog.warning(translate('PathPocket', 'Face might not be within rotation accessibility limits.')) # Determine final depth as highest value of bottom boundbox of vertical face, # in case of uneven faces on bottom if len(self.vert) > 0: vFinDep = self.vert[0].BoundBox.ZMin for vFace in self.vert: if vFace.BoundBox.ZMin > vFinDep: vFinDep = vFace.BoundBox.ZMin # Determine if vertical faces for a loop: Extract planar loop wire as new horizontal face. self.vertical = PathGeom.combineConnectedShapes(self.vert) # pylint: disable=attribute-defined-outside-init self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] # pylint: disable=attribute-defined-outside-init for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): msg = translate('PathPocket', 'Vertical faces do not form a loop - ignoring') PathLog.error(msg) # title = translate("Path", "Face Selection Warning") # self.guiMessage(title, msg, True) else: face.translate(FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin)) self.horiz.append(face) msg = translate('Path', 'Verify final depth of pocket shaped by vertical faces.') PathLog.warning(msg) # title = translate('Path', 'Depth Warning') # self.guiMessage(title, msg, False) # add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in self.getExtensions(obj): wire = ext.getWire() if wire: face = Part.Face(wire) self.horiz.append(face) self.exts.append(face) # move all horizontal faces to FinalDepth # for f in self.horiz: # f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin)) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] # pylint: disable=attribute-defined-outside-init for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() # shape.tessellate(0.1) shpZMin = shape.BoundBox.ZMin PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(shape.BoundBox.ZMin)) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wFace = Part.Face(wire) if wFace.BoundBox.ZMin != shpZMin: wFace.translate(FreeCAD.Vector(0, 0, shpZMin - wFace.BoundBox.ZMin)) self.horizontal.append(wFace) PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(wFace.BoundBox.ZMin)) else: self.horizontal.append(shape) # extrude all faces up to StartDepth and those are the removal shapes sD = obj.StartDepth.Value fD = obj.FinalDepth.Value clrnc = 0.5 for face in self.horizontal: afD = fD useAngle = angle shpZMin = face.BoundBox.ZMin PathLog.debug('self.horizontal shpZMin: {}'.format(shpZMin)) if self.isFaceUp(subBase, face) is False: useAngle += 180.0 invZ = (-2 * shpZMin) - clrnc face.translate(FreeCAD.Vector(0.0, 0.0, invZ)) shpZMin = -1 * shpZMin else: face.translate(FreeCAD.Vector(0.0, 0.0, -1 * clrnc)) if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off': if shpZMin > obj.FinalDepth.Value: afD = shpZMin if sD <= afD: sD = afD + 1.0 msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ') PathLog.warning(msg + ' {} mm.'.format(sD)) else: face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin)) extent = FreeCAD.Vector(0, 0, sD - afD + clrnc) extShp = face.removeSplitter().extrude(extent) self.removalshapes.append((extShp, False, 'pathPocketShape', useAngle, axis, sD, afD)) PathLog.debug("Extent values are strDep: {}, finDep: {}, extrd: {}".format(sD, afD, extent)) # Efor face # Efor else: # process the job base object as a whole PathLog.debug(translate("Path", 'Processing model as a whole ...')) finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] # pylint: disable=attribute-defined-outside-init stockBB = self.stock.Shape.BoundBox self.removalshapes = [] # pylint: disable=attribute-defined-outside-init self.bodies = [] # pylint: disable=attribute-defined-outside-init for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep)) for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes: # pylint: disable=unused-variable shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def refresh(self): PathLog.track() self.form.blockSignals(True) self.updateTool() self.updateUI() self.form.blockSignals(False)
def createPath(self, obj, pathData, tags): PathLog.track() commands = [] lastEdge = 0 lastTag = 0 # sameTag = None t = 0 # inters = None edge = None segm = 50 if hasattr(obj, 'SegmentationFactor'): segm = obj.SegmentationFactor if segm <= 0: segm = 50 obj.SegmentationFactor = 50 self.mappers = [] mapper = None tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value horizRapid = tc.HorizRapid.Value vertRapid = tc.VertRapid.Value while edge or lastEdge < len(pathData.edges): PathLog.debug("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = pathData.edges[lastEdge] debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(pathData.edges))) lastEdge += 1 # sameTag = None if mapper: mapper.add(edge) if mapper.mappingComplete(): commands.extend(mapper.commands) edge = mapper.tail mapper = None else: edge = None if edge: tIndex = (t + lastTag) % len(tags) t += 1 i = tags[tIndex].intersects(edge, edge.FirstParameter) if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i, segm, pathData.maxZ, hSpeed = horizFeed, vSpeed = vertFeed) self.mappers.append(mapper) edge = mapper.tail if not mapper and t >= len(tags): # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') if pathData.rapid.isRapid(edge): v = edge.Vertexes[1] if not commands and PathGeom.isRoughly(0, v.X) and PathGeom.isRoughly(0, v.Y) and not PathGeom.isRoughly(0, v.Z): # The very first move is just to move to ClearanceHeight commands.append(Path.Command('G0', {'Z': v.Z, 'F': horizRapid})) else: commands.append(Path.Command('G0', {'X': v.X, 'Y': v.Y, 'Z': v.Z, 'F': vertRapid})) else: commands.extend(PathGeom.cmdsForEdge(edge, segm=segm, hSpeed = horizFeed, vSpeed = vertFeed)) edge = None t = 0 return Path.Path(commands)
def __init__(self, obj): PathLog.track() obj.addProperty( "App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP( "PathOp", "Make False, to prevent operation from generating code")) obj.addProperty( "App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "An optional comment for this Operation")) obj.addProperty( "App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label")) features = self.opFeatures(obj) if FeatureBaseGeometry & features: self.addBaseProperty(obj) if FeatureLocations & features: obj.addProperty( "App::PropertyVectorList", "Locations", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Base locations for this operation")) if FeatureTool & features: obj.addProperty( "App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP( "PathOp", "The tool controller that will be used to calculate the path" )) self.addOpValues(obj, ['tooldia']) if FeatureDepths & features: obj.addProperty( "App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP( "PathOp", "Starting Depth of Tool- first cut depth in Z")) obj.addProperty( "App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP( "PathOp", "Final Depth of Tool- lowest value in Z")) if FeatureNoFinalDepth & features: obj.setEditorMode('FinalDepth', 2) # hide self.addOpValues(obj, ['start', 'final']) if FeatureStepDown & features: obj.addProperty( "App::PropertyDistance", "StepDown", "Depth", QtCore.QT_TRANSLATE_NOOP("PathOp", "Incremental Step Down of Tool")) if FeatureFinishDepth & features: obj.addProperty( "App::PropertyDistance", "FinishDepth", "Depth", QtCore.QT_TRANSLATE_NOOP( "PathOp", "Maximum material removed on final pass.")) if FeatureHeights & features: obj.addProperty( "App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP( "PathOp", "The height needed to clear clamps and obstructions")) obj.addProperty( "App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP( "PathOp", "Rapid Safety Height between locations.")) if FeatureStartPoint & features: obj.addProperty( "App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) obj.addProperty( "App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP( "PathOp", "make True, if specifying a Start Point")) self.initOperation(obj) if self.setDefaultValues(obj): obj.Proxy = self