From cb47eb871294667ae0ec0661d8dce77e690b2115 Mon Sep 17 00:00:00 2001 From: pekkaroi Date: Sat, 7 Jul 2018 12:05:33 +0300 Subject: [PATCH] Path: Surface op improvements for drop cutter algo --- src/Mod/Path/PathScripts/PathSurface.py | 148 ++++++++++++++++-------- 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 09975c869f..e5d85580d6 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -26,10 +26,10 @@ from __future__ import print_function import FreeCAD import MeshPart -#import Part +# import Part import Path import PathScripts.PathLog as PathLog -#import PathScripts.PathPocketBase as PathPocketBase +# import PathScripts.PathPocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils import PathScripts.PathOp as PathOp @@ -52,6 +52,7 @@ else: def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) + class ObjectSurface(PathOp.ObjectOp): '''Proxy object for Surfacing operation.''' def baseObject(self): @@ -64,28 +65,51 @@ class ObjectSurface(PathOp.ObjectOp): # return 0 def opFeatures(self, obj): '''opFeatures(obj) ... return all standard features and edges based geomtries''' - return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureFinishDepth; + return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown def initOperation(self, obj): '''initPocketOp(obj) ... create facing specific properties''' obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The library to use to generate the path")) - obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] + obj.addProperty("App::PropertyEnumeration", "DropCutterDir", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")) + obj.addProperty("App::PropertyEnumeration", "BoundBox", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) + obj.addProperty("App::PropertyVector", "DropCutterExtraOffset", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")) + obj.addProperty("App::PropertyFloat", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) + obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) obj.addProperty("App::PropertyFloatConstraint", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) + obj.BoundBox = ['Stock', 'BaseBoundBox'] + obj.DropCutterDir = ['X', 'Y'] + obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] obj.SampleInterval = (0.04, 0.01, 1.0, 0.01) - def opExecute(self, obj): - '''opExecute(obj) ... process engraving operation''' - PathLog.track() + self.setEditorProperties(obj) + def setEditorProperties(self, obj): + if obj.Algorithm == 'OCL Dropcutter': + obj.setEditorMode('DropCutterDir', 0) + obj.setEditorMode('DropCutterExtraOffset', 0) + else: + obj.setEditorMode('DropCutterDir', 2) + obj.setEditorMode('DropCutterExtraOffset', 2) + + def onChanged(self, obj, prop): + if prop == "Algorithm": + self.setEditorProperties(obj) + + def opExecute(self, obj): + '''opExecute(obj) ... process surface operation''' + PathLog.track() + print("StepOver is " + str(obj.StepOver)) + if obj.StepOver > 100: + obj.StepOver = 100 + if obj.StepOver < 0.001: + obj.StepOver = 0.001 output = "" if obj.Comment != "": - output += '(' + str(obj.Comment)+')\n' - + output += '(' + str(obj.Comment) + ')\n' output += "(" + obj.Label + ")" output += "(Compensated Tool Path. Diameter: " + str(obj.ToolController.Tool.Diameter) + ")" - parentJob = PathUtils.findParentJob(obj) if parentJob is None: return @@ -97,7 +121,7 @@ class ObjectSurface(PathOp.ObjectOp): import ocl except: FreeCAD.Console.PrintError( - translate("Path_Surface", "This operation requires OpenCamLib to be installed.")+"\n") + translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n") return if self.baseobject.TypeId.startswith('Mesh'): @@ -111,16 +135,20 @@ class ObjectSurface(PathOp.ObjectOp): deflection = PathPreferences.defaultGeometryTolerance() self.baseobject.Shape.tessellate(0.5) mesh = MeshPart.meshFromShape(self.baseobject.Shape, Deflection=deflection) - - bb = mesh.BoundBox + if obj.BoundBox == "BaseBoundBox": + bb = mesh.BoundBox + else: + bb = parentJob.Stock.Shape.BoundBox 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])) + # offset the triangle in Z with DepthOffset + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), + ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), + ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) s.addTriangle(t) if obj.Algorithm == 'OCL Dropcutter': @@ -137,25 +165,25 @@ class ObjectSurface(PathOp.ObjectOp): def drawLoops(loops): nloop = 0 pp = [] - pp.append(Path.Command("(waterline begin)" )) + pp.append(Path.Command("(waterline begin)")) for loop in loops: p = loop[0] - pp.append(Path.Command("(loop begin)" )) + 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 + # 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)" )) + pp.append(Path.Command("(loop end)")) print(" loop ", nloop, " with ", len(loop), " points") nloop = nloop + 1 - pp.append(Path.Command("(waterline end)" )) + pp.append(Path.Command("(waterline end)")) return pp @@ -167,7 +195,12 @@ class ObjectSurface(PathOp.ObjectOp): wl = ocl.Waterline() wl.setSTL(s) - cutter = ocl.CylCutter(obj.ToolController.Tool.Diameter, 5) + + if obj.ToolController.Tool.ToolType == 'BallEndMill': + cutter = ocl.BallCutter(obj.ToolController.Tool.Diameter, 5) # TODO: 5 represents cutting edge height. Should be replaced with the data from toolcontroller? + else: + 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) @@ -195,37 +228,55 @@ class ObjectSurface(PathOp.ObjectOp): def _dropcutter(self, obj, s, bb): import ocl import time + if obj.ToolController.Tool.ToolType == 'BallEndMill': + cutter = ocl.BallCutter(obj.ToolController.Tool.Diameter, 5) # TODO: 5 represents cutting edge height. Should be replaced with the data from toolcontroller? + else: + cutter = ocl.CylCutter(obj.ToolController.Tool.Diameter, 5) - cutter = ocl.CylCutter(obj.ToolController.Tool.Diameter, 5) pdc = ocl.PathDropCutter() # create a pdc pdc.setSTL(s) pdc.setCutter(cutter) - pdc.minimumZ = 0.25 + pdc.setZ(obj.FinalDepth.Value + obj.DepthOffset.Value) # set minimumZ 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 + # the max and min XY area of the operation + xmin = bb.XMin - obj.DropCutterExtraOffset.x + xmax = bb.XMax + obj.DropCutterExtraOffset.x + ymin = bb.YMin - obj.DropCutterExtraOffset.y + ymax = bb.YMax + obj.DropCutterExtraOffset.y 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 + if obj.DropCutterDir == 'Y': + Ny = int(bb.YLength / (cutter.getDiameter() * (obj.StepOver / 100))) + dy = float(ymax - ymin) / Ny # the y step-over - path.append(l) # add the line to the path + # 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 + else: + Nx = int(bb.XLength / (cutter.getDiameter() * (obj.StepOver / 100))) + dx = float(xmax - xmin) / Nx # the y step-over + + # add Line objects to the path in this loop + for n in xrange(0, Nx): + x = xmin + n * dx + p1 = ocl.Point(x, ymin, 0) # start-point of line + p2 = ocl.Point(x, ymax, 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) @@ -253,17 +304,18 @@ class ObjectSurface(PathOp.ObjectOp): def pocketInvertExtraOffset(self): return True - def areaOpSetDefaultValues(self, obj): - '''areaOpSetDefaultValues(obj) ... initialize mill facing properties''' - # obj.StepOver = 50 - # obj.ZigZagAngle = 45.0 + def opSetDefaultValues(self, obj): + '''opSetDefaultValues(obj) ... initialize defauts''' + # obj.ZigZagAngle = 45.0 + obj.StepOver = 50 # 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 + obj.OpStartDepth = d.start_depth + obj.OpFinalDepth = d.final_depth + def Create(name): '''Create(name) ... Creates and returns a Surface operation.'''