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 isVertical(obj): '''isVertical(obj) ... answer True if obj points into Z''' if type(obj) == FreeCAD.Vector: return isRoughly(obj.x, 0) and isRoughly(obj.y, 0) if obj.ShapeType == 'Face': if type(obj.Surface) == Part.Plane: return isHorizontal(obj.Surface.Axis) if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone: return isVertical(obj.Surface.Axis) if type(obj.Surface) == Part.Sphere: return True if type(obj.Surface) == Part.SurfaceOfExtrusion: return isVertical(obj.Surface.Direction) if type(obj.Surface) == Part.SurfaceOfRevolution: return isHorizontal(obj.Surface.Direction) if type(obj.Surface) != Part.BSplineSurface: PathLog.info(translate('PathGeom', "face %s not handled, assuming not vertical") % type(obj.Surface)) return None if obj.ShapeType == 'Edge': if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment: return isVertical(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 isHorizontal(obj.Curve.Axis) if type(obj.Curve) == Part.BezierCurve: # the current assumption is that a bezier curve is vertical if its end points are vertical return isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint) if type(obj.Curve) != Part.BSplineCurve: PathLog.info(translate('PathGeom', "edge %s not handled, assuming not vertical") % type(obj.Curve)) return None PathLog.error(translate('PathGeom', "isVertical(%s) not supported") % obj) return None
def __init__(self, job, parent=None): self.job = job self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobTemplateExport.ui") if job.PostProcessor: ppHint = "%s %s %s" % (job.PostProcessor, job.PostProcessorArgs, job.PostProcessorOutputFile) self.dialog.postProcessingHint.setText(ppHint) else: self.dialog.postProcessingGroup.setEnabled(False) self.dialog.postProcessingGroup.setChecked(False) if job.Stock and not PathJob.isResourceClone(job, 'Stock', 'Stock'): if hasattr(job.Stock, 'ExtXNeg'): seHint = translate('PathJob', "Base -/+ %.2f/%.2f %.2f/%.2f %.2f/%.2f") % (job.Stock.ExtXneg, job.Stock.ExtXpos, job.Stock.ExtYneg, job.Stock.ExtYpos, job.Stock.ExtZneg, job.Stock.ExtZpos) self.dialog.stockPlacement.setChecked(False) elif hasattr(job.Stock, 'Length') and hasattr(job.Stock, 'Width'): seHint = translate('PathJob', "Box: %.2f x %.2f x %.2f") % (job.Stock.Length, job.Stock.Width, job.Stock.Height) elif hasattr(job.Stock, 'Radius'): seHint = translate('PathJob', "Cylinder: %.2f x %.2f") % (job.Stock.Radius, job.Stock.Height) else: seHint = '-' PathLog.error(translate('PathJob', 'Unsupported stock type')) self.dialog.stockExtentHint.setText(seHint) spHint = "%s" % job.Stock.Placement self.dialog.stockPlacementHint.setText(spHint) for tc in sorted(job.ToolController, key=lambda o: o.Label): item = QtGui.QListWidgetItem(tc.Label) item.setData(self.DataObject, tc) item.setCheckState(QtCore.Qt.CheckState.Checked) self.dialog.toolsList.addItem(item) self.dialog.toolsGroup.clicked.connect(self.checkUncheckTools)
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 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 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 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 getFields(self, obj, fields = ['radius', 'height']): if self.IsStock(obj): if 'radius' in fields: obj.Stock.Radius = FreeCAD.Units.Quantity(self.form.stockCylinderRadius.text()) if 'height' in fields: obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockCylinderHeight.text()) else: PathLog.error(translate('PathJob', 'Stock not a cylinder!'))
def tooltableFromAttrs(self, stringattrs): if stringattrs.get('Version') and 1 == int(stringattrs['Version']): attrs = {} for key, val in PathUtil.keyValueIter(stringattrs['Tools']): attrs[int(key)] = val return Path.Tooltable(attrs) else: PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable template version %s") % stringattrs.get('Version')) return None
def getFields(self, obj, fields = ['length', 'widht', 'height']): if self.IsStock(obj): if 'length' in fields: obj.Stock.Length = FreeCAD.Units.Quantity(self.form.stockBoxLength.text()) if 'width' in fields: obj.Stock.Width = FreeCAD.Units.Quantity(self.form.stockBoxWidth.text()) if 'height' in fields: obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockBoxHeight.text()) else: PathLog.error(translate('PathJob', 'Stock not a box!'))
def createRampEdge(self, originalEdge, startPoint, endPoint): # PathLog.debug("Create edge from [{},{},{}] to [{},{},{}]".format(startPoint.x,startPoint.y, startPoint.z, endPoint.x, endPoint.y, endPoint.z)) if type(originalEdge.Curve) == Part.Line or type(originalEdge.Curve) == Part.LineSegment: return Part.makeLine(startPoint, endPoint) elif type(originalEdge.Curve) == Part.Circle: arcMid = originalEdge.valueAt((originalEdge.FirstParameter + originalEdge.LastParameter) / 2) arcMid.z = (startPoint.z + endPoint.z) / 2 return Part.Arc(startPoint, arcMid, endPoint).toShape() else: PathLog.error("Edge should not be helix")
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: selected = FreeCADGui.Selection.getSelection() if 1 == len(selected) and selected[0] in jobs: job = selected[0] else: modelSelected = [] for job in jobs: if all([o in job.Model.Group for o in selected]): modelSelected.append(job) if 1 == len(modelSelected): job = modelSelected[0] else: modelObjectSelected = [] for job in jobs: if all([o in job.Proxy.baseObjects(job) for o in selected]): modelObjectSelected.append(job) if 1 == len(modelObjectSelected): job = modelObjectSelected[0] else: # form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgJobChooser.ui") form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui") if modelObjectSelected: mylist = [j.Label for j in modelObjectSelected] else: mylist = [j.Label for j in jobs] form.cboProject.addItems(mylist) r = form.exec_() if r is False or r == 0: return None else: job = [j for j in jobs if j.Label == form.cboProject.currentText()][0] if obj and job: job.Proxy.addOperation(obj) return job
def execute(self, obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): return if not obj.Base.Path: return if obj.Length < 0: PathLog.error(translate("Length/Radius positive not Null")+"\n") obj.Length = 0.1 self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) obj.Path = self.generateLeadInOutCurve(obj)
def setup(self, obj): obj.Angle = 60 obj.Method = 2 toolLoad = obj.ToolController if toolLoad is None or toolLoad.ToolNumber == 0: PathLog.error(translate("No Tool Controller is selected. We need a tool to build a Path\n")) return else: tool = toolLoad.Proxy.getTool(toolLoad) if not tool: PathLog.error(translate("No Tool found. We need a tool to build a Path.\n")) return
def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() zValues = [] if obj.StepDown.Value != 0: z = obj.StartDepth.Value - obj.StepDown.Value while z > obj.FinalDepth.Value: zValues.append(z) z -= obj.StepDown.Value zValues.append(obj.FinalDepth.Value) self.zValues = zValues output = '' try: if self.baseobject.isDerivedFrom('Sketcher::SketchObject') or \ self.baseobject.isDerivedFrom('Part::Part2DObject') or \ hasattr(self.baseobject, 'ArrayType'): output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) # we only consider the outer wire if this is a Face wires = [] for w in self.baseobject.Shape.Wires: tempedges = PathUtils.cleanedges(w.Edges, 0.5) wires.append(Part.Wire(tempedges)) output += self.buildpathocc(obj, wires, zValues) self.wires = wires elif isinstance(self.baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet wires = [] for tag in self.baseobject.Proxy.getTags(self.baseobject, transform=True): output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) tagWires = [] for w in tag.Wires: tempedges = PathUtils.cleanedges(w.Edges, 0.5) tagWires.append(Part.Wire(tempedges)) output += self.buildpathocc(obj, tagWires, zValues) wires.extend(tagWires) self.wires = wires else: raise ValueError('Unknown baseobject type for engraving') output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) except Exception as e: #PathLog.error("Exception: %s" % e) PathLog.error(translate("Path", "The Job Base Object has no engraveable element. Engraving operation will produce no output."))
def shapeBoundBox(obj): if hasattr(obj, 'Shape'): return obj.Shape.BoundBox if obj and 'App::Part' == obj.TypeId: bounds = [shapeBoundBox(o) for o in obj.Group] if bounds: bb = bounds[0] for b in bounds[1:]: bb = bb.united(b) return bb if obj: PathLog.error(translate('PathStock', "Invalid base object %s - no shape found") % obj.Name) return None
def addOperation(self, op, before = None): group = self.obj.Operations.Group if op not in group: if before: try: group.insert(group.index(before), op) except Exception as e: PathLog.error(e) group.append(op) else: group.append(op) self.obj.Operations.Group = group op.Path.Center = self.obj.Operations.Path.Center
def CreateToolProfile(self, tool, dir, pos, rad): type = tool.ToolType # rad = tool.Diameter / 2.0 - 0.001 * pos[2] # hack to overcome occ bug xf = dir[0] * rad yf = dir[1] * rad xp = pos[0] yp = pos[1] zp = pos[2] h = tool.CuttingEdgeHeight if h <= 0.0: #set default if user fails to avoid freeze h = 1.0 PathLog.error("SET Tool Length") # common to all tools vTR = Vector(xp + yf, yp - xf, zp + h) vTC = Vector(xp, yp, zp + h) vBC = Vector(xp, yp, zp) lT = Part.makeLine(vTR, vTC) res = None if type == "ChamferMill": ang = 90 - tool.CuttingEdgeAngle / 2.0 if ang > 80: ang = 80 if ang < 0: ang = 0 h1 = math.tan(ang * math.pi / 180) * rad if h1 > (h - 0.1): h1 = h - 0.1 vBR = Vector(xp + yf, yp - xf, zp + h1) lR = Part.makeLine(vBR, vTR) lB = Part.makeLine(vBC, vBR) res = Part.Wire([lB, lR, lT]) elif type == "BallEndMill": h1 = rad if h1 >= h: h1 = h - 0.1 vBR = Vector(xp + yf, yp - xf, zp + h1) r2 = h1 / 2.0 h2 = rad - math.sqrt(rad * rad - r2 * r2) vBCR = Vector(xp + yf / 2.0, yp - xf / 2.0, zp + h2) cB = Part.Edge(Part.Arc(vBC, vBCR, vBR)) lR = Part.makeLine(vBR, vTR) res = Part.Wire([cB, lR, lT]) else: # default: assume type == "EndMill" vBR = Vector(xp + yf, yp - xf, zp) lR = Part.makeLine(vBR, vTR) lB = Part.makeLine(vBC, vBR) res = Part.Wire([lB, lR, lT]) return res
def _setBaseAndStock(self, obj, ignoreErrors=False): job = PathUtils.findParentJob(obj) if not job: if not ignoreErrors: PathLog.error(translate("Path", "No parent job found for operation.")) return False if not job.Model.Group: if not ignoreErrors: PathLog.error(translate("Path", "Parent job %s doesn't have a base object") % job.Label) return False self.job = job self.model = job.Model.Group self.stock = job.Stock return True
def Activated(self): # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: PathLog.error(translate('Path_DressupTag', 'Please select one path object')+'\n') return baseObject = selection[0] # everything ok! FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupTag', 'Create Tag Dress-up')) FreeCADGui.addModule('PathScripts.PathDressupTagGui') FreeCADGui.doCommand("PathScripts.PathDressupTagGui.Create(App.ActiveDocument.%s)" % baseObject.Name) FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute()
def Create(base, template=None): '''Create(base, template) ... creates a job instance for the given base object using template to configure it.''' FreeCADGui.addModule('PathScripts.PathJob') FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job")) try: obj = PathJob.Create('Job', base, template) ViewProvider(obj.ViewObject) FreeCAD.ActiveDocument.commitTransaction() obj.ViewObject.Proxy.editObject(obj.Stock) return obj except: PathLog.error(sys.exc_info()) FreeCAD.ActiveDocument.abortTransaction()
def drillTipLength(tool): """returns the length of the drillbit tip.""" if tool.CuttingEdgeAngle == 180 or tool.CuttingEdgeAngle == 0.0 or tool.Diameter == 0.0: return 0.0 else: if tool.CuttingEdgeAngle <= 0 or tool.CuttingEdgeAngle >= 180: PathLog.error(translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°") % tool.CuttingEdgeAngle) return 0.0 theta = math.radians(tool.CuttingEdgeAngle) length = (tool.Diameter/2) / math.tan(theta/2) if length < 0: PathLog.error(translate("Path", "Cutting Edge Angle (%.2f) results in negative tool tip length") % tool.CuttingEdgeAngle) return 0.0 return length
def updateStockEditor(self, index): def setupFromBaseEdit(): if not self.stockFromBase: self.stockFromBase = StockFromBaseBoundBoxEdit(self.obj, self.form) self.stockEdit = self.stockFromBase def setupCreateBoxEdit(): if not self.stockCreateBox: self.stockCreateBox = StockCreateBoxEdit(self.obj, self.form) self.stockEdit = self.stockCreateBox def setupCreateCylinderEdit(): if not self.stockCreateCylinder: self.stockCreateCylinder = StockCreateCylinderEdit(self.obj, self.form) self.stockEdit = self.stockCreateCylinder def setupFromExisting(): if not self.stockFromExisting: self.stockFromExisting = StockFromExistingEdit(self.obj, self.form) if self.stockFromExisting.candidates(self.obj): self.stockEdit = self.stockFromExisting return True return False if index == -1: if self.obj.Stock is None or StockFromBaseBoundBoxEdit.IsStock(self.obj): setupFromBaseEdit() elif StockCreateBoxEdit.IsStock(self.obj): setupCreateBoxEdit() elif StockCreateCylinderEdit.IsStock(self.obj): setupCreateCylinderEdit() elif StockFromExistingEdit.IsStock(self.obj): setupFromExisting() else: PathLog.error(translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label) else: if index == StockFromBaseBoundBoxEdit.Index: setupFromBaseEdit() elif index == StockCreateBoxEdit.Index: setupCreateBoxEdit() elif index == StockCreateCylinderEdit.Index: setupCreateCylinderEdit() elif index == StockFromExistingEdit.Index: if not setupFromExisting(): setupFromBaseEdit() index = -1 else: PathLog.error(translate('PathJob', "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index)) self.stockEdit.activate(self.obj, index == -1) if -1 != index: self.template.updateUI()
def doExecute(self, obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): return if not obj.Base.Path: return if not obj.Base.Path.Commands: return pathData = self.setup(obj) if not pathData: print("execute - no pathData") return self.tags = [] if hasattr(obj, "Positions"): self.tags, positions, disabled = self.createTagsPositionDisabled(obj, obj.Positions, obj.Disabled) if obj.Disabled != disabled: PathLog.debug("Updating properties.... %s vs. %s" % (obj.Disabled, disabled)) obj.Positions = positions obj.Disabled = disabled if not self.tags: print("execute - no tags") obj.Path = obj.Base.Path return try: self.processTags(obj) except Exception as e: PathLog.error("processing tags failed clearing all tags ... '%s'" % (e.args[0])) #if sys.version_info.major < 3: # traceback.print_exc(e) #else: # traceback.print_exc() obj.Path = obj.Base.Path # update disabled in case there are some additional ones disabled = copy.copy(self.obj.Disabled) solids = [] for tag in self.tags: solids.append(tag.solid) if not tag.enabled and tag.id not in disabled: disabled.append(tag.id) self.solids = solids if obj.Disabled != disabled: obj.Disabled = disabled
def opOnChanged(self, obj, prop): '''opOnChanged(obj, prop) ... base implemenation of the notification framework - do not overwrite. The base implementation takes a stab at determining Heights and Depths if the operations's Base changes. Do not overwrite, overwrite areaOpOnChanged(obj, prop) instead.''' #PathLog.track(obj.Label, prop) if prop in ['AreaParams', 'PathParams', 'removalshape']: obj.setEditorMode(prop, 2) if PathOp.FeatureBaseGeometry & self.opFeatures(obj): if prop == 'Base' and len(obj.Base) == 1: PathLog.info("opOnChanged(%s, %s)" % (obj.Label, prop)) try: (base, sub) = obj.Base[0] bb = base.Shape.BoundBox # parent boundbox subobj = base.Shape.getElement(sub[0]) fbb = subobj.BoundBox # feature boundbox obj.StartDepth = bb.ZMax obj.ClearanceHeight = bb.ZMax + 5.0 obj.SafeHeight = bb.ZMax + 3.0 if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face obj.FinalDepth = bb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut obj.FinalDepth = fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall obj.FinalDepth = fbb.ZMin elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf obj.FinalDepth = fbb.ZMin else: # catch all obj.FinalDepth = bb.ZMin if hasattr(obj, 'Side'): if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: obj.Side = "Outside" else: obj.Side = "Inside" except Exception as e: PathLog.error(translate("PatArea", "Error in calculating depths: %s") % e) obj.StartDepth = 5.0 obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 if hasattr(obj, 'Side'): obj.Side = "Outside" self.areaOpOnChanged(obj, prop)
def updateUI(self): job = self.job if job.PostProcessor: ppHint = "%s %s %s" % (job.PostProcessor, job.PostProcessorArgs, job.PostProcessorOutputFile) self.dialog.postProcessingHint.setText(ppHint) else: self.dialog.postProcessingGroup.setEnabled(False) self.dialog.postProcessingGroup.setChecked(False) if job.Stock and not PathJob.isResourceClone(job, 'Stock', 'Stock'): stockType = PathStock.StockType.FromStock(job.Stock) if stockType == PathStock.StockType.FromBase: seHint = translate('PathJob', "Base -/+ %.2f/%.2f %.2f/%.2f %.2f/%.2f") % (job.Stock.ExtXneg, job.Stock.ExtXpos, job.Stock.ExtYneg, job.Stock.ExtYpos, job.Stock.ExtZneg, job.Stock.ExtZpos) self.dialog.stockPlacement.setChecked(False) elif stockType == PathStock.StockType.CreateBox: seHint = translate('PathJob', "Box: %.2f x %.2f x %.2f") % (job.Stock.Length, job.Stock.Width, job.Stock.Height) elif stockType == PathStock.StockType.CreateCylinder: seHint = translate('PathJob', "Cylinder: %.2f x %.2f") % (job.Stock.Radius, job.Stock.Height) else: seHint = '-' PathLog.error(translate('PathJob', 'Unsupported stock type')) self.dialog.stockExtentHint.setText(seHint) spHint = "%s" % job.Stock.Placement self.dialog.stockPlacementHint.setText(spHint) rapidChanged = not job.SetupSheet.Proxy.hasDefaultToolRapids() depthsChanged = not job.SetupSheet.Proxy.hasDefaultOperationDepths() heightsChanged = not job.SetupSheet.Proxy.hasDefaultOperationHeights() opsWithSettings = job.SetupSheet.Proxy.operationsWithSettings() settingsChanged = rapidChanged or depthsChanged or heightsChanged or 0 != len(opsWithSettings) self.dialog.settingsGroup.setChecked(settingsChanged) self.dialog.settingToolRapid.setChecked(rapidChanged) self.dialog.settingOperationDepths.setChecked(depthsChanged) self.dialog.settingOperationHeights.setChecked(heightsChanged) self.dialog.settingsOpsList.clear() for op in opsWithSettings: item = QtGui.QListWidgetItem(op) item.setCheckState(QtCore.Qt.CheckState.Checked) self.dialog.settingsOpsList.addItem(item) self.dialog.toolsList.clear() for tc in sorted(job.ToolController, key=lambda o: o.Label): item = QtGui.QListWidgetItem(tc.Label) item.setData(self.DataObject, tc) item.setCheckState(QtCore.Qt.CheckState.Checked) self.dialog.toolsList.addItem(item)
def getreversed(self, edges): """ Reverses the edge array and the direction of each edge """ outedges = [] for edge in reversed(edges): # reverse the start and end points startPoint = edge.valueAt(edge.LastParameter) endPoint = edge.valueAt(edge.FirstParameter) if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: outedges.append(Part.makeLine(startPoint, endPoint)) elif type(edge.Curve) == Part.Circle: arcMid = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2) outedges.append(Part.Arc(startPoint, arcMid, endPoint).toShape()) else: PathLog.error("Edge should not be helix") return outedges
def updateToolController(self): tc = self.obj try: tc.Label = self.form.tcName.text() tc.ToolNumber = self.form.tcNumber.value() self.horizFeed.updateProperty() self.vertFeed.updateProperty() self.horizRapid.updateProperty() self.vertRapid.updateProperty() tc.SpindleSpeed = self.form.spindleSpeed.value() tc.SpindleDir = self.form.spindleDirection.currentText() self.editor.updateTool() tc.Tool = self.editor.tool except Exception as e: PathLog.error(translate("PathToolController", "Error updating TC: %s") % e)
def getFields(self, obj, fields = ['xneg', 'xpos', 'yneg', 'ypos', 'zneg', 'zpos']): PathLog.track(obj.Label, fields) if self.IsStock(obj): if 'xneg' in fields: obj.Stock.ExtXneg = FreeCAD.Units.Quantity(self.form.stockExtXneg.text()) if 'xpos' in fields: obj.Stock.ExtXpos = FreeCAD.Units.Quantity(self.form.stockExtXpos.text()) if 'yneg' in fields: obj.Stock.ExtYneg = FreeCAD.Units.Quantity(self.form.stockExtYneg.text()) if 'ypos' in fields: obj.Stock.ExtYpos = FreeCAD.Units.Quantity(self.form.stockExtYpos.text()) if 'zneg' in fields: obj.Stock.ExtZneg = FreeCAD.Units.Quantity(self.form.stockExtZneg.text()) if 'zpos' in fields: obj.Stock.ExtZpos = FreeCAD.Units.Quantity(self.form.stockExtZpos.text()) else: PathLog.error(translate('PathJob', 'Stock not from Base bound box!'))
def Create(baseObject, name='DressupTag'): ''' Create(basePath, name='DressupTag') ... create tag dressup object for the given base path. ''' if not baseObject.isDerivedFrom('Path::Feature'): PathLog.error(translate('Path_DressupTag', 'The selected object is not a path')+'\n') return None if baseObject.isDerivedFrom('Path::FeatureCompoundPython'): PathLog.error(translate('Path_DressupTag', 'Please select a Profile object')) return None obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "TagDressup") dbo = ObjectTagDressup(obj, baseObject) job = PathUtils.findParentJob(baseObject) job.Proxy.addOperation(obj) dbo.setup(obj, True) return obj
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 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 and UserInput: job = UserInput.createJob() elif len(jobs) == 1: job = jobs[0] elif UserInput: job = UserInput.chooseJob(jobs) if obj and job: job.Proxy.addOperation(obj) return job
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)) else: PathLog.error(translate('PathToolController', "Unsupported PathToolController template version %s") % template.get(ToolControllerTemplate.Version))
def addBaseGeometry(self, selection): PathLog.track(selection) if len(selection) != 1: 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": PathLog.error(translate("PathProject", "Vertexes are not supported")) return False if not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge": PathLog.error(translate("PathProject", "Edges are not supported")) return False if not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face": PathLog.error(translate("PathProject", "Faces are not supported")) return False else: if not self.supportsPanels() or not 'Panel' in sel.Object.Name: PathLog.error(translate("PathProject", "Please select %s of a solid" % self.featureName())) return False for sub in sel.SubElementNames: self.obj.Proxy.addBase(self.obj, sel.Object, sub) return True
def opSetDefaultValues(self, obj, job): """opSetDefaultValues(obj) ... base implementation, do not overwrite. The base implementation sets the depths and heights based on the areaOpShapeForDepths() return value. Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.""" PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) if PathOp.FeatureDepths & self.opFeatures(obj): try: shape = self.areaOpShapeForDepths(obj, job) except Exception as ee: PathLog.error(ee) shape = None # Set initial start and final depths if shape is None: PathLog.debug("shape is None") startDepth = 1.0 finalDepth = 0.0 else: bb = job.Stock.Shape.BoundBox startDepth = bb.ZMax finalDepth = bb.ZMin # obj.StartDepth.Value = startDepth # obj.FinalDepth.Value = finalDepth obj.OpStartDepth.Value = startDepth obj.OpFinalDepth.Value = finalDepth PathLog.debug( "Default OpDepths are Start: {}, and Final: {}".format( obj.OpStartDepth.Value, obj.OpFinalDepth.Value)) PathLog.debug("Default Depths are Start: {}, and Final: {}".format( startDepth, finalDepth)) self.areaOpSetDefaultValues(obj, job)
def setup(self, obj, generate=False): PathLog.debug("setup") self.obj = obj try: pathData = PathData(obj) except ValueError: PathLog.error( translate( "Path_DressupTag", "Cannot insert holding tags for this path - please select a Profile path" ) + "\n") return None self.toolRadius = PathDressup.toolController( obj.Base).Tool.Diameter / 2 self.pathData = pathData if generate: obj.Height = self.pathData.defaultTagHeight() obj.Width = self.pathData.defaultTagWidth() obj.Angle = self.pathData.defaultTagAngle() obj.Radius = self.pathData.defaultTagRadius() count = HoldingTagPreferences.defaultCount() self.generateTags(obj, count) return self.pathData
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 tooltableFromAttrs(self, stringattrs): if stringattrs.get("Version") and 1 == int(stringattrs["Version"]): tt = Path.Tooltable() tt.Version = 1 tt.Name = self.getNextToolTableName() if stringattrs.get("Version"): tt.Version = stringattrs.get("Version") if stringattrs.get("TableName"): tt.Name = stringattrs.get("TableName") if any(table.Name == tt.Name for table in self.toolTables): tt.Name = self.getNextToolTableName(tt.Name) for key, attrs in PathUtil.keyValueIter(stringattrs["Tools"]): tool = Path.Tool() tool.Name = str(attrs["name"]) tool.ToolType = str(attrs["tooltype"]) tool.Material = str(attrs["material"]) tool.Diameter = float(attrs["diameter"]) tool.LengthOffset = float(attrs["lengthOffset"]) tool.FlatRadius = float(attrs["flatRadius"]) tool.CornerRadius = float(attrs["cornerRadius"]) tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"]) tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"]) tt.setTool(int(key), tool) return tt else: PathLog.error( translate( "PathToolLibraryManager", "Unsupported Path tooltable template version %s", ) % stringattrs.get("Version")) return None
def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle")) if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees.")) return try: faces = [] for base in obj.BaseShapes: faces.extend(base.Shape.Faces) for base in obj.Base: for sub in base[1]: shape = getattr(base[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) if not faces: for model in self.model: if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'): faces.extend(model.Shape.Faces) if faces: self.buildPathMedial(obj, faces) else: PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.')) except Exception as e: #PathLog.error(e) #traceback.print_exc() PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.'))
def Activated(self): # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: PathLog.error( translate("Path_DressupRampEntry", "Please select one path object") + "\n" ) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): PathLog.error( translate("Path_DressupRampEntry", "The selected object is not a path") + "\n" ) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): PathLog.error( translate("Path_DressupRampEntry", "Please select a Profile object") ) return # everything ok! FreeCAD.ActiveDocument.openTransaction("Create RampEntry Dress-up") FreeCADGui.addModule("PathScripts.PathDressupRampEntry") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "RampEntryDressup")' ) FreeCADGui.doCommand( "dbo = PathScripts.PathDressupRampEntry.ObjectDressup(obj)" ) FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name) FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)") FreeCADGui.doCommand("obj.Base = base") FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)") FreeCADGui.doCommand( "obj.ViewObject.Proxy = PathScripts.PathDressupRampEntry.ViewProviderDressup(obj.ViewObject)" ) FreeCADGui.doCommand( "Gui.ActiveDocument.getObject(base.Name).Visibility = False" ) FreeCADGui.doCommand("dbo.setup(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute()
def Activated(self): # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: PathLog.error(translate("Please select one path object\n")) return baseObject = selection[0] if not baseObject.isDerivedFrom("Path::Feature"): PathLog.error(translate("The selected object is not a path\n")) return if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): PathLog.error(translate("Please select a Profile object")) return # everything ok! FreeCAD.ActiveDocument.openTransaction( translate("Create RampEntry Dress-up")) FreeCADGui.addModule("PathScripts.PathDressUpRampEntry") FreeCADGui.addModule("PathScripts.PathUtils") FreeCADGui.doCommand( 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "RampEntryDressup")' ) FreeCADGui.doCommand( 'dbo = PathScripts.PathDressupRampEntry.ObjectDressup(obj)') FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) FreeCADGui.doCommand( 'PathScripts.PathDressupRampEntry.ViewProviderDressup(obj.ViewObject)' ) FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand( 'Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') FreeCADGui.doCommand( 'obj.ToolController = PathScripts.PathUtils.findToolController(obj)' ) FreeCADGui.doCommand('dbo.setup(obj)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute()
def opSetDefaultValues(self, obj, job): '''opSetDefaultValues(obj) ... base implementation, do not overwrite. The base implementation sets the depths and heights based on the areaOpShapeForDepths() return value. Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.''' PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label)) # Initial setting for EnableRotation is taken from Job settings/SetupSheet # User may override on per-operation basis as needed. if hasattr(job.SetupSheet, 'SetupEnableRotation'): obj.EnableRotation = job.SetupSheet.SetupEnableRotation else: obj.EnableRotation = 'Off' PathLog.debug("opSetDefaultValues(): Enable Rotation: {}".format( obj.EnableRotation)) if PathOp.FeatureDepths & self.opFeatures(obj): try: shape = self.areaOpShapeForDepths(obj, job) except Exception as ee: # pylint: disable=broad-except PathLog.error(ee) shape = None # Set initial start and final depths if shape is None: PathLog.debug("shape is None") startDepth = 1.0 finalDepth = 0.0 else: bb = job.Stock.Shape.BoundBox startDepth = bb.ZMax finalDepth = bb.ZMin # Adjust start and final depths if rotation is enabled if obj.EnableRotation != 'Off': self.initWithRotation = True self.stockBB = PathUtils.findParentJob( obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init # Calculate rotational distances/radii opHeights = self.opDetermineRotationRadii( obj ) # return is list with tuples [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfset)] (xRotRad, yRotRad, zRotRad) = opHeights[0] # pylint: disable=unused-variable PathLog.debug("opHeights[0]: " + str(opHeights[0])) PathLog.debug("opHeights[1]: " + str(opHeights[1])) if obj.EnableRotation == 'A(x)': startDepth = xRotRad if obj.EnableRotation == 'B(y)': startDepth = yRotRad else: startDepth = max(xRotRad, yRotRad) finalDepth = -1 * startDepth obj.StartDepth.Value = startDepth obj.FinalDepth.Value = finalDepth obj.OpStartDepth.Value = startDepth obj.OpFinalDepth.Value = finalDepth PathLog.debug( "Default OpDepths are Start: {}, and Final: {}".format( obj.OpStartDepth.Value, obj.OpFinalDepth.Value)) PathLog.debug("Default Depths are Start: {}, and Final: {}".format( startDepth, finalDepth)) self.areaOpSetDefaultValues(obj, job)
def helix_cut(self, obj, x0, y0, r_out, r_in, dr): '''helix_cut(obj, x0, y0, r_out, r_in, dr) ... generate helix commands for specified hole. x0, y0: coordinates of center r_out, r_in: outer and inner radius of the hole dr: step over radius value''' from numpy import ceil, linspace if (obj.StartDepth.Value <= obj.FinalDepth.Value): return out = "(helix_cut <{0}, {1}>, {2})".format( x0, y0, ", ".join( map(str, (r_out, r_in, dr, obj.StartDepth.Value, obj.FinalDepth.Value, obj.StepDown.Value, obj.SafeHeight.Value, self.radius, self.vertFeed, self.horizFeed, obj.Direction, obj.StartSide)))) nz = max( int( ceil((obj.StartDepth.Value - obj.FinalDepth.Value) / obj.StepDown.Value)), 2) zi = linspace(obj.StartDepth.Value, obj.FinalDepth.Value, 2 * nz + 1) def xyz(x=None, y=None, z=None): out = "" if x is not None: out += " X" + fmt(x) if y is not None: out += " Y" + fmt(y) if z is not None: out += " Z" + fmt(z) return out def rapid(x=None, y=None, z=None): return "G0" + xyz(x, y, z) + "\n" def F(f=None): return (" F" + fmt(f) if f else "") def feed(x=None, y=None, z=None, f=None): return "G1" + xyz(x, y, z) + F(f) + "\n" def arc(x, y, i, j, z, f): if obj.Direction == "CW": code = "G2" elif obj.Direction == "CCW": code = "G3" return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt( x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n" def helix_cut_r(r): arc_cmd = 'G2' if obj.Direction == 'CW' else 'G3' out = "" out += rapid(x=x0 + r, y=y0) self.commandlist.append( Path.Command('G0', { 'X': x0 + r, 'Y': y0, 'F': self.horizRapid })) out += rapid(z=obj.StartDepth.Value + 2 * self.radius) self.commandlist.append( Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) out += feed(z=obj.StartDepth.Value, f=self.vertFeed) self.commandlist.append( Path.Command('G1', { 'Z': obj.StartDepth.Value, 'F': self.vertFeed })) # z = obj.FinalDepth.Value for i in range(1, nz + 1): out += arc(x0 - r, y0, i=-r, j=0.0, z=zi[2 * i - 1], f=self.horizFeed) self.commandlist.append( Path.Command( arc_cmd, { 'X': x0 - r, 'Y': y0, 'Z': zi[2 * i - 1], 'I': -r, 'J': 0.0, 'F': self.horizFeed })) out += arc(x0 + r, y0, i=r, j=0.0, z=zi[2 * i], f=self.horizFeed) self.commandlist.append( Path.Command( arc_cmd, { 'X': x0 + r, 'Y': y0, 'Z': zi[2 * i], 'I': r, 'J': 0.0, 'F': self.horizFeed })) out += arc(x0 - r, y0, i=-r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) self.commandlist.append( Path.Command( arc_cmd, { 'X': x0 - r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': -r, 'J': 0.0, 'F': self.horizFeed })) out += arc(x0 + r, y0, i=r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) self.commandlist.append( Path.Command( arc_cmd, { 'X': x0 + r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': r, 'J': 0.0, 'F': self.horizFeed })) out += feed(z=obj.StartDepth.Value + 2 * self.radius, f=self.vertFeed) out += rapid(z=obj.SafeHeight.Value) self.commandlist.append( Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) return out assert (r_out > 0.0) assert (r_in >= 0.0) msg = None if r_out < 0.0: msg = "r_out < 0" elif r_in > 0 and r_out - r_in < 2 * self.radius: msg = "r_out - r_in = {0} is < tool diameter of {1}".format( r_out - r_in, 2 * self.radius) elif r_in == 0.0 and not r_out > self.radius / 2.: msg = "Cannot drill a hole of diameter {0} with a tool of diameter {1}".format( 2 * r_out, 2 * self.radius) elif obj.StartSide not in ["Inside", "Outside"]: msg = "Invalid value for parameter 'obj.StartSide'" if msg: out += "(ERROR: Hole at {0}:".format( (x0, y0, obj.StartDepth.Value)) + msg + ")\n" PathLog.error("PathHelix: Hole at {0}:".format( (x0, y0, obj.StartDepth.Value)) + msg + "\n") return out if r_in > 0: out += "(annulus mode)\n" r_out = r_out - self.radius r_in = r_in + self.radius if abs((r_out - r_in) / dr) < 1e-5: radii = [(r_out + r_in) / 2] else: nr = max(int(ceil((r_out - r_in) / dr)), 2) radii = linspace(r_out, r_in, nr) elif r_out <= 2 * dr: out += "(single helix mode)\n" radii = [r_out - self.radius] assert (radii[0] > 0) else: out += "(full hole mode)\n" r_out = r_out - self.radius r_in = dr / 2 nr = max(1 + int(ceil((r_out - r_in) / dr)), 2) radii = linspace(r_out, r_in, nr) assert (all(radii > 0)) if obj.StartSide == "Inside": radii = radii[::-1] for r in radii: out += "(radius {0})\n".format(r) out += helix_cut_r(r) return out
def errorMessage(grp, job): PathLog.error("{} corrupt in {} job.".format(grp, job.Name))
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 faceType = 0 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 sdi = len(starts) - 1 fdi = len(finals) - 1 cbi = len(cuts) - 1 pocket = (cuts[cbi], False, '3DPocket', 0.0, 'X', starts[sdi], finals[fdi]) 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 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.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 job = PathUtils.findParentJob(obj) if not job: PathLog.error( translate("Path", "No parent job found for operation.")) return if not job.Base: PathLog.error( translate("Path", "Parent job %s doesn't have a base object") % job.Label) return self.baseobject = job.Base 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 else: self.radius = tool.Diameter / 2 self.tool = tool 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 areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") self.removalshapes = [] self.horiz = [] vertical = [] for o in obj.Base: PathLog.debug("Base item: {}".format(o)) base = o[0] for sub in o[1]: if "Face" in sub: face = base.Shape.getElement(sub) if type(face.Surface) == Part.Plane and PathGeom.isVertical(face.Surface.Axis): # it's a flat horizontal face self.horiz.append(face) elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis): # vertical cylinder wall if any(e.isClosed() for e in face.Edges): # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) self.horiz.append(disk) else: # partial cylinder wall vertical.append(face) elif type(face.Surface) == Part.Plane and PathGeom.isHorizontal(face.Surface.Axis): vertical.append(face) else: PathLog.error(translate('PathPocket', "Pocket does not support shape %s.%s") % (base.Label, sub)) self.vertical = PathGeom.combineConnectedShapes(vertical) 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) # 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 = [] for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() shape.tessellate(0.1) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) self.horizontal.append(Part.Face(wire)) else: self.horizontal.append(shape) # extrude all faces up to StartDepth and those are the removal shapes extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) self.removalshapes = [(face.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.outline = Part.Face(TechDraw.findShapeOutline(self.baseobject.Shape, 1, FreeCAD.Vector(0, 0, 1))) stockBB = self.stock.Shape.BoundBox self.outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) self.body = self.outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.removalshapes = [(self.stock.Shape.cut(self.body), False)] for (shape,hole) in self.removalshapes: shape.tessellate(0.1) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def libararySaveLinuxCNC(self, path): # linuxcnc line template LIN = "T{} P{} X{} Y{} Z{} A{} B{} C{} U{} V{} W{} D{} I{} J{} Q{}; {}" with open(path, "w") as fp: fp.write(";\n") for row in range(self.toolModel.rowCount()): toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole) toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole) bit = PathToolBit.Factory.CreateFrom(toolPath) if bit: PathLog.track(bit) pocket = bit.Pocket if hasattr(bit, "Pocket") else "0" xoffset = bit.Xoffset if hasattr(bit, "Xoffset") else "0" yoffset = bit.Yoffset if hasattr(bit, "Yoffset") else "0" zoffset = bit.Zoffset if hasattr(bit, "Zoffset") else "0" aoffset = bit.Aoffset if hasattr(bit, "Aoffset") else "0" boffset = bit.Boffset if hasattr(bit, "Boffset") else "0" coffset = bit.Coffset if hasattr(bit, "Coffset") else "0" uoffset = bit.Uoffset if hasattr(bit, "Uoffset") else "0" voffset = bit.Voffset if hasattr(bit, "Voffset") else "0" woffset = bit.Woffset if hasattr(bit, "Woffset") else "0" diameter = (bit.Diameter.getUserPreferred()[0].split()[0] if hasattr(bit, "Diameter") else "0") frontangle = bit.FrontAngle if hasattr( bit, "FrontAngle") else "0" backangle = bit.BackAngle if hasattr(bit, "BackAngle") else "0" orientation = (bit.Orientation if hasattr( bit, "Orientation") else "0") remark = bit.Label fp.write( LIN.format( toolNr, pocket, xoffset, yoffset, zoffset, aoffset, boffset, coffset, uoffset, voffset, woffset, diameter, frontangle, backangle, orientation, remark, ) + "\n") FreeCAD.ActiveDocument.removeObject(bit.Name) else: PathLog.error("Could not find tool #{} ".format(toolNr))
def analyzeVerticalFaces(self, obj, vertTuples): hT = [] # base = FreeCAD.ActiveDocument.getObject(self.modelName) # Separate elements, regroup by orientation (axis_angle combination) vTags = ['X34.2'] vGrps = [[(2.3, 3.4, 'X')]] for tup in vertTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in vTags: # Determine index of found string i = 0 for orn in vTags: if orn == tag: break i += 1 vGrps[i].append(tup) else: vTags.append(tag) # add orientation entry vGrps.append([tup]) # add orientation entry # Remove temp elements vTags.pop(0) vGrps.pop(0) # check all faces in each axis_angle group shpList = [] zmaxH = 0.0 for o in range(0, len(vTags)): shpList = [] zmaxH = vGrps[o][0].BoundBox.ZMax for (face, sub, angle, axis, tag, strDep, finDep, trans) in vGrps[o]: shpList.append(face) # Identify tallest face to use as zMax if face.BoundBox.ZMax > zmaxH: zmaxH = face.BoundBox.ZMax # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop self.vertical = PathGeom.combineConnectedShapes(shpList) 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.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring') ) else: strDep = zmaxH + self.leadIn # base.Shape.BoundBox.ZMax finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, sub, angle, axis, tag, strDep, finDep, trans hT.append(tup) # Eol return hT
def updateStockEditor(self, index, force=False): import PathScripts.PathJobGui as PathJobGui import PathScripts.PathStock as PathStock def setupFromBaseEdit(): PathLog.track(index, force) if force or not self.stockFromBase: self.stockFromBase = PathJobGui.StockFromBaseBoundBoxEdit( self.obj, self.form, force) self.stockEdit = self.stockFromBase def setupCreateBoxEdit(): PathLog.track(index, force) if force or not self.stockCreateBox: self.stockCreateBox = PathJobGui.StockCreateBoxEdit( self.obj, self.form, force) self.stockEdit = self.stockCreateBox def setupCreateCylinderEdit(): PathLog.track(index, force) if force or not self.stockCreateCylinder: self.stockCreateCylinder = PathJobGui.StockCreateCylinderEdit( self.obj, self.form, force) self.stockEdit = self.stockCreateCylinder def setupFromExisting(): PathLog.track(index, force) if force or not self.stockFromExisting: self.stockFromExisting = PathJobGui.StockFromExistingEdit( self.obj, self.form, force) if self.stockFromExisting.candidates(self.obj): self.stockEdit = self.stockFromExisting return True return False if index == -1: if self.obj.Stock is None or PathJobGui.StockFromBaseBoundBoxEdit.IsStock( self.obj): setupFromBaseEdit() elif PathJobGui.StockCreateBoxEdit.IsStock(self.obj): setupCreateBoxEdit() elif PathJobGui.StockCreateCylinderEdit.IsStock(self.obj): setupCreateCylinderEdit() elif PathJobGui.StockFromExistingEdit.IsStock(self.obj): setupFromExisting() else: PathLog.error( translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label) else: if index == PathJobGui.StockFromBaseBoundBoxEdit.Index: setupFromBaseEdit() elif index == PathJobGui.StockCreateBoxEdit.Index: setupCreateBoxEdit() elif index == PathJobGui.StockCreateCylinderEdit.Index: setupCreateCylinderEdit() elif index == PathJobGui.StockFromExistingEdit.Index: if not setupFromExisting(): setupFromBaseEdit() index = -1 else: PathLog.error( translate('PathJob', "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index)) self.stockEdit.activate(self.obj, index == -1)
def updateUI(self): job = self.job if job.PostProcessor: ppHint = "%s %s %s" % ( job.PostProcessor, job.PostProcessorArgs, job.PostProcessorOutputFile, ) self.dialog.postProcessingHint.setText(ppHint) else: self.dialog.postProcessingGroup.setEnabled(False) self.dialog.postProcessingGroup.setChecked(False) if job.Stock and not PathJob.isResourceClone(job, "Stock", "Stock"): stockType = PathStock.StockType.FromStock(job.Stock) if stockType == PathStock.StockType.FromBase: seHint = translate( "Path_Job", "Base -/+ %.2f/%.2f %.2f/%.2f %.2f/%.2f") % ( job.Stock.ExtXneg, job.Stock.ExtXpos, job.Stock.ExtYneg, job.Stock.ExtYpos, job.Stock.ExtZneg, job.Stock.ExtZpos, ) self.dialog.stockPlacement.setChecked(False) elif stockType == PathStock.StockType.CreateBox: seHint = translate("Path_Job", "Box: %.2f x %.2f x %.2f") % ( job.Stock.Length, job.Stock.Width, job.Stock.Height, ) elif stockType == PathStock.StockType.CreateCylinder: seHint = translate("Path_Job:", "Cylinder: %.2f x %.2f") % ( job.Stock.Radius, job.Stock.Height, ) elif stockType == PathStock.StockType.Unknown: seHint = "-" else: # Existing Solid seHint = "-" PathLog.error(translate("Path_Job", "Unsupported stock type")) self.dialog.stockExtentHint.setText(seHint) spHint = "%s" % job.Stock.Placement self.dialog.stockPlacementHint.setText(spHint) rapidChanged = not job.SetupSheet.Proxy.hasDefaultToolRapids() depthsChanged = not job.SetupSheet.Proxy.hasDefaultOperationDepths() heightsChanged = not job.SetupSheet.Proxy.hasDefaultOperationHeights() coolantChanged = not job.SetupSheet.Proxy.hasDefaultCoolantMode() opsWithSettings = job.SetupSheet.Proxy.operationsWithSettings() settingsChanged = (rapidChanged or depthsChanged or heightsChanged or coolantChanged or 0 != len(opsWithSettings)) self.dialog.settingsGroup.setChecked(settingsChanged) self.dialog.settingToolRapid.setChecked(rapidChanged) self.dialog.settingOperationDepths.setChecked(depthsChanged) self.dialog.settingOperationHeights.setChecked(heightsChanged) self.dialog.settingCoolant.setChecked(coolantChanged) self.dialog.settingsOpsList.clear() for op in opsWithSettings: item = QtGui.QListWidgetItem(op) item.setCheckState(QtCore.Qt.CheckState.Checked) self.dialog.settingsOpsList.addItem(item) self.dialog.toolsList.clear() for tc in sorted(job.Tools.Group, key=lambda o: o.Label): item = QtGui.QListWidgetItem(tc.Label) item.setData(self.DataObject, tc) item.setCheckState(QtCore.Qt.CheckState.Checked) self.dialog.toolsList.addItem(item)
def execute(self, obj): PathLog.track() if not obj.Base: PathLog.error(translate("Path_DressupTag", "No Base object found.")) return if not obj.Base.isDerivedFrom("Path::Feature"): PathLog.error( translate("Path_DressupTag", "Base is not a Path::Feature object.")) return if not obj.Base.Path: PathLog.error( translate("Path_DressupTag", "Base doesn't have a Path to dress-up.")) return if not obj.Base.Path.Commands: PathLog.error(translate("Path_DressupTag", "Base Path is empty.")) return self.obj = obj minZ = +MaxInt minX = minZ minY = minZ maxZ = -MaxInt maxX = maxZ maxY = maxZ # the assumption is that all helixes are in the xy-plane - in other words there is no # intermittent point of a command that has a lower/higher Z-position than the start and # and end positions of a command. lastPt = FreeCAD.Vector(0, 0, 0) for cmd in obj.Base.Path.Commands: pt = PathGeom.commandEndPoint(cmd, lastPt) if lastPt.x != pt.x: maxX = max(pt.x, maxX) minX = min(pt.x, minX) if lastPt.y != pt.y: maxY = max(pt.y, maxY) minY = min(pt.y, minY) if lastPt.z != pt.z: maxZ = max(pt.z, maxZ) minZ = min(pt.z, minZ) lastPt = pt PathLog.debug("bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" % (minX, minY, minZ, maxX, maxY, maxZ)) self.ptMin = FreeCAD.Vector(minX, minY, minZ) self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ) self.masterSolid = TagSolid(self, minZ, self.toolRadius()) self.solids = [ self.masterSolid.cloneAt(pos) for pos in self.obj.Positions ] self.tagSolid = Part.Compound(self.solids) self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) # pylint: disable=unused-variable self.edges = self.wire.Edges maxTagZ = minZ + obj.Height.Value # lastX = 0 # lastY = 0 lastZ = 0 commands = [] for cmd in obj.Base.Path.Commands: if cmd in PathGeom.CmdMove: if lastZ <= maxTagZ or cmd.Parameters.get("Z", lastZ) <= maxTagZ: pass else: commands.append(cmd) else: commands.append(cmd) obj.Path = obj.Base.Path PathLog.track()
def getPath(self): """getPath() ... Call this method on an instance of the class to generate and return path data for the requested path array.""" if len(self.baseList) == 0: PathLog.error( translate("PathArray", "No base objects for PathArray.")) return None base = self.baseList for b in base: if not b.isDerivedFrom("Path::Feature"): return if not b.Path: return if not b.ToolController: return if b.ToolController != base[0].ToolController: # this may be important if Job output is split by tool controller PathLog.warning( translate( "PathArray", "Arrays of paths having different tool controllers are handled according to the tool controller of the first path.", )) # build copies output = "" random.seed(self.seed) if self.arrayType == "Linear1D": for i in range(self.copies): pos = FreeCAD.Vector( self.offsetVector.x * (i + 1), self.offsetVector.y * (i + 1), self.offsetVector.z * (i + 1), ) pos = self._calculateJitter(pos) for b in base: pl = FreeCAD.Placement() pl.move(pos) np = Path.Path( [cm.transform(pl) for cm in b.Path.Commands]) output += np.toGCode() elif self.arrayType == "Linear2D": if self.swapDirection: for i in range(self.copiesY + 1): for j in range(self.copiesX + 1): if (i % 2) == 0: pos = FreeCAD.Vector( self.offsetVector.x * j, self.offsetVector.y * i, self.offsetVector.z * i, ) else: pos = FreeCAD.Vector( self.offsetVector.x * (self.copiesX - j), self.offsetVector.y * i, self.offsetVector.z * i, ) pos = self._calculateJitter(pos) for b in base: pl = FreeCAD.Placement() # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): pl.move(pos) np = Path.Path([ cm.transform(pl) for cm in b.Path.Commands ]) output += np.toGCode() else: for i in range(self.copiesX + 1): for j in range(self.copiesY + 1): if (i % 2) == 0: pos = FreeCAD.Vector( self.offsetVector.x * i, self.offsetVector.y * j, self.offsetVector.z * i, ) else: pos = FreeCAD.Vector( self.offsetVector.x * i, self.offsetVector.y * (self.copiesY - j), self.offsetVector.z * i, ) pos = self._calculateJitter(pos) for b in base: pl = FreeCAD.Placement() # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): pl.move(pos) np = Path.Path([ cm.transform(pl) for cm in b.Path.Commands ]) output += np.toGCode() # Eif else: for i in range(self.copies): for b in base: ang = 360 if self.copies > 0: ang = self.angle / self.copies * (1 + i) np = self.rotatePath(b.Path.Commands, ang, self.centre) output += np.toGCode() # return output return Path.Path(output)
def scanJob(self, forward): PathLog.track() job = self.mk.getJob() if job and hasattr(job, 'Path') and job.Path: bb = job.Path.BoundBox if bb.isValid(): off = self.mk['status.motion.offset.g5x'] bb.move(FreeCAD.Vector(off['x'], off['y'], off['z'])) if self.mk.boundBox().isInside(bb): mkx = self.displayPos('x') mky = self.displayPos('y') begin = FreeCAD.Vector(mkx, mky, 0) pts = [] bb = job.Path.BoundBox if forward: pts.append(FreeCAD.Vector(bb.XMin, bb.YMin, 0)) pts.append(FreeCAD.Vector(bb.XMax, bb.YMin, 0)) pts.append(FreeCAD.Vector(bb.XMax, bb.YMax, 0)) pts.append(FreeCAD.Vector(bb.XMin, bb.YMax, 0)) else: pts.append(FreeCAD.Vector(bb.XMin, bb.YMin, 0)) pts.append(FreeCAD.Vector(bb.XMin, bb.YMax, 0)) pts.append(FreeCAD.Vector(bb.XMax, bb.YMax, 0)) pts.append(FreeCAD.Vector(bb.XMax, bb.YMin, 0)) dist = [begin.distanceToPoint(p) for p in pts] rot = dist.index(min(dist)) pts = pts[rot:] + pts[:rot] pts.append(pts[0]) PathLog.info(" begin = (%5.2f, %5.2f)" % (begin.x, begin.y)) for i, p in enumerate(pts): PathLog.info(" pts[%d] = (%5.2f, %5.2f)" % (i, p.x, p.y)) jog = [] if not PathGeom.pointsCoincide(begin, pts[0]): PathLog.info("Move to start point (%.2f, %.2f)" % (pts[0].x, pts[0].y)) jog.append(self._jogXYCmdsFromTo(begin, pts[0])) for i, j in zip(pts, pts[1:]): jog.append(self._jogXYCmdsFromTo(i, j)) sequence = [[cmd] for cmd in MKUtils.taskModeManual(self.mk)] sequence.extend(jog) self.mk['command'].sendCommandSequence(sequence) else: mbb = self.mk.boundBox() msg = ["Cannot scan job!"] if mbb.XMin > bb.XMin: msg.append("X limit min exceeded by: %.2f" % (mbb.XMin - bb.XMin)) if mbb.XMax < bb.XMax: msg.append("X limit max exceeded by: %.2f" % (bb.XMax - mbb.XMax)) if mbb.YMin > bb.YMin: msg.append("Y limit min exceeded by: %.2f" % (mbb.YMin - bb.YMin)) if mbb.YMax < bb.YMax: msg.append("Y limit max exceeded by: %.2f" % (bb.YMax - mbb.YMax)) if mbb.ZMin > bb.ZMin: msg.append("Z limit min exceeded by: %.2f" % (mbb.ZMin - bb.ZMin)) if mbb.ZMax < bb.ZMax: msg.append("Z limit max exceeded by: %.2f" % (bb.ZMax - mbb.ZMax)) mb = PySide.QtGui.QMessageBox() mb.setWindowIcon( machinekit.IconResource('machinekiticon.png')) mb.setWindowTitle('Machinekit') mb.setTextFormat(PySide.QtCore.Qt.TextFormat.RichText) mb.setText("<div align='center'>%s</div>" % '<br/>'.join(msg)) mb.setIcon(PySide.QtGui.QMessageBox.Critical) mb.setStandardButtons(PySide.QtGui.QMessageBox.Ok) mb.exec_() else: PathLog.error("BoundBox of job %s is not valid" % job.Label) else: PathLog.error('No job uploaded for scanning')
def opExecute(self, obj): '''opExecute(obj) ... process engraving operation''' PathLog.track() job = PathUtils.findParentJob(obj) if job and job.Base: obj.BaseObject = job.Base zValues = [] if obj.StepDown.Value != 0: z = obj.StartDepth.Value - obj.StepDown.Value while z > obj.FinalDepth.Value: zValues.append(z) z -= obj.StepDown.Value zValues.append(obj.FinalDepth.Value) self.zValues = zValues try: if self.baseobject.isDerivedFrom('Sketcher::SketchObject') or \ self.baseobject.isDerivedFrom('Part::Part2DObject') or \ hasattr(self.baseobject, 'ArrayType'): PathLog.track() self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) # we only consider the outer wire if this is a Face wires = [] for w in self.baseobject.Shape.Wires: wires.append(Part.Wire(w.Edges)) self.buildpathocc(obj, wires, zValues) self.wires = wires elif isinstance(self.baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet PathLog.track() wires = [] for tag in self.baseobject.Proxy.getTags(self.baseobject, transform=True): self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) tagWires = [] for w in tag.Wires: tagWires.append(Part.Wire(w.Edges)) self.buildpathocc(obj, tagWires, zValues) wires.extend(tagWires) self.wires = wires elif obj.Base: PathLog.track() wires = [] for base, subs in obj.Base: #edges = [] #for sub in subs: # edges.extend(base.Shape.getElement(sub).Edges) #shapeWires = adjustWirePlacement(obj, base, TechDraw.edgeWalker(edges)) #wires.extend(shapeWires) for feature in subs: sub = base.Shape.getElement(feature) if sub.Wires: shapeWires = sub.Wires else: shapeWires = [Part.Wire(sub.Edges)] wires.extend(adjustWirePlacement(obj, base, shapeWires)) self.buildpathocc(obj, wires, zValues) self.wires = wires elif not obj.BaseShapes: PathLog.track() raise ValueError(translate('PathEngrave', "Unknown baseobject type for engraving (%s)") % (obj.Base)) if obj.BaseShapes: PathLog.track() wires = [] for shape in obj.BaseShapes: shapeWires = adjustWirePlacement(obj, shape, shape.Shape.Wires) self.buildpathocc(obj, shapeWires, zValues) wires.extend(shapeWires) self.wires = wires except Exception as e: PathLog.error(e) traceback.print_exc() PathLog.error(translate('PathEngrave', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.'))
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( "Pocket does not support shape {}.{}".format( 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( "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 areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("areaOpShapes() in PathPocketShape.py") def judgeFinalDepth(obj, fD): if obj.FinalDepth.Value >= fD: return obj.FinalDepth.Value else: return fD def judgeStartDepth(obj, sD): if obj.StartDepth.Value >= sD: return obj.StartDepth.Value else: return sD def analyzeVerticalFaces(self, obj, vertTuples): hT = [] # base = FreeCAD.ActiveDocument.getObject(self.modelName) # Separate elements, regroup by orientation (axis_angle combination) vTags = ['X34.2'] vGrps = [[(2.3, 3.4, 'X')]] for tup in vertTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in vTags: # Determine index of found string i = 0 for orn in vTags: if orn == tag: break i += 1 vGrps[i].append(tup) else: vTags.append(tag) # add orientation entry vGrps.append([tup]) # add orientation entry # Remove temp elements vTags.pop(0) vGrps.pop(0) # check all faces in each axis_angle group shpList = [] zmaxH = 0.0 for o in range(0, len(vTags)): shpList = [] zmaxH = vGrps[o][0].BoundBox.ZMax for (face, sub, angle, axis, tag, strDep, finDep, trans) in vGrps[o]: shpList.append(face) # Identify tallest face to use as zMax if face.BoundBox.ZMax > zmaxH: zmaxH = face.BoundBox.ZMax # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop self.vertical = PathGeom.combineConnectedShapes(shpList) 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.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring') ) else: strDep = zmaxH + self.leadIn # base.Shape.BoundBox.ZMax finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, sub, angle, axis, tag, strDep, finDep, trans hT.append(tup) # Eol return hT if obj.Base: PathLog.debug('base items exist. Processing...') self.removalshapes = [] self.horiz = [] vertical = [] horizTuples = [] vertTuples = [] axis = 'X' angle = 0.0 reset = False resetPlacement = None trans = FreeCAD.Vector(0.0, 0.0, 0.0) for o in obj.Base: PathLog.debug('Base item: {}'.format(o)) base = o[0] # Limit sub faces to children of single Model object. if self.modelName is None: self.modelName = base.Name else: if base.Name != self.modelName: for sub in o[1]: PathLog.error(sub + " is not a part of Model object: " + self.modelName) o[1] = [] PathLog.error( "Only processing faces on a single Model object per operation." ) PathLog.error( "You will need to separate faces per Model object within the Job." ) startBase = FreeCAD.Vector(base.Placement.Base.x, base.Placement.Base.y, base.Placement.Base.z) startAngle = base.Placement.Rotation.Angle startAxis = base.Placement.Rotation.Axis startRotation = FreeCAD.Rotation(startAxis, startAngle) resetPlacement = FreeCAD.Placement(startBase, startRotation) for sub in o[1]: if 'Face' in sub: PathLog.debug('sub: {}'.format(sub)) # Determine angle of rotation needed to make normal vector = (0,0,1) strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value trans = FreeCAD.Vector(0.0, 0.0, 0.0) rtn = False if obj.UseRotation != 'Off': (rtn, angle, axis) = self.pocketRotationAnalysis(obj, base, sub, prnt=True) if rtn is True: reset = True PathLog.debug( str(sub) + ": rotating model to make face normal at (0,0,1) ..." ) if axis == 'X': bX = 0.0 bY = 0.0 bZ = math.sin(math.radians( angle)) * base.Placement.Base.y vect = FreeCAD.Vector(1, 0, 0) elif axis == 'Y': bX = 0.0 bY = 0.0 bZ = math.sin(math.radians( angle)) * base.Placement.Base.x if obj.B_AxisErrorOverride is True: bZ = -1 * bZ vect = FreeCAD.Vector(0, 1, 0) # Rotate base to such that Surface.Axis of pocket bottom is Z=1 base.Placement.Rotation = FreeCAD.Rotation( vect, angle) base.recompute() trans = FreeCAD.Vector(bX, bY, bZ) else: axis = 'X' angle = 0.0 tag = axis + str(round(angle, 7)) face = base.Shape.getElement(sub) if type(face.Surface ) == Part.Plane and PathGeom.isVertical( face.Surface.Axis): # it's a flat horizontal face PathLog.debug(" == Part.Plane: isVertical") # Adjust start and finish depths for pocket strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans horizTuples.append(tup) elif type(face.Surface ) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis): PathLog.debug("== 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)) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth( obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = disk, sub, angle, axis, tag, strDep, finDep, trans horizTuples.append(tup) else: # partial cylinder wall vertical.append(face) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth( obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans vertTuples.append(tup) PathLog.debug(sub + "is vertical after rotation.") elif type(face.Surface ) == Part.Plane and PathGeom.isHorizontal( face.Surface.Axis): vertical.append(face) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans vertTuples.append(tup) PathLog.debug(sub + "is vertical after rotation.") else: PathLog.error( translate( 'PathPocket', 'Pocket does not support shape %s.%s') % (base.Label, sub)) if reset is True: base.Placement.Rotation = startRotation base.recompute() reset = False # End IF # End FOR base.Placement = resetPlacement base.recompute() # End FOR # Analyze vertical faces via PathGeom.combineConnectedShapes() # hT = analyzeVerticalFaces(self, obj, vertTuples) # horizTuples.extend(hT) # This section will be replaced analyzeVerticalFaces(self, obj, vertTuples) above self.vertical = PathGeom.combineConnectedShapes(vertical) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0.0, 0.0, 1.0)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring')) else: # self.horiz.append(face) strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, 'vertFace', 0.0, 'X', 'X0.0', strDep, finDep, FreeCAD.Vector( 0.0, 0.0, 0.0) horizTuples.append(tup) # add faces for extensions self.exts = [] for ext in self.getExtensions(obj): wire = Part.Face(ext.getWire()) if wire: face = Part.Face(wire) # self.horiz.append(face) strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, 'vertFace', 0.0, 'X', 'X0.0', strDep, finDep, FreeCAD.Vector( 0.0, 0.0, 0.0) horizTuples.append(tup) self.exts.append(face) # move all horizontal faces to FinalDepth for (face, sub, angle, axis, tag, strDep, finDep, trans) in horizTuples: # face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - face.BoundBox.ZMin)) if angle <= 0.0: if axis == 'X': face.translate( FreeCAD.Vector( 0, trans.z, trans.z + finDep - face.BoundBox.ZMin)) elif axis == 'Y': face.translate( FreeCAD.Vector( -1 * trans.z, 0, trans.z + finDep - face.BoundBox.ZMin)) else: if axis == 'X': face.translate( FreeCAD.Vector( 0, -1 * trans.z, trans.z + finDep - face.BoundBox.ZMin)) elif axis == 'Y': face.translate( FreeCAD.Vector( trans.z, 0, trans.z + finDep - face.BoundBox.ZMin)) # Separate elements, regroup by orientation (axis_angle combination) hTags = ['X34.2'] hGrps = [[(2.3, 3.4, 'X')]] for tup in horizTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in hTags: # Determine index of found string i = 0 for orn in hTags: if orn == tag: break i += 1 hGrps[i].append(tup) else: hTags.append(tag) # add orientation entry hGrps.append([tup]) # add orientation entry # Remove temp elements hTags.pop(0) hGrps.pop(0) # check all faces in each axis_angle group self.horizontal = [] shpList = [] for o in range(0, len(hTags)): PathLog.debug('hTag: {}'.format(hTags[o])) shpList = [] for (face, sub, angle, axis, tag, strDep, finDep, trans) in hGrps[o]: shpList.append(face) # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop for shape in PathGeom.combineConnectedShapes(shpList): shape.sewShape() # shape.tessellate(0.05) # Russ4262 0.1 original if obj.UseOutline: wire = TechDraw.findShapeOutline( shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate( FreeCAD.Vector( 0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) PathLog.debug( " -obj.UseOutline: obj.FinalDepth.Value" + str(obj.FinalDepth.Value)) PathLog.debug(" -obj.UseOutline: wire.BoundBox.ZMin" + str(wire.BoundBox.ZMin)) # shape.tessellate(0.05) # Russ4262 0.1 original face = Part.Face(wire) tup = face, sub, angle, axis, tag, strDep, finDep, trans self.horizontal.append(tup) else: # Re-pair shape to tuple set for (face, sub, angle, axis, tag, strDep, finDep, trans) in hGrps[o]: if shape is face: tup = face, sub, angle, axis, tag, strDep, finDep, trans self.horizontal.append(tup) break # Eol # extrude all faces up to StartDepth and those are the removal shapes for (face, sub, angle, axis, tag, strDep, finDep, trans) in self.horizontal: # extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) extent = FreeCAD.Vector(0, 0, strDep - finDep) shp = face.removeSplitter().extrude(extent) # tup = shp, False, sub, angle, axis, tag, strDep, finDep, trans tup = shp, False, sub, angle, axis # shape, isHole, sub, angle, axis self.removalshapes.append(tup) 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 PathLog.debug(" -Using outlines; no obj.Base") self.removalshapes = [] 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, 'outline', 0.0, 'X')) for (shape, isHole, sub, angle, axis) in self.removalshapes: shape.tessellate(0.05) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
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 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 = [] 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: for (base, subsList, angle, axis, stock) in baseSubsTuples: holes = [] faces = [] 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)) 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) # 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): PathLog.track() # Recalculate depthparams (strDep, finDep) = self.calculateStartFinalDepths( obj, shape, stock) finalDepths.append(finDep) PathLog.debug( "Adjusted face depths strDep: {}, and finDep: {}". format(self.strDep, self.finDep)) finish_step = obj.FinishDepth.Value if hasattr( obj, "FinishDepth") else 0.0 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) 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() if profileshape: # Recalculate depthparams (strDep, finDep) = self.calculateStartFinalDepths( obj, profileshape, stock) finalDepths.append(finDep) PathLog.debug( "Adjusted face depths strDep: {}, and finDep: {}". format(self.strDep, self.finDep)) finish_step = obj.FinishDepth.Value if hasattr( obj, "FinishDepth") else 0.0 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) else: strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value 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: # Recalculate depthparams (strDep, finDep) = self.calculateStartFinalDepths( obj, shape, stock) finalDepths.append(finDep) finish_step = obj.FinishDepth.Value if hasattr( obj, "FinishDepth") else 0.0 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) env = PathUtils.getEnvelope( base.Shape, subshape=shape, depthparams=self.depthparams) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) # Eif # adjust FinalDepth as needed finalDepth = min(finalDepths) if obj.FinalDepth.Value < finalDepth: obj.FinalDepth.Value = finalDepth 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)) 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 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.strG0ZsafeHeight = Path.Command( # was a Feed rate with G1 "G0", {"Z": self.safeHeight, "F": tc.VertRapid.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: 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)