def updateDepths(self, obj, ignoreErrors=False): '''updateDepths(obj) ... base implementation calculating depths depending on base geometry. Should not be overwritten.''' def faceZmin(bb, fbb): if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face return fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut return fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall return fbb.ZMin elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf return fbb.ZMin return bb.ZMin if not self._setBaseAndStock(obj, ignoreErrors): return False stockBB = self.stock.Shape.BoundBox zmin = stockBB.ZMin zmax = stockBB.ZMax obj.OpStockZMin = zmin obj.OpStockZMax = zmax if hasattr(obj, 'Base') and obj.Base: for base, sublist in obj.Base: bb = base.Shape.BoundBox zmax = max(zmax, bb.ZMax) for sub in sublist: try: fbb = base.Shape.getElement(sub).BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) except Part.OCCError as e: PathLog.error(e) else: # clearing with stock boundaries job = PathUtils.findParentJob(obj) zmax = stockBB.ZMax zmin = job.Proxy.modelBoundBox(job).ZMax if FeatureDepths & self.opFeatures(obj): # first set update final depth, it's value is not negotiable if not PathGeom.isRoughly(obj.OpFinalDepth.Value, zmin): obj.OpFinalDepth = zmin zmin = obj.OpFinalDepth.Value def minZmax(z): if hasattr(obj, 'StepDown') and not PathGeom.isRoughly( obj.StepDown.Value, 0): return z + obj.StepDown.Value else: return z + 1 # ensure zmax is higher than zmin if (zmax - 0.0001) <= zmin: zmax = minZmax(zmin) # update start depth if requested and required if not PathGeom.isRoughly(obj.OpStartDepth.Value, zmax): obj.OpStartDepth = zmax else: # every obj has a StartDepth if obj.StartDepth.Value != zmax: obj.StartDepth = zmax self.opUpdateDepths(obj)
def test20(self): '''Start and end are equal or start lower than finish ''' clearance_height = 15 rapid_safety_space = 12 start_depth = 10 step_down = 2 z_finish_step = 0 final_depth = 10 user_depths = None expected = [] d = PU.depth_params(clearance_height, rapid_safety_space, start_depth, step_down, z_finish_step, final_depth, user_depths) r = d.get_depths() self.assertListEqual(r, expected) start_depth = 10 final_depth = 15 expected = [] d = PU.depth_params(clearance_height, rapid_safety_space, start_depth, step_down, z_finish_step, final_depth, user_depths) r = d.get_depths() self.assertListEqual(r, expected)
def setDefaultValues(self, obj): '''setDefaultValues(obj) ... base implementation. Do not overwrite, overwrite opSetDefaultValues() instead.''' PathUtils.addToJob(obj) obj.Active = True features = self.opFeatures(obj) if FeatureTool & features: obj.ToolController = PathUtils.findToolController(obj) if FeatureDepths & features: obj.StartDepth = 1.0 obj.FinalDepth = 0.0 if FeatureStepDown & features: obj.StepDown = 1.0 if FeatureHeights & features: obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 if FeatureStartPoint & features: obj.UseStartPoint = False self.opSetDefaultValues(obj)
def execute(self, obj): output = "" toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " (" + obj.ToolDescription + ")" else: obj.Label = obj.UserLabel + " (" + obj.ToolDescription + ")" output += "(remote gcode goes here)" path = Path.Path(output) obj.Path = path
def test20(self): '''Start and end are equal or start lower than finish ''' clearance_height= 15 safe_height = 12 start_depth = 10 step_down = 2 z_finish_step = 0 final_depth = 10 user_depths = None expected =[10] d = PU.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) r = [i for i in d] self.assertListEqual (r, expected) start_depth = 10 final_depth = 15 expected =[] d = PU.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) r = [i for i in d] self.assertListEqual (r, expected)
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") removalshapes = [] for b in obj.Base: PathLog.debug("Base item: {}".format(b)) for sub in b[1]: if "Face" in sub: shape = Part.makeCompound([getattr(b[0].Shape, sub)]) else: edges = [getattr(b[0].Shape, sub) for sub in b[1]] shape = Part.makeFace(edges, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(self.baseobject.Shape, subshape=shape, depthparams=self.depthparams) obj.removalshape = env.cut(self.baseobject.Shape) removalshapes.append((obj.removalshape, False)) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") env = PathUtils.getEnvelope(self.baseobject.Shape, subshape=None, depthparams=self.depthparams) obj.removalshape = env.cut(self.baseobject.Shape) removalshapes = [(obj.removalshape, False)] return removalshapes
def findHoles(self, obj, shape): import DraftGeomUtils as dgu PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] tooldiameter = obj.ToolController.Proxy.getTool(obj.ToolController).Diameter PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter)) if dgu.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode())) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): PathLog.debug('face candidate: {} is drillable '.format(f)) x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) PathLog.debug("holes found: {}".format(holelist)) return holelist
def drawLoops(loops): nloop = 0 waterlinestring = "" waterlinestring += "(waterline begin)" for loop in loops: p = loop[0] loopstring = "(loop begin)" + "\n" loopstring += "G0 Z" + str(obj.SafeHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" loopstring += "G0 X" + \ str(fmt(p.x)) + " Y" + str(fmt(p.y)) + "F " + PathUtils.fmt(self.horizRapid) + "\n" loopstring += "G1 Z" + str(fmt(p.z)) + "\n" for p in loop[1:]: loopstring += "G1 X" + \ str(fmt(p.x)) + " Y" + str(fmt(p.y)) + \ " Z" + str(fmt(p.z)) + "\n" zheight = p.z p = loop[0] loopstring += "G1 X" + \ str(fmt(p.x)) + " Y" + str(fmt(p.y)) + \ " Z" + str(fmt(zheight)) + "\n" loopstring += "(loop end)" + "\n" print " loop ", nloop, " with ", len(loop), " points" nloop = nloop + 1 waterlinestring += loopstring waterlinestring += "(waterline end)" + "\n" return waterlinestring
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] for b in obj.Base: for sub in b[1]: shape = getattr(b[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) else: PathLog.debug('The base subobject is not a face') return planeshape = Part.makeCompound(faces) PathLog.debug("Working on a collection of faces {}".format(faces)) # If no base object, do planing of top surface of entire model else: planeshape = self.baseobject.Shape PathLog.debug("Working on a shape {}".format(self.baseobject.Name)) # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) elif obj.BoundaryShape == 'Stock': stock = PathUtils.findParentJob(obj).Stock.Shape env = stock else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) return [(env, False)]
def setDefaultValues(self, obj): '''setDefaultValues(obj) ... base implementation. Do not overwrite, overwrite opSetDefaultValues() instead.''' job = PathUtils.addToJob(obj) obj.Active = True features = self.opFeatures(obj) if FeatureTool & features: obj.ToolController = PathUtils.findToolController(obj) obj.OpToolDiameter = 1.0 if FeatureDepths & features: obj.setExpression('StartDepth', job.SetupSheet.StartDepthExpression) obj.setExpression('FinalDepth', job.SetupSheet.FinalDepthExpression) obj.OpStartDepth = 1.0 obj.OpFinalDepth = 0.0 if FeatureStepDown & features: obj.setExpression('StepDown', job.SetupSheet.StepDownExpression) if FeatureHeights & features: if job.SetupSheet.SafeHeightExpression: obj.setExpression('SafeHeight', job.SetupSheet.SafeHeightExpression) if job.SetupSheet.ClearanceHeightExpression: obj.setExpression('ClearanceHeight', job.SetupSheet.ClearanceHeightExpression) if FeatureStartPoint & features: obj.UseStartPoint = False self.opSetDefaultValues(obj) obj.recompute()
def setup(self, obj): debugPrint("Here we go ... ") if hasattr(obj.Base, "BoneBlacklist"): # dressing up a bone dressup obj.Side = obj.Base.Side else: # otherwise dogbones are opposite of the base path's side if obj.Base.Side == Side.Left: obj.Side = Side.Right elif obj.Base.Side == Side.Right: obj.Side = Side.Left else: # This will cause an error, which is fine for now 'cause I don't know what to do here obj.Side = 'On' self.toolRadius = 5 toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.toolRadius = 5 else: tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if not tool or tool.Diameter == 0: self.toolRadius = 5 else: self.toolRadius = tool.Diameter / 2 self.shapes = {} self.dbg = []
def setFields(self): self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) self.form.finalDepth.setText(FreeCAD.Units.Quantity(self.obj.FinalDepth.Value, FreeCAD.Units.Length).UserString) self.form.finishDepth.setText(FreeCAD.Units.Quantity(self.obj.FinishDepth.Value, FreeCAD.Units.Length).UserString) self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown, FreeCAD.Units.Length).UserString) self.form.safeHeight.setText(FreeCAD.Units.Quantity(self.obj.SafeHeight.Value, FreeCAD.Units.Length).UserString) self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] self.form.uiToolController.blockSignals(True) self.form.uiToolController.addItems(labels) self.form.uiToolController.blockSignals(False) if self.obj.ToolController is not None: index = self.form.uiToolController.findText( self.obj.ToolController.Label, QtCore.Qt.MatchFixedString) PathLog.debug("searching for TC label {}. Found Index: {}".format(self.obj.ToolController.Label, index)) if index >= 0: self.form.uiToolController.blockSignals(True) self.form.uiToolController.setCurrentIndex(index) self.form.uiToolController.blockSignals(False) else: self.obj.ToolController = PathUtils.findToolController(self.obj) for i in self.obj.Base: self.form.baseList.addItem(i[0].Name) index = self.form.algorithmSelect.findText( self.obj.Algorithm, QtCore.Qt.MatchFixedString) if index >= 0: self.form.algorithmSelect.blockSignals(True) self.form.algorithmSelect.setCurrentIndex(index) self.form.algorithmSelect.blockSignals(False)
def execute(self, obj): import Part # math #DraftGeomUtils output = "" toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.vertRapid = 100 self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if not tool or tool.Diameter == 0: self.radius = 0.25 else: self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name self.setLabel(obj) output += "(" + obj.Label + ")" if not obj.UseComp: output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")" else: output += "(Uncompensated Tool Path)" parentJob = PathUtils.findParentJob(obj) if parentJob is None: return baseobject = parentJob.Base if baseobject is None: return contourwire = TechDraw.findShapeOutline(baseobject.Shape,1, Vector(0,0,1)) edgelist = contourwire.Edges edgelist = Part.__sortEdges__(edgelist) try: output += self._buildPathLibarea(obj, edgelist) except: FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") if obj.Active: path = Path.Path(output) obj.Path = path if obj.ViewObject: obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def _dropcutter(self, obj, s, bb): import ocl import time cutter = ocl.CylCutter(self.radius * 2, 5) pdc = ocl.PathDropCutter() # create a pdc pdc.setSTL(s) pdc.setCutter(cutter) pdc.minimumZ = 0.25 pdc.setSampling(obj.SampleInterval) # some parameters for this "zigzig" pattern xmin = bb.XMin - cutter.getDiameter() xmax = bb.XMax + cutter.getDiameter() ymin = bb.YMin - cutter.getDiameter() ymax = bb.YMax + cutter.getDiameter() # number of lines in the y-direction Ny = int(bb.YLength / cutter.getDiameter()) dy = float(ymax - ymin) / Ny # the y step-over path = ocl.Path() # create an empty path object # add Line objects to the path in this loop for n in xrange(0, Ny): y = ymin + n * dy p1 = ocl.Point(xmin, y, 0) # start-point of line p2 = ocl.Point(xmax, y, 0) # end-point of line if (n % 2 == 0): # even l = ocl.Line(p1, p2) # line-object else: # odd l = ocl.Line(p2, p1) # line-object path.append(l) # add the line to the path pdc.setPath(path) # run drop-cutter on the path t_before = time.time() pdc.run() t_after = time.time() print "calculation took ", t_after - t_before, " s" # retrieve the points clp = pdc.getCLPoints() print "points received: " + str(len(clp)) # generate the path commands output = "" output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" output += "G0 X" + str(clp[0].x) + " Y" + str(clp[0].y) + "F " + PathUtils.fmt(self.horizRapid) + "\n" output += "G1 Z" + str(clp[0].z) + " F" + str(self.vertFeed) + "\n" for c in clp: output += "G1 X" + str(c.x) + " Y" + \ str(c.y) + " Z" + str(c.z) + "\n" return output
def execute(self,obj): if obj.Base: # tie the toolnumber to the PathLoadTool object ToolNumber if len(obj.InList)>0: #check to see if obj is in the Project group yet project = obj.InList[0] tl = int(PathUtils.changeTool(obj,project)) obj.ToolNumber= tl tool = PathUtils.getTool(obj,obj.ToolNumber) if tool: self.radius = tool.Diameter/2 else: # temporary value,in case we don't have any tools defined already self.radius = 0.25 # self.radius = 0.25 self.clearance = obj.ClearanceHeight.Value self.step_down=obj.StepDown.Value self.start_depth=obj.StartDepth.Value self.final_depth=obj.FinalDepth.Value self.rapid_safety_space=obj.RetractHeight.Value self.side=obj.Side self.offset_extra=obj.OffsetExtra.Value self.use_CRC=obj.UseComp self.vf=obj.VertFeed.Value self.hf=obj.HorizFeed.Value edgelist = [] if obj.StartPtName and obj.UseStartPt: self.startpt = FreeCAD.ActiveDocument.getObject(obj.StartPtName).Shape else: self.startpt = None if obj.EndPtName and obj.UseEndPt: self.endpt = FreeCAD.ActiveDocument.getObject(obj.EndPtName).Shape else: self.endpt = None for e in obj.Edgelist: edgelist.append(FreeCAD.ActiveDocument.getObject(obj.Base[0].Name).Shape.Edges[e-1]) output=PathKurveUtils.makePath(edgelist,self.side,self.radius,self.vf,self.hf,self.offset_extra, \ self.rapid_safety_space,self.clearance,self.start_depth,self.step_down, \ self.final_depth,self.use_CRC,obj.Direction,self.startpt,self.endpt) if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def areaOpSetDefaultValues(self, obj): '''areaOpSetDefaultValues(obj) ... initialize mill facing properties''' # obj.StepOver = 50 # obj.ZigZagAngle = 45.0 # need to overwrite the default depth calculations for facing job = PathUtils.findParentJob(obj) if job and job.Base: d = PathUtils.guessDepths(job.Base.Shape, None) obj.OpStartDepth = d.safe_height obj.OpFinalDepth = d.start_depth
def execute(self, obj): output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " :" + obj.ToolDescription else: obj.Label = obj.UserLabel + " :" + obj.ToolDescription if obj.Base: output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value)+"\n" wires = [] for o in obj.Base: # we only consider the outer wire if this is a Face for w in o[0].Shape.Wires: tempedges = PathUtils.cleanedges(w.Edges, 0.5) wires.append (Part.Wire(tempedges)) if obj.Algorithm == "OCC Native": output += self.buildpathocc(obj, wires) output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value)+"\n" # print output if output == "": output += "(No commands processed)" if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def setupToolController(self, obj, combo): '''setupToolController(obj, combo) ... helper function to setup obj's ToolController in the given combo box.''' controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] combo.blockSignals(True) combo.addItems(labels) combo.blockSignals(False) if obj.ToolController is None: obj.ToolController = PathUtils.findToolController(obj) if obj.ToolController is not None: self.selectInComboBox(obj.ToolController.Label, combo)
def opSetDefaultValues(self, obj, job): '''opSetDefaultValues(obj, job) ... initialize defaults''' # obj.ZigZagAngle = 45.0 obj.StepOver = 50 obj.Optimize = True # need to overwrite the default depth calculations for facing job = PathUtils.findParentJob(obj) if job and job.Stock: d = PathUtils.guessDepths(job.Stock.Shape, None) obj.OpStartDepth = d.start_depth obj.OpFinalDepth = d.final_depth
def opExecute(self, obj, getsim=False): '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() self.endVector = None 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=obj.StartDepth.Value, step_down=obj.StepDown.Value, z_finish_step=finish_step, final_depth=obj.FinalDepth.Value, user_depths=None) if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None shapes = self.areaOpShapes(obj) jobs = [{ 'x': s[0].BoundBox.XMax, 'y': s[0].BoundBox.YMax, 'shape': s } for s in shapes] jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] sims = [] for (shape, isHole) in shapes: try: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) self.commandlist.extend(pp.Commands) sims.append(sim) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.") if self.areaOpRetractTool(obj): self.endVector = None return sims
def setFields(self): self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) self.form.finalDepth.setText(FreeCAD.Units.Quantity(self.obj.FinalDepth.Value, FreeCAD.Units.Length).UserString) self.form.safeHeight.setText(FreeCAD.Units.Quantity(self.obj.SafeHeight.Value, FreeCAD.Units.Length).UserString) self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString) self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) self.form.rollRadius.setText(FreeCAD.Units.Quantity(self.obj.RollRadius.Value, FreeCAD.Units.Length).UserString) self.form.useCompensation.setChecked(self.obj.UseComp) self.form.useStartPoint.setChecked(self.obj.UseStartPoint) self.form.useEndPoint.setChecked(self.obj.UseEndPoint) self.form.processHoles.setChecked(self.obj.processHoles) self.form.processPerimeter.setChecked(self.obj.processPerimeter) self.form.processCircles.setChecked(self.obj.processCircles) index = self.form.cutSide.findText( self.obj.Side, QtCore.Qt.MatchFixedString) if index >= 0: self.form.cutSide.blockSignals(True) self.form.cutSide.setCurrentIndex(index) self.form.cutSide.blockSignals(False) index = self.form.direction.findText( self.obj.Direction, QtCore.Qt.MatchFixedString) if index >= 0: self.form.direction.blockSignals(True) self.form.direction.setCurrentIndex(index) self.form.direction.blockSignals(False) controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] self.form.uiToolController.blockSignals(True) self.form.uiToolController.addItems(labels) self.form.uiToolController.blockSignals(False) if self.obj.ToolController is not None: index = self.form.uiToolController.findText( self.obj.ToolController.Label, QtCore.Qt.MatchFixedString) PathLog.debug("searching for TC label {}. Found Index: {}".format(self.obj.ToolController.Label, index)) if index >= 0: self.form.uiToolController.blockSignals(True) self.form.uiToolController.setCurrentIndex(index) self.form.uiToolController.blockSignals(False) else: self.obj.ToolController = PathUtils.findToolController(self.obj) self.form.baseList.blockSignals(True) for i in self.obj.Base: for sub in i[1]: self.form.baseList.addItem(i[0].Name + "." + sub) self.form.baseList.blockSignals(False) self.form.update()
def setFields(self): self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) self.form.finalDepth.setText(FreeCAD.Units.Quantity(self.obj.FinalDepth.Value, FreeCAD.Units.Length).UserString) self.form.safeHeight.setText(FreeCAD.Units.Quantity(self.obj.SafeHeight.Value, FreeCAD.Units.Length).UserString) self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown.Value, FreeCAD.Units.Length).UserString) self.form.extraOffset.setText(FreeCAD.Units.Quantity(self.obj.MaterialAllowance, FreeCAD.Units.Length).UserString) self.form.useStartPoint.setChecked(self.obj.UseStartPoint) self.form.useZigZag.setChecked(self.obj.UseZigZag) self.form.zigZagUnidirectional.setChecked(self.obj.ZigUnidirectional) self.form.zigZagAngle.setText(FreeCAD.Units.Quantity(self.obj.ZigZagAngle, FreeCAD.Units.Angle).UserString) self.form.stepOverPercent.setValue(self.obj.StepOver) index = self.form.algorithmSelect.findText(self.obj.Algorithm, QtCore.Qt.MatchFixedString) if index >= 0: self.form.algorithmSelect.blockSignals(True) self.form.algorithmSelect.setCurrentIndex(index) self.form.algorithmSelect.blockSignals(False) index = self.form.cutMode.findText( self.obj.CutMode, QtCore.Qt.MatchFixedString) if index >= 0: self.form.cutMode.blockSignals(True) self.form.cutMode.setCurrentIndex(index) self.form.cutMode.blockSignals(False) # for i in self.obj.Base: # self.form.baseList.addItem(i[0].Name + "." + i[1][0]) self.form.baseList.blockSignals(True) for i in self.obj.Base: for sub in i[1]: self.form.baseList.addItem(i[0].Name + "." + sub) self.form.baseList.blockSignals(False) controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] self.form.uiToolController.blockSignals(True) self.form.uiToolController.addItems(labels) self.form.uiToolController.blockSignals(False) if self.obj.ToolController is not None: index = self.form.uiToolController.findText( self.obj.ToolController.Label, QtCore.Qt.MatchFixedString) PathLog.debug("searching for TC label {}. Found Index: {}".format(self.obj.ToolController.Label, index)) if index >= 0: self.form.uiToolController.blockSignals(True) self.form.uiToolController.setCurrentIndex(index) self.form.uiToolController.blockSignals(False) else: self.obj.ToolController = PathUtils.findToolController(self.obj)
def setFields(self): self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) self.form.finalDepth.setText(FreeCAD.Units.Quantity(self.obj.FinalDepth.Value, FreeCAD.Units.Length).UserString) self.form.finishDepth.setText(FreeCAD.Units.Quantity(self.obj.FinishDepth.Value, FreeCAD.Units.Length).UserString) self.form.stepDown.setText(FreeCAD.Units.Quantity(self.obj.StepDown, FreeCAD.Units.Length).UserString) self.form.safeHeight.setText(FreeCAD.Units.Quantity(self.obj.SafeHeight.Value, FreeCAD.Units.Length).UserString) self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) self.form.stepOverPercent.setValue(self.obj.StepOver) self.form.useZigZag.setChecked(self.obj.UseZigZag) self.form.zigZagUnidirectional.setChecked(self.obj.ZigUnidirectional) self.form.zigZagAngle.setValue(FreeCAD.Units.Quantity(self.obj.ZigZagAngle, FreeCAD.Units.Angle)) self.form.extraOffset.setValue(self.obj.PassExtension.Value) index = self.form.cutMode.findText( self.obj.CutMode, QtCore.Qt.MatchFixedString) if index >= 0: self.form.cutMode.blockSignals(True) self.form.cutMode.setCurrentIndex(index) self.form.cutMode.blockSignals(False) index = self.form.boundaryShape.findText( self.obj.BoundaryShape, QtCore.Qt.MatchFixedString) if index >= 0: self.form.boundaryShape.blockSignals(True) self.form.boundaryShape.setCurrentIndex(index) self.form.boundaryShape.blockSignals(False) for i in self.obj.Base: for sub in i[1]: self.form.baseList.addItem(i[0].Name + "." + sub) controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] self.form.uiToolController.blockSignals(True) self.form.uiToolController.addItems(labels) self.form.uiToolController.blockSignals(False) if self.obj.ToolController is None: self.obj.ToolController = PathUtils.findToolController(self.obj) if self.obj.ToolController is not None: index = self.form.uiToolController.findText( self.obj.ToolController.Label, QtCore.Qt.MatchFixedString) if index >= 0: self.form.uiToolController.blockSignals(True) self.form.uiToolController.setCurrentIndex(index) self.form.uiToolController.blockSignals(False)
def onDelete(self, arg1=None, arg2=None): '''this makes sure that the base operation is added back to the project and visible''' FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True job = PathUtils.findParentJob(arg1.Object) job.Proxy.addOperation(arg1.Object.Base) arg1.Object.Base = None return True
def addBaseGeometry(self, selection): added = False shapes = self.obj.BaseShapes for sel in selection: job = PathUtils.findParentJob(self.obj) base = job.Proxy.resourceClone(job, sel.Object) if not base: PathLog.notice((translate("Path", "%s is not a Base Model object of the job %s")+"\n") % (sel.Object.Label, job.Label)) continue if base in shapes: PathLog.notice((translate("Path", "Base shape %s already in the list")+"\n") % (sel.Object.Label)) continue if base.isDerivedFrom('Part::Part2DObject'): if sel.HasSubObjects: # selectively add some elements of the drawing to the Base for sub in sel.SubElementNames: if 'Vertex' in sub: PathLog.info(translate("Path", "Ignoring vertex")) else: self.obj.Proxy.addBase(self.obj, base, sub) else: # when adding an entire shape to BaseShapes we can take its sub shapes out of Base self.obj.Base = [(p,el) for p,el in self.obj.Base if p != base] shapes.append(base) self.obj.BaseShapes = shapes added = True else: # user wants us to engrave an edge of face of a base model base = self.super().addBaseGeometry(selection) added = added or base return added
def Create(): obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Machine") Machine(obj) _ViewProviderMachine(obj.ViewObject) PathUtils.addToProject(obj) UnitParams = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units") if UnitParams.GetInt('UserSchema') == 0: obj.MachineUnits = 'Metric' #metric mode else: obj.MachineUnits = 'Inch' obj.ViewObject.ShowFirstRapid = False return obj
def _OpenCloseResourceEditor(obj, vobj, edit): job = PathUtils.findParentJob(obj) if job and job.ViewObject and job.ViewObject.Proxy: if edit: job.ViewObject.Proxy.editObject(obj) else: job.ViewObject.Proxy.uneditObject(obj)
def onChanged(self,obj,prop): mode = 2 obj.setEditorMode('Placement',mode) if prop == "PostProcessor": sys.path.append(os.path.split(obj.PostProcessor)[0]) lessextn = os.path.splitext(obj.PostProcessor)[0] postname = os.path.split(lessextn)[1] exec "import %s as current_post" % postname if hasattr (current_post, "UNITS"): if current_post.UNITS == "G21": obj.MachineUnits = "Metric" else: obj.MachineUnits = "Inch" if hasattr (current_post, "MACHINE_NAME"): obj.MachineName = current_post.MACHINE_NAME if hasattr (current_post, "CORNER_MAX"): obj.X_Max = current_post.CORNER_MAX['x'] obj.Y_Max = current_post.CORNER_MAX['y'] obj.Z_Max = current_post.CORNER_MAX['z'] if hasattr (current_post, "CORNER_MIN"): obj.X_Min = current_post.CORNER_MIN['x'] obj.Y_Min = current_post.CORNER_MIN['y'] obj.Z_Min = current_post.CORNER_MIN['z'] if prop == "Tooltable": proj = PathUtils.findProj() for g in proj.Group: if not(isinstance(g.Proxy, PathScripts.PathMachine.Machine)): g.touch()
def _buildPathLibarea(self, obj, edgelist): import PathScripts.PathKurveUtils as PathKurveUtils import math import area output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' if obj.StartPoint and obj.UseStartPoint: startpoint = obj.StartPoint else: startpoint = None if obj.EndPoint and obj.UseEndPoint: endpoint = obj.EndPoint else: endpoint = None PathKurveUtils.output('mem') PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed) output = "" output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" curve = PathKurveUtils.makeAreaCurve(edgelist, obj.Direction, startpoint, endpoint) roll_radius = 2.0 extend_at_start = 0.0 extend_at_end = 0.0 lead_in_line_len = 0.0 lead_out_line_len = 0.0 if obj.UseComp is False: obj.Side = 'On' else: if obj.Direction == 'CW': obj.Side = 'Left' else: obj.Side = 'Right' PathKurveUtils.clear_tags() for i in range(len(obj.locs)): tag = obj.locs[i] h = obj.heights[i] l = obj.lengths[i] a = math.radians(obj.angles[i]) PathKurveUtils.add_tag(area.Point(tag.x, tag.y), l, a, h) depthparams = depth_params( obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown, 0.0, obj.FinalDepth.Value, None) PathKurveUtils.profile2( curve, obj.Side, self.radius, self.vertFeed, self.horizFeed, self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius, None, None, depthparams, extend_at_start, extend_at_end, lead_in_line_len, lead_out_line_len) output += PathKurveUtils.retrieve_gcode() return output
def execute(self,obj): output = "G90 G98\n" # rapid to first hole location, with spindle still retracted: p0 = obj.locations[0] output += "G0 X"+str(p0.x) + " Y" + str(p0.y)+ "\n" # move tool to clearance plane output += "G0 Z" + str(obj.ClearanceHeight.Value) + "\n" if obj.PeckDepth.Value > 0: cmd = "G83" qword = " Q"+ str(obj.PeckDepth.Value) else: cmd = "G81" qword = "" for p in obj.locations: output += cmd + " X" + str(p.x) + " Y" + str(p.y) + " Z" + str(obj.FinalDepth.Value) + qword + " R" + str(obj.RetractHeight.Value) + " F" + str(obj.VertFeed.Value) + "\n" output += "G80\n" print output path = Path.Path(output) obj.Path = path # tie the toolnumber to the PathLoadTool object ToolNumber if len(obj.InList)>0: #check to see if obj is in the Project group yet project = obj.InList[0] tl = int(PathUtils.changeTool(obj,project)) obj.ToolNumber= tl
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all wires formed by the base edges.''' PathLog.track() if obj.UseComp: self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: self.commandlist.append(Path.Command("(Uncompensated Tool Path)")) shapes = [] if obj.Base: basewires = [] for b in obj.Base: edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) basewires.append((b[0], findWires(edgelist))) for base,wires in basewires: for wire in wires: f = Part.makeFace(wire, 'Part::FaceMakerSimple') # shift the compound to the bottom of the base object for # proper sectioning zShift = b[0].Shape.BoundBox.ZMin - f.BoundBox.ZMin newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation) f.Placement = newPlace env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams) shapes.append((env, False)) return shapes
def export(objectslist, filename, argstring): if not processArguments(argstring): return None global UNITS global UNIT_SPEED_FORMAT for obj in objectslist: if not hasattr(obj, "Path"): print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") return None print("postprocessing...") gcode = "" # write header if OUTPUT_HEADER: gcode += linenumber() + "(Exported by FreeCAD)\n" gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" gcode += linenumber() + "(Output Time:" + str(now) + ")\n" # Write the preamble if OUTPUT_COMMENTS: gcode += linenumber() + "(begin preamble)\n" for line in PREAMBLE.splitlines(False): gcode += linenumber() + line + "\n" gcode += linenumber() + UNITS + "\n" for obj in objectslist: # fetch machine details job = PathUtils.findParentJob(obj) myMachine = 'not set' if hasattr(job, "MachineName"): myMachine = job.MachineName if hasattr(job, "MachineUnits"): if job.MachineUnits == "Metric": UNITS = "G21" UNIT_SPEED_FORMAT = 'mm/min' else: UNITS = "G20" UNIT_SPEED_FORMAT = 'in/min' # do the pre_op if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: %s)\n" % obj.Label gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT) for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line gcode += parse(obj) # do the post_op if OUTPUT_COMMENTS: gcode += linenumber() + "(finish operation: %s)\n" % obj.Label for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # do the post_amble if OUTPUT_COMMENTS: gcode += "(begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line if FreeCAD.GuiUp and SHOW_EDITOR: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: final = dia.editor.toPlainText() else: final = gcode else: final = gcode print("done postprocessing.") if not filename == '-': gfile = pythonopen(filename, "wb") gfile.write(final) gfile.close() return final
def execute(self, obj): if obj.Base: # tie the toolnumber to the PathLoadTool object ToolNumber if len(obj.InList ) > 0: #check to see if obj is in the Project group yet project = obj.InList[0] tl = int(PathUtils.changeTool(obj, project)) obj.ToolNumber = tl tool = PathUtils.getTool(obj, obj.ToolNumber) if tool: radius = tool.Diameter / 2 else: # temporary value, to be taken from the properties later on radius = 0.001 if obj.Base[0].Shape.ShapeType == "Wire": #a pure wire was picked wire = obj.Base[0].Shape else: #we are dealing with a face and it's edges or just a face if obj.Edge1: e1 = FreeCAD.ActiveDocument.getObject( obj.Base[0].Name).Shape.Edges[ eval(obj.Edge1[1][0].lstrip('Edge')) - 1] if e1.BoundBox.ZMax <> e1.BoundBox.ZMin: FreeCAD.Console.PrintError( 'vertical edges not valid yet\n') return if obj.Base[0].Shape.ShapeType == 'Wire': wire = obj.Base[0].Shape if obj.Base[0].Shape.ShapeType == 'Solid' or obj.Base[ 0].Shape.ShapeType == 'Compound': shape = obj.Base[0].Shape for fw in shape.Wires: if (fw.BoundBox.ZMax == e1.BoundBox.ZMax) and ( fw.BoundBox.ZMin == e1.BoundBox.ZMin): for e in fw.Edges: if e.isSame(e1): #FreeCAD.Console.PrintMessage('found the same objects\n') wire = fw elif obj.Face1: # we are only dealing with a face or faces f1 = FreeCAD.ActiveDocument.getObject( obj.Base[0].Name).Shape.Faces[ eval(obj.Face1[1][0].lstrip('Face')) - 1] # make the side Left and direction CW for normal cnc milling obj.Direction = 'CW' obj.Side = "Left" # we only consider the outer wire if this is a single Face wire = f1.OuterWire if obj.Direction == 'CCW': clockwise = False else: clockwise = True output = "" output += '(' + str(obj.Comment) + ')\n' FirstEdge = None if obj.Edge1: ename = obj.Edge1[1][0] edgeNumber = int(ename[4:]) - 1 FirstEdge = obj.Base[0].Shape.Edges[edgeNumber] ZMax = obj.Base[0].Shape.BoundBox.ZMax ZCurrent = obj.ClearanceHeight.Value if obj.UseStartDepth: output += PathUtils.MakePath( wire, obj.Side, radius, clockwise, obj.ClearanceHeight.Value, obj.StepDown.Value, obj.StartDepth.Value, obj.FinalDepth.Value, FirstEdge, obj.PathClosed, obj.SegLen.Value, obj.VertFeed.Value, obj.HorizFeed.Value) else: output += PathUtils.MakePath( wire, obj.Side, radius, clockwise, obj.ClearanceHeight.Value, obj.StepDown.Value, ZMax, obj.FinalDepth.Value, FirstEdge, obj.PathClosed, obj.SegLen.Value, obj.VertFeed.Value, obj.HorizFeed.Value) if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def execute(self, obj): import Part # math #DraftGeomUtils output = "" toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.vertRapid = 100 self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if not tool or tool.Diameter == 0: self.radius = 0.25 else: self.radius = tool.Diameter / 2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " :" + obj.ToolDescription else: obj.Label = obj.UserLabel + " :" + obj.ToolDescription output += "(" + obj.Label + ")" if not obj.UseComp: output += "(Compensated Tool Path. Diameter: " + str( self.radius * 2) + ")" else: output += "(Uncompensated Tool Path)" parentJob = PathUtils.findParentJob(obj) if parentJob is None: return baseobject = parentJob.Base if baseobject is None: return contourwire = TechDraw.findShapeOutline(baseobject.Shape, 1, Vector(0, 0, 1)) edgelist = contourwire.Edges edgelist = Part.__sortEdges__(edgelist) try: output += self._buildPathLibarea(obj, edgelist) except: FreeCAD.Console.PrintError( "Something unexpected happened. Unable to generate a contour path. Check project and tool config." ) if obj.Active: path = Path.Path(output) obj.Path = path if obj.ViewObject: obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def execute(self, obj): import Part # math #DraftGeomUtils output = "" toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.vertRapid = 100 self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if tool.Diameter == 0: self.radius = 0.25 else: self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " :" + obj.ToolDescription else: obj.Label = obj.UserLabel + " :" + obj.ToolDescription output += "(" + obj.Label + ")" if obj.Side != "On": output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")" else: output += "(Uncompensated Tool Path)" if obj.Base: wires = [] for b in obj.Base: edgelist = [] for sub in b[1]: edgelist.append(getattr(b[0].Shape, sub)) wires.extend(findWires(edgelist)) for wire in wires: edgelist = wire.Edges edgelist = Part.__sortEdges__(edgelist) output += self._buildPathLibarea(obj, edgelist) if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def _buildPathArea(self, obj, baseobject, isHole, start, getsim): """_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.""" PathLog.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) heights = [i for i in self.depthparams] PathLog.debug("depths: {}".format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) PathLog.debug("Area with params: {}".format(area.getParams())) sections = area.makeSections( mode=0, project=self.areaOpUseProjection(obj), heights=heights ) PathLog.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) pathParams = self.areaOpPathParams(obj, isHole) pathParams["shapes"] = shapelist pathParams["feedrate"] = self.horizFeed pathParams["feedrate_v"] = self.vertFeed pathParams["verbose"] = True pathParams["resume_height"] = obj.SafeHeight.Value pathParams["retraction"] = obj.ClearanceHeight.Value pathParams["return_end"] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams["preamble"] = False if not self.areaOpRetractTool(obj): pathParams["threshold"] = 2.001 * self.radius if self.endVector is not None: pathParams["start"] = self.endVector elif PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: pathParams["start"] = obj.StartPoint obj.PathParams = str( {key: value for key, value in pathParams.items() if key != "shapes"} ) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) PathLog.debug("pp: {}, end vector: {}".format(pp, end_vector)) self.endVector = end_vector simobj = None if getsim: areaParams["Thicken"] = True areaParams["ToolRadius"] = self.radius - self.radius * 0.005 area.setParams(**areaParams) sec = area.makeSections(mode=0, project=False, heights=heights)[ -1 ].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj
def updateToolController(self, obj, combo): '''updateToolController(obj, combo) ... helper function to update obj's ToolController property if a different one has been selected in the combo box.''' tc = PathUtils.findToolController(obj, combo.currentText()) if obj.ToolController != tc: obj.ToolController = tc
def execute(self, obj): output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.vertRapid = 100 self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if tool.Diameter == 0: self.radius = 0.25 else: self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " :" + obj.ToolDescription else: obj.Label = obj.UserLabel + " :" + obj.ToolDescription locations = [] output = "(Begin Drilling)\n" if obj.Base: for loc in obj.Base: #print loc for sub in loc[1]: #locations.append(self._findDrillingVector(loc)) if "Face" in sub or "Edge" in sub: s = getattr(loc[0].Shape, sub) else: s = loc[0].Shape if s.ShapeType in ['Wire', 'Edge']: X = s.Edges[0].Curve.Center.x Y = s.Edges[0].Curve.Center.y Z = s.Edges[0].Curve.Center.z elif s.ShapeType in ['Vertex']: X = s.Point.x Y = s.Point.y Z = s.Point.z elif s.ShapeType in ['Face']: #if abs(s.normalAt(0, 0).z) == 1: # horizontal face X = s.CenterOfMass.x Y = s.CenterOfMass.y Z = s.CenterOfMass.z locations.append(FreeCAD.Vector(X, Y, Z)) output += "G90 G98\n" # rapid to clearance height output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" # rapid to first hole location, with spindle still retracted: p0 = locations[0] output += "G0 X" + fmt(p0.x) + " Y" + fmt(p0.y) + "F " + PathUtils.fmt(self.horizRapid) + "\n" # move tool to clearance plane output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" pword = "" qword = "" if obj.PeckDepth.Value > 0: cmd = "G83" qword = " Q" + fmt(obj.PeckDepth.Value) elif obj.DwellTime > 0: cmd = "G82" pword = " P" + fmt(obj.DwellTime) else: cmd = "G81" for p in locations: output += cmd + \ " X" + fmt(p.x) + \ " Y" + fmt(p.y) + \ " Z" + fmt(obj.FinalDepth.Value) + qword + pword + \ " R" + str(obj.RetractHeight.Value) + \ " F" + str(self.vertFeed) + "\n" \ output += "G80\n" # path = Path.Path(output) # obj.Path = path if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def execute(self, obj): PathLog.track() if not obj.Active: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False return commandlist = [] toolLoad = obj.ToolController self.depthparams = depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.SafeHeight.Value, step_down=obj.StepDown, z_finish_step=obj.FinishDepth.Value, final_depth=obj.FinalDepth.Value, user_depths=None) if toolLoad is None or toolLoad.ToolNumber == 0: FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.") return else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value self.vertRapid = toolLoad.VertRapid.Value self.horizRapid = toolLoad.HorizRapid.Value tool = toolLoad.Proxy.getTool(toolLoad) if tool.Diameter == 0: FreeCAD.Console.PrintError("No Tool found or diameter is zero. We need a tool to build a Path.") return else: self.radius = tool.Diameter/2 commandlist.append(Path.Command("(" + obj.Label + ")")) # Facing is done either against base objects if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] for b in obj.Base: for sub in b[1]: shape = getattr(b[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) else: PathLog.debug('The base subobject is not a face') return planeshape = Part.makeCompound(faces) PathLog.info("Working on a collection of faces {}".format(faces)) # If no base object, do planing of top surface of entire model else: parentJob = PathUtils.findParentJob(obj) if parentJob is None: PathLog.debug("No base object. No parent job found") return baseobject = parentJob.Base if baseobject is None: PathLog.debug("Parent job exists but no Base Object") return planeshape = baseobject.Shape PathLog.info("Working on a shape {}".format(baseobject.Name)) # if user wants the boundbox, calculate that PathLog.info("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) # save the envelope for reference obj.removalshape = env try: commandlist.extend(self._buildPathArea(obj, env).Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError(translate("Path_MillFace", "The selected settings did not produce a valid path.\n")) # Let's finish by rapid to clearance...just for safety commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) path = Path.Path(commandlist) obj.Path = path
def findHoles(self, obj, baseobject): '''findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.''' shape = baseobject.Shape PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] features = [] # tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter) tooldiameter = None PathLog.debug('search for holes larger than tooldiameter: {}: '.format( tooldiameter)) if DraftGeomUtils.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): PathLog.debug( 'edge candidate: {} (hash {})is drillable '.format( e, e.hashCode())) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength holelist.append({ 'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True }) features.append((baseobject, candidateEdgeName)) PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateEdgeName)) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): PathLog.debug('face candidate: {} is drillable '.format(f)) if hasattr(f.Surface, 'Center'): x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength else: center = f.Edges[0].Curve.Center x = center.x y = center.y diameter = f.Edges[0].Curve.Radius * 2 holelist.append({ 'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True }) features.append((baseobject, candidateFaceName)) PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateFaceName)) PathLog.debug("holes found: {}".format(holelist)) return features
def opExecute(self, obj): '''opExecute(obj) ... processes all Base features and Locations and collects them in a list of positions and radii which is then passed to circularHoleExecute(obj, holes). If no Base geometries and no Locations are present, the job's Base is inspected and all drillable features are added to Base. In this case appropriate values for depths are also calculated and assigned. Do not overwrite, implement circularHoleExecute(obj, holes) instead.''' PathLog.track() holes = [] baseSubsTuples = [] subCount = 0 allTuples = [] self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.useTempJobClones('Delete') # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.clearHeight = obj.ClearanceHeight.Value # pylint: disable=attribute-defined-outside-init self.safeHeight = obj.SafeHeight.Value # pylint: disable=attribute-defined-outside-init self.axialFeed = 0.0 # pylint: disable=attribute-defined-outside-init self.axialRapid = 0.0 # pylint: disable=attribute-defined-outside-init def haveLocations(self, obj): if PathOp.FeatureLocations & self.opFeatures(obj): return len(obj.Locations) != 0 return False if obj.EnableRotation == 'Off': strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value else: # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (clrOfset, safOfst) = opHeights[1] PathLog.debug("Exec. opHeights[0]: " + str(opHeights[0])) PathLog.debug("Exec. opHeights[1]: " + str(opHeights[1])) # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': strDep = self.xRotRad elif obj.EnableRotation == 'B(y)': strDep = self.yRotRad else: strDep = max(self.xRotRad, self.yRotRad) finDep = -1 * strDep obj.ClearanceHeight.Value = strDep + clrOfset obj.SafeHeight.Value = strDep + safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Complete rotational analysis and temp clone creation as needed if obj.EnableRotation == 'Off': PathLog.debug("Enable Rotation setting is 'Off' for {}.".format( obj.Name)) stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'A', stock)) else: for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] for sub in subsList: if self.isHoleEnabled(obj, base, sub): shape = getattr(base.Shape, sub) rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis( obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary PathLog.debug( "Verifying {} orientation: running faceRotationAnalysis() again." .format(sub)) faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) # pylint: disable=unused-variable if rtn is True: msg = obj.Name + ":: " msg += translate( "Path", "{} might be misaligned after initial rotation." .format(sub)) + " " if obj.AttemptInverseAngle is True and obj.InverseAngle is False: (clnBase, clnStock, angle) = self.applyInverseAngle( obj, clnBase, clnStock, axis, angle) msg += translate( "Path", "Rotated to 'InverseAngle' to attempt access." ) else: if len(subsList) == 1: msg += translate( "Path", "Consider toggling the 'InverseAngle' property and recomputing." ) else: msg += translate( "Path", "Consider transferring '{}' to independent operation." .format(sub)) PathLog.warning(msg) # title = translate("Path", 'Rotation Warning') # self.guiMessage(title, msg, False) else: PathLog.debug( "Face appears to be oriented correctly.") cmnt = "{}: {} @ {}; ".format( sub, axis, str(round(angle, 5))) if cmnt not in obj.Comment: obj.Comment += cmnt tup = clnBase, sub, tag, angle, axis, clnStock allTuples.append(tup) else: if self.warnDisabledAxis(obj, axis, sub) is True: pass # Skip drill feature due to access issue else: 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 allTuples.append(tup) # Eif # Eif subCount += 1 # Efor # Efor (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): PathLog.debug('hTag: {}'.format(Tags[o])) subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor for base, subs, angle, axis, stock in baseSubsTuples: for sub in subs: if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) if pos: # Default is treat selection as 'Face' shape holeBtm = base.Shape.getElement(sub).BoundBox.ZMin if base.Shape.getElement(sub).ShapeType == 'Edge': msg = translate( "Path", "Verify Final Depth of holes based on edges. {} depth is: {} mm" .format(sub, round(holeBtm, 4))) + " " msg += translate( "Path", "Always select the bottom edge of the hole when using an edge." ) PathLog.warning(msg) # Warn user if Final Depth set lower than bottom of hole if finDep < holeBtm: msg = translate( "Path", "Final Depth setting is below the hole bottom for {}." .format(sub)) + ' ' msg += translate( "Path", "{} depth is calculated at {} mm".format( sub, round(holeBtm, 4))) PathLog.warning(msg) holes.append({ 'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub), 'angle': angle, 'axis': axis, 'trgtDep': finDep, 'stkTop': stock.Shape.BoundBox.ZMax }) if haveLocations(self, obj): for location in obj.Locations: # holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'holeBtm': obj.FinalDepth.Value}) holes.append({ 'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'trgtDep': finDep, 'stkTop': PathUtils.findParentJob(obj).stock.Shape.BoundBox.ZMax }) if len(holes) > 0: self.circularHoleExecute( obj, holes) # circularHoleExecute() located in PathDrilling.py self.useTempJobClones( 'Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user PathLog.debug("obj.Name: " + str(obj.Name))
def execute(self,obj): if obj.Base: tool = PathUtils.getLastTool(obj) if tool: radius = tool.Diameter/2 if radius < 0:# safe guard radius -= radius else: # temporary value, to be taken from the properties later on radius = 1 import Part, DraftGeomUtils if "Face" in obj.Base[1][0]: shape = getattr(obj.Base[0].Shape,obj.Base[1][0]) else: edges = [getattr(obj.Base[0].Shape,sub) for sub in obj.Base[1]] shape = Part.Wire(edges) print len(edges) # absolute coords, millimeters, cancel offsets output = "G90\nG21\nG40\n" # save tool if obj.ToolNumber > 0 and tool.ToolNumber != obj.ToolNumber: output += "M06 T" + str(tool.ToolNumber) + "\n" # build offsets offsets = [] nextradius = radius result = DraftGeomUtils.pocket2d(shape,nextradius) while result: #print "Adding " + str(len(result)) + " wires" offsets.extend(result) nextradius += radius result = DraftGeomUtils.pocket2d(shape,nextradius) # first move will be rapid, subsequent will be at feed rate first = True startPoint = None fastZPos = max(obj.StartDepth + 2, obj.RetractHeight) # revert the list so we start with the outer wires if obj.StartAt != 'Edge': offsets.reverse() # print "startDepth: " + str(obj.StartDepth) # print "finalDepth: " + str(obj.FinalDepth) # print "stepDown: " + str(obj.StepDown) # print "finishDepth" + str(obj.FinishDepth) # print "offsets:", len(offsets) def prnt(vlu): return str("%.4f" % round(vlu, 4)) #Fraction of tool radius our plunge helix is to be #FIXME: This should be configurable plungeR = 0.75 #(minimum) Fraction of tool DIAMETER to go back and forth while ramp-plunging #FIXME: This should be configurable #FIXME: The ramp plunging should maybe even be limited to this distance; I don't know what's best rampD = 0.75 #Total offset from the desired pocket edge is tool radius plus the plunge helix radius #Any point on these curves could be the center of a plunge helixBounds = DraftGeomUtils.pocket2d(shape, tool.Diameter / 2. * (1 + plungeR)) #Try to find a location to nicely plunge, starting with a helix, then ramp #Can't do it without knowledge of a tool plungePos = None rampEdge = None if not tool: raise Error("Ramp plunge location-finding requires a tool") return else: #Since we're going to start machining either the inner-most #edge or the outer (depending on StartAt setting), try to #plunge near that location if helixBounds: #Edge is easy- pick a point on helixBounds and go with it if obj.StartAt == 'Edge': plungePos = helixBounds[0].Edges[0].Vertexes[0].Point #Center is harder- use a point from the first offset, check if it works else: plungePos = offsets[0].Edges[0].Vertexes[0].Point #If it turns out this is invalid for some reason, nuke plungePos [perp,idx] = DraftGeomUtils.findPerpendicular(plungePos, shape.Edges) if not perp or perp.Length < tool.Diameter / 2. * (1 + plungeR): plungePos = None #FIXME: Really need to do a point-in-polygon operation to make sure this is within helixBounds #Or some math to prove that it has to be (doubt that's true) #Maybe reverse helixBounds and pick off that? #If we didn't find a place to helix, how about a ramp? if not plungePos: #Check first edge of our offsets if (offsets[0].Edges[0].Length >= tool.Diameter * rampD) and not (isinstance(offsets[0].Edges[0].Curve, Part.Circle)): rampEdge = offsets[0].Edges[0] #The last edge also connects with the starting location- try that elif (offsets[0].Edges[-1].Length >= tool.Diameter * rampD) and not (isinstance(offsets[0].Edges[-1].Curve, Part.Circle)): rampEdge = offsets[0].Edges[-1] else: print "Neither edge works: " + str(offsets[0].Edges[0]) + ", " + str(offsets[0].Edges[-1]) #FIXME: There's got to be a smarter way to find a place to ramp #Returns gcode to perform a rapid move def rapid(x=None, y=None, z=None): retstr = "G00" if (x != None) or (y != None) or (z != None): if (x != None): retstr += " X" + str("%.4f" % x) if (y != None): retstr += " Y" + str("%.4f" % y) if (z != None): retstr += " Z" + str("%.4f" % z) else: return "" return retstr + "\n" #Returns gcode to perform a linear feed def feed(x=None, y=None, z=None): global feedxy retstr = "G01 F" if(x == None) and (y == None): retstr += str("%.4f" % obj.HorizFeed) else: retstr += str("%.4f" % obj.VertFeed) if (x != None) or (y != None) or (z != None): if (x != None): retstr += " X" + str("%.4f" % x) if (y != None): retstr += " Y" + str("%.4f" % y) if (z != None): retstr += " Z" + str("%.4f" % z) else: return "" return retstr + "\n" #Returns gcode to perform an arc #Assumes XY plane or helix around Z #Don't worry about starting Z- assume that's dealt with elsewhere def arc(cx, cy, sx, sy, ex, ey, ez=None, ccw=False): #If start/end radii aren't within eps, abort eps = 0.01 if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps: print "ERROR: Illegal arc: Stand and end radii not equal" return "" #Set [C]CW and feed retstr = "" if ccw: retstr += "G03 F" else: retstr += "G02 F" retstr += str(obj.HorizFeed) #End location retstr += " X" + str("%.4f" % ex) + " Y" + str("%.4f" % ey) #Helix if requested if ez != None: retstr += " Z" + str("%.4f" % ez) #Append center offsets retstr += " I" + str("%.4f" % (cx - sx)) + " J" + str("%.4f" % (cy - sy)) return retstr + "\n" #Returns gcode to helically plunge #destZ is the milling level #startZ is the height we can safely feed down to before helix-ing def helicalPlunge(plungePos, rampangle, destZ, startZ): helixCmds = "(START HELICAL PLUNGE)\n" if(plungePos == None): raise Error("Helical plunging requires a position!") return None if(not tool): raise Error("Helical plunging requires a tool!") return None helixX = plungePos.x + tool.Diameter/2. * plungeR helixY = plungePos.y; helixCirc = math.pi * tool.Diameter * plungeR dzPerRev = math.sin(rampangle/180. * math.pi) * helixCirc #Go to the start of the helix position helixCmds += rapid(helixX, helixY) helixCmds += rapid(z=startZ) #Helix as required to get to the requested depth lastZ = startZ curZ = max(startZ-dzPerRev, destZ) done = False while not done: done = (curZ == destZ) #NOTE: FreeCAD doesn't render this, but at least LinuxCNC considers it valid #helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX, helixY, ez = curZ, ccw=True) #Use two half-helixes; FreeCAD renders that correctly, #and it fits with the other code breaking up 360-degree arcs helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX - tool.Diameter * plungeR, helixY, ez = (curZ + lastZ)/2., ccw=True) helixCmds += arc(plungePos.x, plungePos.y, helixX - tool.Diameter * plungeR, helixY, helixX, helixY, ez = curZ, ccw=True) lastZ = curZ curZ = max(curZ - dzPerRev, destZ) return helixCmds #Returns commands to linearly ramp into a cut #FIXME: This ramps along the first edge, assuming it's long #enough, NOT just wiggling back and forth by ~0.75 * toolD. #Not sure if that's any worse, but it's simpler #FIXME: This code is untested def rampPlunge(edge, rampangle, destZ, startZ): rampCmds = "(START RAMP PLUNGE)\n" if(edge == None): raise Error("Ramp plunging requires an edge!") return None if(not tool): raise Error("Ramp plunging requires a tool!") sPoint = edge.Vertexes[0].Point ePoint = edge.Vertexes[1].Point #Evidently edges can get flipped- pick the right one in this case #FIXME: This is iffy code, based on what already existed in the "for vpos ..." loop below if ePoint == sPoint: #print "FLIP" ePoint = edge.Vertexes[-1].Point #print "Start: " + str(sPoint) + " End: " + str(ePoint) + " Zhigh: " + prnt(startZ) + " ZLow: " + prnt(destZ) rampDist = edge.Length rampDZ = math.sin(rampangle/180. * math.pi) * rampDist rampCmds += rapid(sPoint.x, sPoint.y) rampCmds += rapid(z=startZ) #Ramp down to the requested depth #FIXME: This might be an arc, so handle that as well lastZ = startZ curZ = max(startZ-rampDZ, destZ) done = False while not done: done = (curZ == destZ) #If it's an arc, handle it! if isinstance(edge.Curve,Part.Circle): raise Error("rampPlunge: Screw it, not handling an arc.") #Straight feed! Easy! else: rampCmds += feed(ePoint.x, ePoint.y, curZ) rampCmds += feed(sPoint.x, sPoint.y) lastZ = curZ curZ = max(curZ - rampDZ, destZ) return rampCmds #For helix-ing/ramping, know where we were last time #FIXME: Can probably get this from the "machine"? lastZ = fastZPos for vpos in frange(obj.StartDepth, obj.FinalDepth, obj.StepDown, obj.FinishDepth): # print "vpos: " + str(vpos) #Every for every depth we should helix down first = True # loop over successive wires for currentWire in offsets: # print "new line (offset)" last = None for edge in currentWire.Edges: # print "new edge" if not last: # we set the base GO to our fast move to our starting pos if first: #If we can helix, do so if plungePos: output += helicalPlunge(plungePos, 3, vpos, lastZ) #print output lastZ = vpos #Otherwise, see if we can ramp #FIXME: This could be a LOT smarter (eg, searching for a longer leg of the edge to ramp along) elif rampEdge: output += rampPlunge(rampEdge, 3, vpos, lastZ) lastZ = vpos #Otherwise, straight plunge... Don't want to, but sometimes you might not have a choice. #FIXME: At least not with the lazy ramp programming above... else: print "WARNING: Straight-plunging... probably not good, but we didn't find a place to helix or ramp" startPoint = edge.Vertexes[0].Point output += "G0 X" + prnt(startPoint.x) + " Y" + prnt(startPoint.y) +\ " Z" + prnt(fastZPos) + "\n" first = False #then move slow down to our starting point for our profile last = edge.Vertexes[0].Point output += "G1 X" + prnt(last.x) + " Y" + prnt(last.y) + " Z" + prnt(vpos) + "\n" #if isinstance(edge.Curve,Part.Circle): if DraftGeomUtils.geomType(edge) == "Circle": point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point # print "flipped" center = edge.Curve.Center relcenter = center.sub(last) v1 = last.sub(center) v2 = point.sub(center) if v1.cross(v2).z < 0: output += "G2" else: output += "G3" output += " X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos) output += " I" + prnt(relcenter.x) + " J" +prnt(relcenter.y) + " K" + prnt(relcenter.z) output += "\n" last = point else: point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point output += "G1 X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos) + "\n" last = point #move back up output += "G0 Z" + prnt(fastZPos) + "\n" # print output # path = Path.Path(output) # obj.Path = path if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False
def opUpdateDepths(self, obj): '''updateDepths(obj) ... engraving is always done at the top most z-value''' job = PathUtils.findParentJob(obj) self.opSetDefaultValues(obj, job)
def buildpathocc(self, obj, wires): import Part import DraftGeomUtils output = "" # absolute coords, millimeters, cancel offsets for wire in wires: offset = wire # reorder the wire offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) # we create the path from the offset shape last = None for edge in offset.Edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point output += "G0" + " X" + PathUtils.fmt( last.x ) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt( obj.SafeHeight.Value) # Rapid sto starting position output += "G1" + " X" + PathUtils.fmt( last.x ) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt( obj.FinalDepth.Value) + "F " + PathUtils.fmt( self.vertFeed) + "\n" # Vertical feed to depth if isinstance(edge.Curve, Part.Circle): point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point center = edge.Curve.Center relcenter = center.sub(last) v1 = last.sub(center) v2 = point.sub(center) if v1.cross(v2).z < 0: output += "G2" else: output += "G3" output += " X" + PathUtils.fmt( point.x) + " Y" + PathUtils.fmt( point.y) + " Z" + PathUtils.fmt( obj.FinalDepth.Value) output += " I" + PathUtils.fmt( relcenter.x) + " J" + PathUtils.fmt( relcenter.y) + " K" + PathUtils.fmt(relcenter.z) output += " F " + PathUtils.fmt(self.horizFeed) output += "\n" last = point else: point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point output += "G1 X" + PathUtils.fmt( point.x) + " Y" + PathUtils.fmt( point.y) + " Z" + PathUtils.fmt( obj.FinalDepth.Value) output += " F " + PathUtils.fmt(self.horizFeed) output += "\n" last = point output += "G0 Z " + PathUtils.fmt(obj.SafeHeight.Value) return output
def areaOpShapes(self, obj): """areaOpShapes(obj) ... return top face""" # Facing is done either against base objects holeShape = None PathLog.debug("depthparams: {}".format([i for i in self.depthparams])) if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) self.removalshapes = [] faces = [] holes = [] holeEnvs = [] oneBase = [obj.Base[0][0], True] sub0 = getattr(obj.Base[0][0].Shape, obj.Base[0][1][0]) minHeight = sub0.BoundBox.ZMax for b in obj.Base: for sub in b[1]: shape = getattr(b[0].Shape, sub) if isinstance(shape, Part.Face): faces.append(shape) if shape.BoundBox.ZMin < minHeight: minHeight = shape.BoundBox.ZMin # Limit to one model base per operation if oneBase[0] is not b[0]: oneBase[1] = False if numpy.isclose( abs(shape.normalAt(0, 0).z), 1 ): # horizontal face # Analyze internal closed wires to determine if raised or a recess for wire in shape.Wires[1:]: if obj.ExcludeRaisedAreas: ip = self.isPocket(b[0], shape, wire) if ip is False: holes.append((b[0].Shape, wire)) else: holes.append((b[0].Shape, wire)) else: PathLog.warning( 'The base subobject, "{0}," is not a face. Ignoring "{0}."'.format( sub ) ) if obj.ExcludeRaisedAreas and len(holes) > 0: for shape, wire in holes: f = Part.makeFace(wire, "Part::FaceMakerSimple") env = PathUtils.getEnvelope( shape, subshape=f, depthparams=self.depthparams ) holeEnvs.append(env) holeShape = Part.makeCompound(holeEnvs) PathLog.debug("Working on a collection of faces {}".format(faces)) planeshape = Part.makeCompound(faces) # If no base object, do planing of top surface of entire model else: planeshape = Part.makeCompound([base.Shape for base in self.model]) PathLog.debug("Working on a shape {}".format(obj.Label)) # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox # Apply offset for clearing edges offset = 0 if obj.ClearEdges: offset = self.radius + 0.1 bb.XMin = bb.XMin - offset bb.YMin = bb.YMin - offset bb.XMax = bb.XMax + offset bb.YMax = bb.YMax + offset if obj.BoundaryShape == "Boundbox": bbperim = Part.makeBox( bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1), ) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) if obj.ExcludeRaisedAreas and oneBase[1]: includedFaces = self.getAllIncludedFaces( oneBase[0], env, faceZ=minHeight ) if len(includedFaces) > 0: includedShape = Part.makeCompound(includedFaces) includedEnv = PathUtils.getEnvelope( oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams, ) env = env.cut(includedEnv) elif obj.BoundaryShape == "Stock": stock = PathUtils.findParentJob(obj).Stock.Shape env = stock if obj.ExcludeRaisedAreas and oneBase[1]: includedFaces = self.getAllIncludedFaces( oneBase[0], stock, faceZ=minHeight ) if len(includedFaces) > 0: stockEnv = PathUtils.getEnvelope( partshape=stock, depthparams=self.depthparams ) includedShape = Part.makeCompound(includedFaces) includedEnv = PathUtils.getEnvelope( oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams, ) env = stockEnv.cut(includedEnv) elif obj.BoundaryShape == "Perimeter": if obj.ClearEdges: psZMin = planeshape.BoundBox.ZMin ofstShape = PathUtils.getOffsetArea( planeshape, self.radius * 1.25, plane=planeshape ) ofstShape.translate( FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin) ) env = PathUtils.getEnvelope( partshape=ofstShape, depthparams=self.depthparams ) else: env = PathUtils.getEnvelope( partshape=planeshape, depthparams=self.depthparams ) elif obj.BoundaryShape == "Face Region": baseShape = oneBase[0].Shape psZMin = planeshape.BoundBox.ZMin ofst = 0.0 if obj.ClearEdges: ofst = self.tool.Diameter * 0.51 ofstShape = PathUtils.getOffsetArea(planeshape, ofst, plane=planeshape) ofstShape.translate( FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin) ) # Calculate custom depth params for removal shape envelope, with start and final depth buffers custDepthparams = self._customDepthParams( obj, obj.StartDepth.Value + 0.2, obj.FinalDepth.Value - 0.1 ) # only an envelope ofstShapeEnv = PathUtils.getEnvelope( partshape=ofstShape, depthparams=custDepthparams ) if obj.ExcludeRaisedAreas: env = ofstShapeEnv.cut(baseShape) env.translate( FreeCAD.Vector(0.0, 0.0, -0.00001) ) # lower removal shape into buffer zone else: env = ofstShapeEnv if holeShape: PathLog.debug("Processing holes and face ...") holeEnv = PathUtils.getEnvelope( partshape=holeShape, depthparams=self.depthparams ) newEnv = env.cut(holeEnv) tup = newEnv, False, "pathMillFace" else: PathLog.debug("Processing solid face ...") tup = env, False, "pathMillFace" self.removalshapes.append(tup) obj.removalshape = self.removalshapes[0][0] # save removal shape return self.removalshapes
def GenerateGCode(op, obj, adaptiveResults, helixDiameter): # pylint: disable=unused-argument if len(adaptiveResults) == 0 or len(adaptiveResults[0]["AdaptivePaths"]) == 0: return # minLiftDistance = op.tool.Diameter helixRadius = 0 for region in adaptiveResults: p1 = region["HelixCenterPoint"] p2 = region["StartPoint"] r = math.sqrt( (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]) ) if r > helixRadius: helixRadius = r stepDown = obj.StepDown.Value passStartDepth = obj.StartDepth.Value if stepDown < 0.1: stepDown = 0.1 length = 2 * math.pi * helixRadius if float(obj.HelixAngle) < 1: obj.HelixAngle = 1 if float(obj.HelixAngle) > 89: obj.HelixAngle = 89 if float(obj.HelixConeAngle) < 0: obj.HelixConeAngle = 0 helixAngleRad = math.pi * float(obj.HelixAngle) / 180.0 depthPerOneCircle = length * math.tan(helixAngleRad) # print("Helix circle depth: {}".format(depthPerOneCircle)) stepUp = obj.LiftDistance.Value if stepUp < 0: stepUp = 0 finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 if finish_step > stepDown: finish_step = stepDown depth_params = PathUtils.depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, start_depth=obj.StartDepth.Value, step_down=stepDown, z_finish_step=finish_step, final_depth=obj.FinalDepth.Value, user_depths=None, ) # ml: this is dangerous because it'll hide all unused variables hence forward # however, I don't know what lx and ly signify so I'll leave them for now # pylint: disable=unused-variable # lx = adaptiveResults[0]["HelixCenterPoint"][0] # ly = adaptiveResults[0]["HelixCenterPoint"][1] lz = passStartDepth step = 0 for passEndDepth in depth_params.data: step = step + 1 for region in adaptiveResults: startAngle = math.atan2( region["StartPoint"][1] - region["HelixCenterPoint"][1], region["StartPoint"][0] - region["HelixCenterPoint"][0], ) # lx = region["HelixCenterPoint"][0] # ly = region["HelixCenterPoint"][1] passDepth = passStartDepth - passEndDepth p1 = region["HelixCenterPoint"] p2 = region["StartPoint"] helixRadius = math.sqrt( (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]) ) # Helix ramp if helixRadius > 0.01: r = helixRadius - 0.01 maxfi = passDepth / depthPerOneCircle * 2 * math.pi fi = 0 offsetFi = -maxfi + startAngle - math.pi / 16 helixStart = [ region["HelixCenterPoint"][0] + r * math.cos(offsetFi), region["HelixCenterPoint"][1] + r * math.sin(offsetFi), ] op.commandlist.append( Path.Command("(Helix to depth: %f)" % passEndDepth) ) if obj.UseHelixArcs is False: # rapid move to start point op.commandlist.append( Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) ) op.commandlist.append( Path.Command( "G0", { "X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value, }, ) ) # rapid move to safe height op.commandlist.append( Path.Command( "G0", { "X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value, }, ) ) # move to start depth op.commandlist.append( Path.Command( "G1", { "X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed, }, ) ) if obj.HelixConeAngle == 0: while fi < maxfi: x = region["HelixCenterPoint"][0] + r * math.cos( fi + offsetFi ) y = region["HelixCenterPoint"][1] + r * math.sin( fi + offsetFi ) z = passStartDepth - fi / maxfi * ( passStartDepth - passEndDepth ) op.commandlist.append( Path.Command( "G1", {"X": x, "Y": y, "Z": z, "F": op.vertFeed} ) ) # lx = x # ly = y fi = fi + math.pi / 16 # one more circle at target depth to make sure center is cleared maxfi = maxfi + 2 * math.pi while fi < maxfi: x = region["HelixCenterPoint"][0] + r * math.cos( fi + offsetFi ) y = region["HelixCenterPoint"][1] + r * math.sin( fi + offsetFi ) z = passEndDepth op.commandlist.append( Path.Command( "G1", {"X": x, "Y": y, "Z": z, "F": op.horizFeed} ) ) # lx = x # ly = y fi = fi + math.pi / 16 else: # Cone _HelixAngle = 360 - (float(obj.HelixAngle) * 4) if obj.HelixConeAngle > 6: obj.HelixConeAngle = 6 helixRadius *= 0.9 # Calculate everything helix_height = passStartDepth - passEndDepth r_extra = helix_height * math.tan( math.radians(obj.HelixConeAngle) ) HelixTopRadius = helixRadius + r_extra helix_full_height = HelixTopRadius * ( math.cos(math.radians(obj.HelixConeAngle)) / math.sin(math.radians(obj.HelixConeAngle)) ) # Start height z = passStartDepth i = 0 # Default step down z_step = 0.05 # Bigger angle, smaller step down if _HelixAngle > 120: z_step = 0.025 if _HelixAngle > 240: z_step = 0.015 p = None # Calculate conical helix while z >= passEndDepth: if z < passEndDepth: z = passEndDepth p = CalcHelixConePoint( helix_full_height, i, HelixTopRadius, _HelixAngle ) op.commandlist.append( Path.Command( "G1", { "X": p["X"] + region["HelixCenterPoint"][0], "Y": p["Y"] + region["HelixCenterPoint"][1], "Z": z, "F": op.vertFeed, }, ) ) z = z - z_step i = i + z_step # Calculate some stuff for arcs at bottom p["X"] = p["X"] + region["HelixCenterPoint"][0] p["Y"] = p["Y"] + region["HelixCenterPoint"][1] x_m = ( region["HelixCenterPoint"][0] - p["X"] + region["HelixCenterPoint"][0] ) y_m = ( region["HelixCenterPoint"][1] - p["Y"] + region["HelixCenterPoint"][1] ) i_off = (x_m - p["X"]) / 2 j_off = (y_m - p["Y"]) / 2 # One more circle at target depth to make sure center is cleared op.commandlist.append( Path.Command( "G3", { "X": x_m, "Y": y_m, "Z": passEndDepth, "I": i_off, "J": j_off, "F": op.horizFeed, }, ) ) op.commandlist.append( Path.Command( "G3", { "X": p["X"], "Y": p["Y"], "Z": passEndDepth, "I": -i_off, "J": -j_off, "F": op.horizFeed, }, ) ) else: # Use arcs for helix - no conical shape support helixStart = [ region["HelixCenterPoint"][0] + r, region["HelixCenterPoint"][1], ] # rapid move to start point op.commandlist.append( Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) ) op.commandlist.append( Path.Command( "G0", { "X": helixStart[0], "Y": helixStart[1], "Z": obj.ClearanceHeight.Value, }, ) ) # rapid move to safe height op.commandlist.append( Path.Command( "G0", { "X": helixStart[0], "Y": helixStart[1], "Z": obj.SafeHeight.Value, }, ) ) # move to start depth op.commandlist.append( Path.Command( "G1", { "X": helixStart[0], "Y": helixStart[1], "Z": passStartDepth, "F": op.vertFeed, }, ) ) x = region["HelixCenterPoint"][0] + r y = region["HelixCenterPoint"][1] curDep = passStartDepth while curDep > (passEndDepth + depthPerOneCircle): op.commandlist.append( Path.Command( "G2", { "X": x - (2 * r), "Y": y, "Z": curDep - (depthPerOneCircle / 2), "I": -r, "F": op.vertFeed, }, ) ) op.commandlist.append( Path.Command( "G2", { "X": x, "Y": y, "Z": curDep - depthPerOneCircle, "I": r, "F": op.vertFeed, }, ) ) curDep = curDep - depthPerOneCircle lastStep = curDep - passEndDepth if lastStep > (depthPerOneCircle / 2): op.commandlist.append( Path.Command( "G2", { "X": x - (2 * r), "Y": y, "Z": curDep - (lastStep / 2), "I": -r, "F": op.vertFeed, }, ) ) op.commandlist.append( Path.Command( "G2", { "X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.vertFeed, }, ) ) else: op.commandlist.append( Path.Command( "G2", { "X": x - (2 * r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.vertFeed, }, ) ) op.commandlist.append( Path.Command( "G1", {"X": x, "Y": y, "Z": passEndDepth, "F": op.vertFeed}, ) ) # one more circle at target depth to make sure center is cleared op.commandlist.append( Path.Command( "G2", { "X": x - (2 * r), "Y": y, "Z": passEndDepth, "I": -r, "F": op.horizFeed, }, ) ) op.commandlist.append( Path.Command( "G2", { "X": x, "Y": y, "Z": passEndDepth, "I": r, "F": op.horizFeed, }, ) ) # lx = x # ly = y else: # no helix entry # rapid move to clearance height op.commandlist.append( Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) ) op.commandlist.append( Path.Command( "G0", { "X": region["StartPoint"][0], "Y": region["StartPoint"][1], "Z": obj.ClearanceHeight.Value, }, ) ) # straight plunge to target depth op.commandlist.append( Path.Command( "G1", { "X": region["StartPoint"][0], "Y": region["StartPoint"][1], "Z": passEndDepth, "F": op.vertFeed, }, ) ) lz = passEndDepth z = obj.ClearanceHeight.Value op.commandlist.append(Path.Command("(Adaptive - depth: %f)" % passEndDepth)) # add adaptive paths for pth in region["AdaptivePaths"]: motionType = pth[0] # [0] contains motion type for pt in pth[1]: # [1] contains list of points x = pt[0] y = pt[1] # dist = math.sqrt((x-lx)*(x-lx) + (y-ly)*(y-ly)) if motionType == area.AdaptiveMotionType.Cutting: z = passEndDepth if z != lz: op.commandlist.append( Path.Command("G1", {"Z": z, "F": op.vertFeed}) ) op.commandlist.append( Path.Command("G1", {"X": x, "Y": y, "F": op.horizFeed}) ) elif motionType == area.AdaptiveMotionType.LinkClear: z = passEndDepth + stepUp if z != lz: op.commandlist.append(Path.Command("G0", {"Z": z})) op.commandlist.append(Path.Command("G0", {"X": x, "Y": y})) elif motionType == area.AdaptiveMotionType.LinkNotClear: z = obj.ClearanceHeight.Value if z != lz: op.commandlist.append(Path.Command("G0", {"Z": z})) op.commandlist.append(Path.Command("G0", {"X": x, "Y": y})) # elif motionType == area.AdaptiveMotionType.LinkClearAtPrevPass: # if lx!=x or ly!=y: # op.commandlist.append(Path.Command("G0", { "X": lx, "Y":ly, "Z":passStartDepth+stepUp})) # op.commandlist.append(Path.Command("G0", { "X": x, "Y":y, "Z":passStartDepth+stepUp})) # lx = x # ly = y lz = z # return to safe height in this Z pass z = obj.ClearanceHeight.Value if z != lz: op.commandlist.append(Path.Command("G0", {"Z": z})) lz = z passStartDepth = passEndDepth # return to safe height in this Z pass z = obj.ClearanceHeight.Value if z != lz: op.commandlist.append(Path.Command("G0", {"Z": z})) lz = z z = obj.ClearanceHeight.Value if z != lz: op.commandlist.append(Path.Command("G0", {"Z": z}))
def opExecute(self, obj, getsim=False): """opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.""" PathLog.track() # Instantiate class variables for operation reference self.endVector = None self.leadIn = 2.0 # Initiate depthparams and calculate operation heights for operation self.depthparams = self._customDepthParams( obj, obj.StartDepth.Value, obj.FinalDepth.Value ) # Set start point if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # Adjust tuples length received from other PathWB tools/operations shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub or description tup = fc, iH, "otherOp" shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: locations = [] for s in shapes: if s[2] == "OpenEdge": shp = Part.makeCompound(s[0]) else: shp = s[0] locations.append( {"x": shp.BoundBox.XMax, "y": shp.BoundBox.YMax, "shape": s} ) locations = PathUtils.sort_locations(locations, ["x", "y"]) shapes = [j["shape"] for j in locations] sims = [] for shape, isHole, sub in shapes: profileEdgesIsOpen = False if sub == "OpenEdge": profileEdgesIsOpen = True if ( PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint ): osp = obj.StartPoint self.commandlist.append( Path.Command( "G0", {"X": osp.x, "Y": osp.y, "F": self.horizRapid} ) ) try: if profileEdgesIsOpen: (pp, sim) = self._buildProfileOpenEdges( obj, shape, isHole, start, getsim ) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError( "Something unexpected happened. Check project and tool config." ) else: if profileEdgesIsOpen: ppCmds = pp else: ppCmds = pp.Commands # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if ( self.areaOpRetractTool(obj) and self.endVector is not None and len(self.commandlist) > 1 ): self.endVector[2] = obj.ClearanceHeight.Value self.commandlist.append( Path.Command( "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} ) ) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
def profile(curve, side_of_line, radius=1.0, vertfeed=0.0, horizfeed=0.0, offset_extra=0.0, rapid_safety_space=None, clearance=None, start_depth=None, stepdown=None, final_depth=None, use_CRC=False, roll_on=None, roll_off=None, roll_start=False, roll_end=True, roll_radius=None, roll_start_pt=None, roll_end_pt=None): output = "" output += "G0 Z" + str(clearance) + "\n" print "in profile: 151" offset_curve = area.Curve(curve) if offset_curve.getNumVertices() <= 1: raise Exception, "Sketch has no elements!" if side_of_line == "On": use_CRC = False elif (side_of_line == "Left") or (side_of_line == "Right"): # get tool radius plus little bit of extra offset, if needed to clean # up profile a little more offset = radius + offset_extra if side_of_line == 'Left': offset_curve.Offset(offset) else: offset_curve.Offset(-offset) if offset_curve is False: raise Exception, "couldn't offset kurve " + str(offset_curve) else: raise Exception, "Side must be 'Left','Right', or 'On'" # ========================================================================= # #roll_on roll_off section # roll_on_curve = area.Curve() # if offset_curve.getNumVertices() <= 1: return # first_span = offset_curve.GetFirstSpan() # if roll_on == None: # rollstart = first_span.p # elif roll_on == 'auto': # if roll_radius < 0.0000000001: # rollstart = first_span.p # v = first_span.GetVector(0.0) # if direction == 'right': # off_v = area.Point(v.y, -v.x) # else: # off_v = area.Point(-v.y, v.x) # rollstart = first_span.p + off_v * roll_radius # else: # rollstart = roll_on # # rvertex = area.Vertex(first_span.p) # # if first_span.p == rollstart: # rvertex.type = 0 # else: # v = first_span.GetVector(0.0) # get start direction # rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v) # rvertex.type = -rvertex.type # because TangentialArc was used in reverse # # add a start roll on point # roll_on_curve.append(rollstart) # # # add the roll on arc # roll_on_curve.append(rvertex) # #end of roll_on roll_off section # ========================================================================= # do multiple depths layer_count = int((start_depth - final_depth) / stepdown) if layer_count * stepdown + 0.00001 < start_depth - final_depth: layer_count += 1 current_start_depth = start_depth prev_depth = start_depth for i in range(1, layer_count + 1): if i == layer_count: depth = final_depth else: depth = start_depth - i * stepdown mat_depth = prev_depth start_z = mat_depth # first move output += "G0 X" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x)) +\ " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) +\ " Z" + str(PathUtils.fmt(mat_depth + rapid_safety_space)) + "\n" # feed down to depth mat_depth = depth if start_z > mat_depth: mat_depth = start_z # feed down in Z output += "G1 X" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x)) +\ " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) + " Z" + str(PathUtils.fmt(depth)) +\ " F" + str(PathUtils.fmt(vertfeed)) + "\n" if use_CRC: if side_of_line == 'left': output += "G41" + "\n" else: output += "G42" + "\n" # cut the main kurve current_perim = 0.0 lastx = offset_curve.GetFirstSpan().p.x lasty = offset_curve.GetFirstSpan().p.y for span in offset_curve.GetSpans(): current_perim += span.Length() if span.v.type == 0: # line # feed(span.v.p.x, span.v.p.y, ez) output += "G1 X" + str(PathUtils.fmt(span.v.p.x)) + " Y" + str(PathUtils.fmt(span.v.p.y)) +\ " Z" + str(PathUtils.fmt(depth)) + " F" + \ str(PathUtils.fmt(horizfeed)) + "\n" lastx = span.v.p.x lasty = span.v.p.y elif (span.v.type == 1) or (span.v.type == -1): if span.v.type == 1: # anti-clockwise arc command = 'G3' elif span.v.type == -1: # clockwise arc command = 'G2' arc_I = span.v.c.x - lastx arc_J = span.v.c.y - lasty output += command + "X" + str(PathUtils.fmt( span.v.p.x)) + " Y" + str(PathUtils.fmt( span.v.p.y)) # +" Z"+ str(PathUtils.fmt(depth)) output += " I" + str(PathUtils.fmt(arc_I)) + " J" + str( PathUtils.fmt(arc_J)) + " F" + str( PathUtils.fmt(horizfeed) ) + '\n' # " K"+str(PathUtils.fmt(depth)) +"\n" lastx = span.v.p.x lasty = span.v.p.y else: raise Exception, "valid geometry identifier needed" if use_CRC: # end_CRC() output += "G40" + "\n" # rapid up to the clearance height output += "G0 Z" + str(PathUtils.fmt(clearance)) + "\n" del offset_curve return output
def _buildPathArea(self, obj, baseobject, isHole, start, getsim): '''_buildPathArea(obj, baseobject, isHole, start, getsim) ... internal function.''' # pylint: disable=unused-argument PathLog.track() area = Path.Area() area.setPlane(PathUtils.makeWorkplane(baseobject)) area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return if hasattr(obj, 'ExpandProfile') and obj.ExpandProfile != 0: areaParams = self.areaOpAreaParamsExpandProfile(obj, isHole) # pylint: disable=assignment-from-no-return heights = [i for i in self.depthparams] PathLog.debug('depths: {}'.format(heights)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) PathLog.debug("Area with params: {}".format(area.getParams())) sections = area.makeSections(mode=0, project=self.areaOpUseProjection(obj), heights=heights) PathLog.debug("sections = %s" % sections) shapelist = [sec.getShape() for sec in sections] PathLog.debug("shapelist = %s" % shapelist) pathParams = self.areaOpPathParams(obj, isHole) # pylint: disable=assignment-from-no-return pathParams['shapes'] = shapelist pathParams['feedrate'] = self.horizFeed pathParams['feedrate_v'] = self.vertFeed pathParams['verbose'] = True pathParams['resume_height'] = obj.SafeHeight.Value pathParams['retraction'] = obj.ClearanceHeight.Value pathParams['return_end'] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams['preamble'] = False if not self.areaOpRetractTool(obj): pathParams['threshold'] = 2.001 * self.radius if self.endVector is not None: pathParams['start'] = self.endVector elif PathOp.FeatureStartPoint & self.opFeatures( obj) and obj.UseStartPoint: pathParams['start'] = obj.StartPoint obj.PathParams = str({ key: value for key, value in pathParams.items() if key != 'shapes' }) PathLog.debug("Path with params: {}".format(obj.PathParams)) (pp, end_vector) = Path.fromShapes(**pathParams) PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) self.endVector = end_vector # pylint: disable=attribute-defined-outside-init simobj = None if getsim: areaParams['Thicken'] = True areaParams['ToolRadius'] = self.radius - self.radius * .005 area.setParams(**areaParams) sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj
def review(obj): limits = False "checks the selected project for common errors" toolcontrolcount = 0 for item in obj.Group: print "Checking: " + item.Label if item.Name[:2] == "TC": toolcontrolcount += 1 if item.ToolNumber == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " is using ID 0 which the undefined default. Please set a real tool.\n" )) else: tool = PU.getTool(item, item.ToolNumber) if tool is None: FreeCAD.Console.PrintError( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " is using tool: " + str(item.ToolNumber) + " which is invalid\n")) elif tool.Diameter == 0: FreeCAD.Console.PrintError( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " is using tool: " + str(item.ToolNumber) + " which has a zero diameter\n")) if item.HorizFeed == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Horizontal feed rate\n")) if item.VertFeed == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the Vertical feed rate\n")) if item.SpindleSpeed == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "Tool Controller: " + str(item.Label) + " has a 0 value for the spindle speed\n")) if item.Name[:7] == "Machine": if len(item.Tooltable.Tools) == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "Machine: " + str(item.Label) + " has no tools defined in the tool table\n")) if item.X_Max == item.X_Min or item.Y_Max == item.Y_Min: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "It appears the machine limits haven't been set. Not able to check path extents.\n" )) else: limits = True if toolcontrolcount == 0: FreeCAD.Console.PrintWarning( translate( "Path_Sanity", "A Tool Controller was not found. Default values are used which is dangerous. Please add a Tool Controller.\n" ))
def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ '''opExecute(obj, getsim=False) ... implementation of Path.Area ops. determines the parameters for _buildPathArea(). Do not overwrite, implement areaOpAreaParams(obj, isHole) ... op specific area param dictionary areaOpPathParams(obj, isHole) ... op specific path param dictionary areaOpShapes(obj) ... the shape for path area to process areaOpUseProjection(obj) ... return true if operation can use projection instead.''' PathLog.track() # Instantiate class variables for operation reference self.endVector = None # pylint: disable=attribute-defined-outside-init self.rotateFlag = False # pylint: disable=attribute-defined-outside-init self.leadIn = 2.0 # pylint: disable=attribute-defined-outside-init self.cloneNames = [] # pylint: disable=attribute-defined-outside-init self.guiMsgs = [] # pylint: disable=attribute-defined-outside-init self.tempObjectNames = [] # pylint: disable=attribute-defined-outside-init self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox # pylint: disable=attribute-defined-outside-init self.useTempJobClones( 'Delete') # Clear temporary group and recreate for temp job clones self.rotStartDepth = None # pylint: disable=attribute-defined-outside-init start_depth = obj.StartDepth.Value final_depth = obj.FinalDepth.Value if obj.EnableRotation != 'Off': # Calculate operation heights based upon rotation radii opHeights = self.opDetermineRotationRadii(obj) (self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0] # pylint: disable=attribute-defined-outside-init (self.clrOfset, self.safOfst) = opHeights[1] # pylint: disable=attribute-defined-outside-init # Set clearance and safe heights based upon rotation radii if obj.EnableRotation == 'A(x)': start_depth = self.xRotRad elif obj.EnableRotation == 'B(y)': start_depth = self.yRotRad else: start_depth = max(self.xRotRad, self.yRotRad) final_depth = -1 * start_depth self.rotStartDepth = start_depth # The next two lines are improper code. # The ClearanceHeight and SafeHeight need to be set in opSetDefaultValues() method. # They should not be redefined here, so this entire `if...:` statement needs relocated. obj.ClearanceHeight.Value = start_depth + self.clrOfset obj.SafeHeight.Value = start_depth + self.safOfst # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() # Set axial feed rates based upon horizontal feed rates safeCircum = 2 * math.pi * obj.SafeHeight.Value self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init # Initiate depthparams and calculate operation heights for rotational operation self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) # Set start point if PathOp.FeatureStartPoint & self.opFeatures( obj) and obj.UseStartPoint: start = obj.StartPoint else: start = None aOS = self.areaOpShapes(obj) # pylint: disable=assignment-from-no-return # Adjust tuples length received from other PathWB tools/operations beside PathPocketShape shapes = [] for shp in aOS: if len(shp) == 2: (fc, iH) = shp # fc, iH, sub, angle, axis, strtDep, finDep tup = fc, iH, 'otherOp', 0.0, 'S', start_depth, final_depth shapes.append(tup) else: shapes.append(shp) if len(shapes) > 1: jobs = list() for s in shapes: if s[2] == 'OpenEdge': shp = Part.makeCompound(s[0]) else: shp = s[0] jobs.append({ 'x': shp.BoundBox.XMax, 'y': shp.BoundBox.YMax, 'shape': s }) jobs = PathUtils.sort_jobs(jobs, ['x', 'y']) shapes = [j['shape'] for j in jobs] sims = [] numShapes = len(shapes) for ns in range(0, numShapes): profileEdgesIsOpen = False (shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns] # pylint: disable=unused-variable if sub == 'OpenEdge': profileEdgesIsOpen = True if PathOp.FeatureStartPoint & self.opFeatures( obj) and obj.UseStartPoint: osp = obj.StartPoint self.commandlist.append( Path.Command('G0', { 'X': osp.x, 'Y': osp.y, 'F': self.horizRapid })) if ns < numShapes - 1: nextAxis = shapes[ns + 1][4] else: nextAxis = 'L' self.depthparams = self._customDepthParams(obj, strDep, finDep) try: if profileEdgesIsOpen: (pp, sim) = self._buildProfileOpenEdges( obj, shape, isHole, start, getsim) else: (pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim) except Exception as e: # pylint: disable=broad-except FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError( "Something unexpected happened. Check project and tool config." ) else: if profileEdgesIsOpen: ppCmds = pp else: ppCmds = pp.Commands if obj.EnableRotation != 'Off' and self.rotateFlag is True: # Rotate model to index for cut if axis == 'X': axisOfRot = 'A' elif axis == 'Y': axisOfRot = 'B' elif axis == 'Z': axisOfRot = 'C' else: axisOfRot = 'A' # Rotate Model to correct angle ppCmds.insert( 0, Path.Command('G0', { axisOfRot: angle, 'F': self.axialRapid })) # Raise cutter to safe height ppCmds.insert( 0, Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) # Return index to starting position if axis of rotation changes. if numShapes > 1: if ns != numShapes - 1: if axis != nextAxis: ppCmds.append( Path.Command('G0', { axisOfRot: 0.0, 'F': self.axialRapid })) # Eif # Save gcode commands to object command list self.commandlist.extend(ppCmds) sims.append(sim) # Eif if self.areaOpRetractTool( obj) and self.endVector is not None and len( self.commandlist) > 1: self.endVector[2] = obj.ClearanceHeight.Value self.commandlist.append( Path.Command('G0', { 'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid })) # Raise cutter to safe height and rotate back to original orientation # based on next rotational operation in job if self.rotateFlag is True: resetAxis = False lastJobOp = None nextJobOp = None opIdx = 0 JOB = PathUtils.findParentJob(obj) jobOps = JOB.Operations.Group numJobOps = len(jobOps) for joi in range(0, numJobOps): jo = jobOps[joi] if jo.Name == obj.Name: opIdx = joi lastOpIdx = opIdx - 1 nextOpIdx = opIdx + 1 if lastOpIdx > -1: lastJobOp = jobOps[lastOpIdx] if nextOpIdx < numJobOps: nextJobOp = jobOps[nextOpIdx] if lastJobOp is not None: if hasattr(lastJobOp, 'EnableRotation'): PathLog.debug( 'Last Op, {}, has `EnableRotation` set to {}'.format( lastJobOp.Label, lastJobOp.EnableRotation)) if lastJobOp.EnableRotation != obj.EnableRotation: resetAxis = True # if ns == numShapes - 1: # If last shape, check next op EnableRotation setting if nextJobOp is not None: if hasattr(nextJobOp, 'EnableRotation'): PathLog.debug( 'Next Op, {}, has `EnableRotation` set to {}'.format( nextJobOp.Label, nextJobOp.EnableRotation)) if nextJobOp.EnableRotation != obj.EnableRotation: resetAxis = True # Raise to safe height if rotation activated self.commandlist.append( Path.Command('G0', { 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) # reset rotational axes if necessary if resetAxis is True: self.commandlist.append( Path.Command('G0', { 'A': 0.0, 'F': self.axialRapid })) self.commandlist.append( Path.Command('G0', { 'B': 0.0, 'F': self.axialRapid })) self.useTempJobClones( 'Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user for ton in self.tempObjectNames: # remove temporary objects by name FreeCAD.ActiveDocument.removeObject(ton) PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims
def _buildPathLibarea(self, obj, edgelist): import PathScripts.PathKurveUtils as PathKurveUtils # import math # import area output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' if obj.StartPoint and obj.UseStartPoint: startpoint = obj.StartPoint else: startpoint = None if obj.EndPoint and obj.UseEndPoint: endpoint = obj.EndPoint else: endpoint = None PathKurveUtils.output('mem') PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed) output = "" output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" curve = PathKurveUtils.makeAreaCurve(edgelist, obj.Direction, startpoint, endpoint) '''The following line uses a profile function written for use with FreeCAD. It's clean but incomplete. It doesn't handle print("x = " + str(point.x)) print("y - " + str(point.y)) holding tags start location CRC or probably other features in heekscnc''' # output += PathKurveUtils.profile(curve, side, radius, vf, hf, offset_extra, rapid_safety_space, clearance, start_depth, step_down, final_depth, use_CRC) '''The following calls the original procedure from h toolLoad = obj.activeTCeekscnc profile function. This, in turn, calls many other procedures to modify the profile. This procedure is hacked together from heekscnc and has not been thoroughly reviewed or understood for FreeCAD. It can probably be thoroughly optimized and improved but it'll take a smarter mind than mine to do it. -sliptonic Feb16''' roll_radius = 2.0 extend_at_start = 0.0 extend_at_end = 0.0 lead_in_line_len = 0.0 lead_out_line_len = 0.0 ''' Right here, I need to know the Holding Tags group from the tree that refers to this profile operation and build up the tags for PathKurve Utils. I need to access the location vector, length, angle in radians and height. ''' depthparams = depth_params( obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value, None) PathKurveUtils.profile2( curve, obj.Side, self.radius, self.vertFeed, self.horizFeed, self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius, None, None, depthparams, extend_at_start, extend_at_end, lead_in_line_len, lead_out_line_len) output += PathKurveUtils.retrieve_gcode() return output
def makeAreaCurve(edges, direction, startpt=None, endpt=None): curveobj = area.Curve() cleanededges = Part.__sortEdges__(PathUtils.cleanedges(edges, 0.01)) # for e in cleanededges: # print str(e.valueAt(e.FirstParameter)) + "," + # str(e.valueAt(e.LastParameter)) edgelist = [] if len(cleanededges) == 1: # user selected a single edge. edgelist = cleanededges else: # edgelist = [] #Multiple edges. Need to sequence the vetexes. # First get the first segment oriented correctly. # We first compare the last parameter of the first segment to see if it # matches either end of the second segment. If not, it must need # flipping. if cleanededges[0].valueAt(cleanededges[0].LastParameter) in [ cleanededges[1].valueAt(cleanededges[1].FirstParameter), cleanededges[1].valueAt(cleanededges[1].LastParameter) ]: edge0 = cleanededges[0] else: edge0 = PathUtils.reverseEdge(cleanededges[0]) edgelist.append(edge0) # Now iterate the rest of the edges matching the last parameter of the # previous segment. for edge in cleanededges[1:]: if edge.valueAt(edge.FirstParameter) == edgelist[-1].valueAt( edgelist[-1].LastParameter): nextedge = edge else: nextedge = PathUtils.reverseEdge(edge) edgelist.append(nextedge) # print "makeareacurve 87: " + "area.Point(" + # str(edgelist[0].Vertexes[0].X) + ", " + # str(edgelist[0].Vertexes[0].Y)+")" curveobj.append( area.Point(edgelist[0].Vertexes[0].X, edgelist[0].Vertexes[0].Y)) # seglist =[] # if direction=='CW': # edgelist.reverse() # for e in edgelist: # seglist.append(PathUtils.reverseEdge(e)) #swap end points on every segment # else: # for e in edgelist: # seglist.append(e) for s in edgelist: curveobj.append(makeAreaVertex(s)) if startpt: # future nearest point code yet to be worked out -fixme # v1 = Vector(startpt.X,startpt.Y,startpt.Z) # perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge) # perppoint1 = DraftGeomUtils.findDistance(v1,firstedge) # if perppoint1: # curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y)) # else: # curveobj.ChangeStart(area.Point(startpt.X,startpt.Y)) curveobj.ChangeStart(area.Point(startpt.x, startpt.y)) if endpt: # future nearest point code yet to be worked out -fixme # v2 = Vector(endpt.X,endpt.Y,endpt.Z) # perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge) # if perppoint2: # curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y)) # else: # curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y)) curveobj.ChangeEnd(area.Point(endpt.x, endpt.y)) if curveobj.IsClockwise() and direction == 'CCW': curveobj.Reverse() elif not curveobj.IsClockwise() and direction == 'CW': curveobj.Reverse() return curveobj
def Activated(self): import Path from PathScripts import PathUtils, PathProfile, PathProject prjexists = False selection = PathSelection.multiSelect() if not selection: return # if everything is ok, execute and register the transaction in the undo/redo stack FreeCAD.ActiveDocument.openTransaction( translate("PathProfile", "Create Profile")) FreeCADGui.addModule("PathScripts.PathProfile") obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Profile") PathProfile.ObjectProfile(obj) PathProfile.ViewProviderProfile(obj.ViewObject) obj.Base = (FreeCAD.ActiveDocument.getObject(selection['objname'])) if selection['facenames']: #FreeCAD.Console.PrintMessage('There are edges selected\n') obj.Face1 = (FreeCAD.ActiveDocument.getObject( selection['objname']), selection['facenames'][0]) if len(selection['facenames']) > 1: obj.Face2 = (FreeCAD.ActiveDocument.getObject( selection['objname']), selection['facenames'][-1]) if selection['edgenames']: #FreeCAD.Console.PrintMessage('There are edges selected\n') obj.Edge1 = (FreeCAD.ActiveDocument.getObject( selection['objname']), (selection['edgenames'][0])) if len(selection['edgenames']) > 1: obj.Edge2 = (FreeCAD.ActiveDocument.getObject( selection['objname']), (selection['edgenames'][-1])) if selection['pointlist']: FreeCADGui.doCommand('from FreeCAD import Vector') stptX, stptY, stptZ = selection['pointlist'][0].X, selection[ 'pointlist'][0].Y, selection['pointlist'][0].Z obj.StartPoint = Vector((stptX), (stptY), (stptZ)) if len( selection['pointlist'] ) > 1: # we have more than one point so we have an end point endptX, endptY, endptZ = selection['pointlist'][ -1].X, selection['pointlist'][-1].Y, selection[ 'pointlist'][-1].Z obj.EndPoint = Vector(endptX, endptY, endptZ) if selection['pathwire'].isClosed(): obj.PathClosed = True if selection['clockwise']: obj.Side = "Left" obj.Direction = "CW" elif selection['clockwise'] == False: obj.Side = "Right" obj.Direction = "CCW" else: obj.Side = "On" obj.Direction = "CCW" obj.PathClosed = False ZMax = obj.Base[0].Shape.BoundBox.ZMax ZMin = obj.Base[0].Shape.BoundBox.ZMin obj.StepDown.Value = 1.0 obj.StartDepth.Value = ZMax - obj.StepDown.Value obj.FinalDepth.Value = ZMin - 1.0 obj.ClearanceHeight.Value = ZMax + 5.0 obj.SegLen.Value = 0.5 obj.Active = True obj.ViewObject.ShowFirstRapid = False project = PathUtils.addToProject(obj) tl = PathUtils.changeTool(obj, project) if tl: obj.ToolNumber = tl FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute()
def onDelete(self, arg1=None, arg2=None): '''this makes sure that the base operation is added back to the project and visible''' FreeCADGui.ActiveDocument.getObject( arg1.Object.Base.Name).Visibility = True PathUtils.addToJob(arg1.Object.Base) return True
def onChanged(self, obj, prop): if not 'Restore' in obj.State and prop == "Radius": job = PathUtils.findParentJob(obj) if job: job.Proxy.setCenterOfRotation(self.center(obj))
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 circularHoleExecute(self, obj, holes): """circularHoleExecute(obj, holes) ... generate drill operation for each hole in holes.""" PathLog.track() machine = PathMachineState.MachineState() self.commandlist.append(Path.Command("(Begin Drilling)")) # rapid to clearance height command = Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) machine.addCommand(command) self.commandlist.append(command) self.commandlist.append(Path.Command("G90")) # Absolute distance mode # Calculate offsets to add to target edge endoffset = 0.0 if obj.ExtraOffset == "Drill Tip": endoffset = PathUtils.drillTipLength(self.tool) elif obj.ExtraOffset == "2x Drill Tip": endoffset = PathUtils.drillTipLength(self.tool) * 2 # http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g98-g99 self.commandlist.append(Path.Command(obj.ReturnLevel)) holes = PathUtils.sort_locations(holes, ["x", "y"]) # This section is technical debt. The computation of the # target shapes should be factored out for re-use. # This will likely mean refactoring upstream CircularHoleBase to pass # spotshapes instead of holes. startHeight = obj.StartDepth.Value + self.job.SetupSheet.SafeHeightOffset.Value edgelist = [] for hole in holes: v1 = FreeCAD.Vector(hole["x"], hole["y"], obj.StartDepth.Value) v2 = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value - endoffset) edgelist.append(Part.makeLine(v1, v2)) # iterate the edgelist and generate gcode for edge in edgelist: PathLog.debug(edge) # move to hole location startPoint = edge.Vertexes[0].Point command = Path.Command("G0", { "X": startPoint.x, "Y": startPoint.y }) self.commandlist.append(command) machine.addCommand(command) command = Path.Command("G0", {"Z": startHeight}) self.commandlist.append(command) machine.addCommand(command) # command = Path.Command("G1", {"Z": obj.StartDepth.Value}) # self.commandlist.append(command) # machine.addCommand(command) # Technical Debt: We are assuming the edges are aligned. # This assumption should be corrected and the necessary rotations # performed to align the edge with the Z axis for drilling # Perform drilling dwelltime = obj.DwellTime if obj.DwellEnabled else 0.0 peckdepth = obj.PeckDepth.Value if obj.PeckEnabled else 0.0 repeat = 1 # technical debt: Add a repeat property for user control chipBreak = (obj.chipBreakEnabled and obj.PeckEnabled) try: drillcommands = generator.generate(edge, dwelltime, peckdepth, repeat, obj.RetractHeight.Value, chipBreak=chipBreak) except ValueError as e: # any targets that fail the generator are ignored PathLog.info(e) continue for command in drillcommands: self.commandlist.append(command) machine.addCommand(command) # Cancel canned drilling cycle self.commandlist.append(Path.Command("G80")) command = Path.Command("G0", {"Z": obj.SafeHeight.Value}) self.commandlist.append(command) machine.addCommand(command) # Apply feedrates to commands PathFeedRate.setFeedRate(self.commandlist, obj.ToolController)
def _waterline(self, obj, s, bb): import time import ocl def drawLoops(loops): nloop = 0 pp = [] pp.append(Path.Command("(waterline begin)")) for loop in loops: p = loop[0] pp.append(Path.Command("(loop begin)")) pp.append( Path.Command('G0', { "Z": obj.SafeHeight.Value, 'F': self.vertRapid })) pp.append( Path.Command('G0', { 'X': p.x, "Y": p.y, 'F': self.horizRapid })) pp.append(Path.Command('G1', {"Z": p.z, 'F': self.vertFeed})) for p in loop[1:]: pp.append( Path.Command('G1', { 'X': p.x, "Y": p.y, "Z": p.z, 'F': self.horizFeed })) # zheight = p.z p = loop[0] pp.append( Path.Command('G1', { 'X': p.x, "Y": p.y, "Z": p.z, 'F': self.horizFeed })) pp.append(Path.Command("(loop end)")) print(" loop ", nloop, " with ", len(loop), " points") nloop = nloop + 1 pp.append(Path.Command("(waterline end)")) return pp depthparams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown, obj.FinishDepth.Value, obj.FinalDepth.Value) t_before = time.time() zheights = [i for i in depthparams] wl = ocl.Waterline() wl.setSTL(s) cutter = ocl.CylCutter(obj.ToolController.Tool.Diameter, 5) wl.setCutter(cutter) # this should be smaller than the smallest details in the STL file wl.setSampling(obj.SampleInterval) # AdaptiveWaterline() also has settings for minimum sampling interval # (see c++ code) all_loops = [] print("zheights: {}".format(zheights)) for zh in zheights: print("calculating Waterline at z= ", zh) wl.reset() wl.setZ(zh) # height for this waterline wl.run() all_loops.append(wl.getLoops()) t_after = time.time() calctime = t_after - t_before n = 0 output = [] for loops in all_loops: # at each z-height, we may get many loops print(" %d/%d:" % (n, len(all_loops))) output.extend(drawLoops(loops)) n = n + 1 print("(" + str(calctime) + ")") return output
def execute(self, obj): import MeshPart FreeCAD.Console.PrintWarning( translate("PathSurface", "Hold on. This might take a minute.\n")) output = "" if obj.Comment != "": output += '(' + str(obj.Comment) + ')\n' toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) self.radius = tool.Diameter / 2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name if obj.UserLabel == "": obj.Label = obj.Name + " :" + obj.ToolDescription else: obj.Label = obj.UserLabel + " :" + obj.ToolDescription output += "(" + obj.Label + ")" output += "(Compensated Tool Path. Diameter: " + str( self.radius * 2) + ")" if obj.Base: for b in obj.Base: if obj.Algorithm in ['OCL Dropcutter', 'OCL Waterline']: try: import ocl except: FreeCAD.Console.PrintError( translate( "PathSurface", "This operation requires OpenCamLib to be installed.\n" )) return mesh = b[0] if mesh.TypeId.startswith('Mesh'): mesh = mesh.Mesh bb = mesh.BoundBox else: bb = mesh.Shape.BoundBox mesh = MeshPart.meshFromShape(mesh.Shape, MaxLength=2) s = ocl.STLSurf() for f in mesh.Facets: p = f.Points[0] q = f.Points[1] r = f.Points[2] t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), ocl.Point(q[0], q[1], q[2]), ocl.Point(r[0], r[1], r[2])) s.addTriangle(t) if obj.Algorithm == 'OCL Dropcutter': output = self._dropcutter(obj, s, bb) elif obj.Algorithm == 'OCL Waterline': output = self._waterline(obj, s, bb) if obj.Active: path = Path.Path(output) obj.Path = path obj.ViewObject.Visibility = True else: path = Path.Path("(inactive operation)") obj.Path = path obj.ViewObject.Visibility = False