diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py index 6b7b78790b..fedef35959 100644 --- a/src/Mod/Path/PathScripts/PathContour.py +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -23,15 +23,14 @@ # *************************************************************************** import FreeCAD -#from FreeCAD import Vector import Path import PathScripts.PathLog as PathLog from PathScripts import PathUtils from PathScripts.PathUtils import depth_params from PySide import QtCore -#import TechDraw import ArchPanel import Part +from PathScripts.PathUtils import waiting_effects LOG_MODULE = 'PathContour' PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) @@ -123,15 +122,15 @@ class ObjectContour: obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 + @waiting_effects def _buildPathArea(self, obj, baseobject, start=None): PathLog.track() - profile = Path.Area() profile.setPlane(Part.makeCircle(10)) profile.add(baseobject) profileparams = {'Fill': 0, - 'Coplanar' : 0} + 'Coplanar': 0} if obj.UseComp is False: profileparams['Offset'] = 0.0 @@ -219,17 +218,14 @@ class ObjectContour: for shape in shapes: f = Part.makeFace([shape], 'Part::FaceMakerSimple') thickness = baseobject.Group[0].Source.Thickness - contourshape = f.extrude(FreeCAD.Vector(0,0, thickness)) + contourshape = f.extrude(FreeCAD.Vector(0, 0, thickness)) try: commandlist.extend(self._buildPathArea(obj, contourshape).Commands) except Exception as e: print(e) FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") else: - #fixbase = baseobject.Shape.copy() - #fixbase.fix(0.00001, 0.00001, 0.00001) env = PathUtils.getEnvelope(baseobject.Shape, obj.StartDepth) - try: commandlist.extend(self._buildPathArea(obj, env).Commands) except Exception as e: diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index e7c8c97c73..5d33445400 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -30,6 +30,7 @@ from PathScripts import PathUtils import Part from FreeCAD import Vector import PathScripts.PathLog as PathLog +from PathScripts.PathUtils import waiting_effects LOG_MODULE = 'PathMillFace' PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) @@ -158,6 +159,7 @@ class ObjectFace: return self.getStock(o) return None + @waiting_effects def _buildPathArea(self, obj, baseobject): """build the face path using PathArea""" from PathScripts.PathUtils import depth_params @@ -170,28 +172,27 @@ class ObjectFace: stepover = (self.radius * 2) * (float(obj.StepOver)/100) pocketparams = {'Fill': 0, - 'Coplanar' : 0, - 'PocketMode': 4, - 'SectionCount': -1, - 'Angle': obj.ZigZagAngle, - 'FromCenter': (obj.StartAt == "Center"), - 'PocketStepover': stepover, - 'PocketExtraOffset': obj.PassExtension.Value } + 'Coplanar': 0, + 'PocketMode': 4, + 'SectionCount': -1, + 'Angle': obj.ZigZagAngle, + 'FromCenter': (obj.StartAt == "Center"), + 'PocketStepover': stepover, + 'PocketExtraOffset': obj.PassExtension.Value} depthparams = depth_params( - clearance_height = obj.ClearanceHeight.Value, - rapid_safety_space = obj.SafeHeight.Value, - start_depth = obj.StartDepth.Value, - step_down = obj.StepDown, - z_finish_step = obj.FinishDepth.Value, - final_depth = obj.FinalDepth.Value, - user_depths = None) + clearance_height=obj.ClearanceHeight.Value, + rapid_safety_space=obj.SafeHeight.Value, + start_depth=obj.StartDepth.Value, + step_down=obj.StepDown, + z_finish_step=obj.FinishDepth.Value, + final_depth=obj.FinalDepth.Value, + user_depths=None) boundary.setParams(**pocketparams) sections = boundary.makeSections(mode=0, project=False, heights=depthparams.get_depths()) shapelist = [sec.getShape() for sec in sections] - params = {'shapes': shapelist, 'feedrate': self.horizFeed, 'feedrate_v': self.vertFeed, @@ -204,7 +205,6 @@ class ObjectFace: # else: # params['orientation'] = 0 - PathLog.debug("Generating Path with params: {}".format(params)) pp = Path.fromShapes(**params) @@ -277,8 +277,6 @@ class ObjectFace: print(e) FreeCAD.Console.PrintWarning(translate("PathMillFace", "The selected settings did not produce a valid path.\n")) - #FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") - if obj.Active: path = Path.Path(commandlist) obj.Path = path @@ -368,7 +366,6 @@ class CommandPathMillFace: FreeCADGui.doCommand('obj.StartDepth = ' + str(ztop + 1)) FreeCADGui.doCommand('obj.FinalDepth =' + str(ztop)) FreeCADGui.doCommand('obj.ZigZagAngle = 45.0') - #FreeCADGui.doCommand('obj.UseZigZag = True') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)') snippet = ''' @@ -469,7 +466,6 @@ class TaskPanel: self.form.boundaryShape.setCurrentIndex(index) self.form.boundaryShape.blockSignals(False) - index = self.form.offsetpattern.findText( self.obj.OffsetPattern, QtCore.Qt.MatchFixedString) if index >= 0: @@ -614,8 +610,6 @@ class TaskPanel: self.form.boundaryShape.currentIndexChanged.connect(self.getFields) self.form.stepOverPercent.editingFinished.connect(self.getFields) self.form.offsetpattern.currentIndexChanged.connect(self.getFields) - #self.form.useZigZag.clicked.connect(self.getFields) - #self.form.zigZagUnidirectional.clicked.connect(self.getFields) self.form.zigZagAngle.editingFinished.connect(self.getFields) self.form.uiToolController.currentIndexChanged.connect(self.getFields) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index f2ae43cd6b..4f797bb3c6 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -28,6 +28,7 @@ from PathScripts import PathUtils from PathScripts.PathUtils import depth_params from DraftGeomUtils import findWires import PathScripts.PathLog as PathLog +from PathScripts.PathUtils import waiting_effects """Path Profile from Edges Object and Command""" @@ -107,8 +108,7 @@ class ObjectProfile: return None def onChanged(self, obj, prop): - if prop == "UserLabel": - obj.Label = obj.UserLabel + " :" + obj.ToolDescription + pass def addprofilebase(self, obj, ss, sub=""): baselist = obj.Base @@ -144,6 +144,7 @@ class ObjectProfile: obj.Base = baselist self.execute(obj) + @waiting_effects def _buildPathLibarea(self, obj, edgelist): import PathScripts.PathKurveUtils as PathKurveUtils # import math diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 7bd3ad0b3c..b8433fe4c5 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -31,13 +31,30 @@ import PathScripts from PathScripts import PathJob import numpy import PathLog -#from math import pi from FreeCAD import Vector import Path +from PySide import QtCore +from PySide import QtGui LOG_MODULE = 'PathUtils' PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) -#PathLog.trackModule('PathUtils') +# PathLog.trackModule('PathUtils') + + +def waiting_effects(function): + def new_function(*args, **kwargs): + QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + res = None + try: + res = function(*args, **kwargs) + except Exception as e: + raise e + print("Error {}".format(e.args[0])) + finally: + QtGui.QApplication.restoreOverrideCursor() + return res + return new_function + def cleanedges(splines, precision): '''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths. @@ -74,8 +91,10 @@ def cleanedges(splines, precision): return edges + def curvetowire(obj, steps): '''adapted from DraftGeomUtils, because the discretize function changed a bit ''' + points = obj.copy().discretize(Distance=eval('steps')) p0 = points[0] edgelist = [] @@ -85,6 +104,7 @@ def curvetowire(obj, steps): p0 = p return edgelist + def isDrillable(obj, candidate, tooldiameter=None): PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(obj, candidate, tooldiameter)) drillable = False @@ -99,13 +119,13 @@ def isDrillable(obj, candidate, tooldiameter=None): v1 = edge.Vertexes[1].Point if (v1.sub(v0).x == 0) and (v1.sub(v0).y == 0): # vector of top center - lsp = Vector(face.BoundBox.Center.x,face.BoundBox.Center.y, face.BoundBox.ZMax) + lsp = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMax) # vector of bottom center - lep = Vector(face.BoundBox.Center.x,face.BoundBox.Center.y, face.BoundBox.ZMin) + lep = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMin) if obj.isInside(lsp, 0, False) or obj.isInside(lep, 0, False): drillable = False # eliminate elliptical holes - elif not hasattr(face.Surface, "Radius"): #abs(face.BoundBox.XLength - face.BoundBox.YLength) > 0.05: + elif not hasattr(face.Surface, "Radius"): drillable = False else: if tooldiameter is not None: @@ -116,7 +136,7 @@ def isDrillable(obj, candidate, tooldiameter=None): for edge in candidate.Edges: if isinstance(edge.Curve, Part.Circle) and edge.isClosed(): PathLog.debug("candidate is a circle or ellipse") - if not hasattr(edge.Curve, "Radius"): #bbdiff > 0.05: + if not hasattr(edge.Curve, "Radius"): PathLog.debug("No radius. Ellipse.") drillable = False else: @@ -128,13 +148,16 @@ def isDrillable(obj, candidate, tooldiameter=None): PathLog.debug("candidate is drillable: {}".format(drillable)) return drillable + # fixme set at 4 decimal places for testing def fmt(val): return format(val, '.4f') + def segments(poly): ''' A sequence of (x,y) numeric coordinates pairs ''' return zip(poly, poly[1:] + [poly[0]]) + def loopdetect(obj, edge1, edge2): ''' Returns a loop wire that includes the two edges. @@ -149,15 +172,16 @@ def loopdetect(obj, edge1, edge2): for wire in obj.Shape.Wires: for e in wire.Edges: if e.hashCode() == edge1.hashCode(): - candidates.append((wire.hashCode(),wire)) + candidates.append((wire.hashCode(), wire)) if e.hashCode() == edge2.hashCode(): - candidates.append((wire.hashCode(),wire)) - loop = set([x for x in candidates if candidates.count(x) > 1]) #return the duplicate item + candidates.append((wire.hashCode(), wire)) + loop = set([x for x in candidates if candidates.count(x) > 1]) # return the duplicate item if len(loop) != 1: return None loopwire = next(x for x in loop)[1] return loopwire + def filterArcs(arcEdge): '''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list ''' s = arcEdge @@ -175,10 +199,10 @@ def filterArcs(arcEdge): arcstpt = s.valueAt(s.FirstParameter) arcmid = s.valueAt( (s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter) - arcquad1 = s.valueAt((s.LastParameter - s.FirstParameter) - * 0.25 + s.FirstParameter) # future midpt for arc1 - arcquad2 = s.valueAt((s.LastParameter - s.FirstParameter) - * 0.75 + s.FirstParameter) # future midpt for arc2 + arcquad1 = s.valueAt((s.LastParameter - s.FirstParameter) * + 0.25 + s.FirstParameter) # future midpt for arc1 + arcquad2 = s.valueAt((s.LastParameter - s.FirstParameter) * + 0.75 + s.FirstParameter) # future midpt for arc2 arcendpt = s.valueAt(s.LastParameter) # reconstruct with 2 arcs arcseg1 = Part.ArcOfCircle(arcstpt, arcquad1, arcmid) @@ -194,6 +218,7 @@ def filterArcs(arcEdge): pass return splitlist + def getEnvelope(partshape, stockheight=None): ''' getEnvelop(partshape, stockheight=None) @@ -211,6 +236,7 @@ def getEnvelope(partshape, stockheight=None): else: return sec.extrude(FreeCAD.Vector(0, 0, partshape.BoundBox.ZMax)) + def getEnvelopeTD(partshape, stockheight=None): ''' getEnvelopTD(partshape, stockheight=None) @@ -222,12 +248,13 @@ def getEnvelopeTD(partshape, stockheight=None): stockheight = float ''' import TechDraw - sec = Part.Face(TechDraw.findShapeOutline(partshape, 1 , FreeCAD.Vector(0,0,1))) + sec = Part.Face(TechDraw.findShapeOutline(partshape, 1, FreeCAD.Vector(0, 0, 1))) if stockheight is not None: return sec.extrude(FreeCAD.Vector(0, 0, stockheight)) else: return sec.extrude(FreeCAD.Vector(0, 0, partshape.BoundBox.ZMax)) + def reverseEdge(e): if geomType(e) == "Circle": arcstpt = e.valueAt(e.FirstParameter) @@ -242,6 +269,7 @@ def reverseEdge(e): return newedge + def changeTool(obj, job): tlnum = 0 for p in job.Group: @@ -257,6 +285,7 @@ def changeTool(obj, job): if g == obj: return tlnum + def getToolControllers(obj): '''returns all the tool controllers''' controllers = [] @@ -272,6 +301,7 @@ def getToolControllers(obj): controllers.append(g) return controllers + def findToolController(obj, name=None): '''returns a tool controller with a given name. If no name is specified, returns the first controller. @@ -279,7 +309,7 @@ def findToolController(obj, name=None): PathLog.track('name: {}'.format(name)) c = None - #First check if a user has selected a tool controller in the tree. Return the first one and remove all from selection + # First check if a user has selected a tool controller in the tree. Return the first one and remove all from selection for sel in FreeCADGui.Selection.getSelectionEx(): if hasattr(sel.Object, 'Proxy'): if isinstance(sel.Object.Proxy, PathScripts.PathLoadTool.LoadTool): @@ -294,16 +324,16 @@ def findToolController(obj, name=None): if len(controllers) == 0: return None - #If there's only one in the job, use it. + # If there's only one in the job, use it. if len(controllers) == 1: if name is None or name == controllers[0].Label: tc = controllers[0] else: tc = None - elif name is not None: #More than one, make the user choose. + elif name is not None: # More than one, make the user choose. tc = [i for i in controllers if i.Label == name][0] else: - #form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgTCChooser.ui") + # form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgTCChooser.ui") form = FreeCADGui.PySideUic.loadUi(":/panels/DlgTCChooser.ui") mylist = [i.Label for i in controllers] form.uiToolController.addItems(mylist) @@ -314,6 +344,7 @@ def findToolController(obj, name=None): tc = [i for i in controllers if i.Label == form.uiToolController.currentText()][0] return tc + def findParentJob(obj): '''retrieves a parent job object for an operation or other Path object''' PathLog.track() @@ -326,7 +357,8 @@ def findParentJob(obj): return grandParent return None -def GetJobs(jobname = None): + +def GetJobs(jobname=None): '''returns all jobs in the current document. If name is given, returns that job''' PathLog.track() jobs = [] @@ -340,7 +372,8 @@ def GetJobs(jobname = None): jobs.append(o) return jobs -def addToJob(obj, jobname = None): + +def addToJob(obj, jobname=None): '''adds a path object to a job obj = obj jobname = None''' @@ -360,7 +393,7 @@ def addToJob(obj, jobname = None): elif len(jobs) == 1: job = jobs[0] else: - #form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgJobChooser.ui") + # form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DlgJobChooser.ui") form = FreeCADGui.PySideUic.loadUi(":/panels/DlgJobChooser.ui") mylist = [i.Name for i in jobs] form.cboProject.addItems(mylist) @@ -376,6 +409,7 @@ def addToJob(obj, jobname = None): job.Group = g return job + def rapid(x=None, y=None, z=None): """ Returns gcode string to perform a rapid move.""" retstr = "G00" @@ -390,6 +424,7 @@ def rapid(x=None, y=None, z=None): return "" return retstr + "\n" + def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0): """ Return gcode string to perform a linear feed.""" global feedxy @@ -410,6 +445,7 @@ def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0): return "" return retstr + "\n" + def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False): """ Return gcode string to perform an arc. @@ -446,6 +482,7 @@ def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False): return retstr + "\n" + def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed): """ Return gcode string to perform helical entry move. @@ -492,6 +529,7 @@ def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed return helixCmds + def rampPlunge(edge, rampangle, destZ, startZ): """ Return gcode string to linearly ramp down to milling level.