From 3b5d2d2e7fd70d87f16e067b5bfda0488fc759a1 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 30 May 2020 09:56:56 -0500 Subject: [PATCH 1/4] Path: Add missing `Setup()` properties; Visual organization --- src/Mod/Path/PathScripts/PathPocketBase.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketBase.py b/src/Mod/Path/PathScripts/PathPocketBase.py index 702ba5b8d4..62a9c70bf1 100644 --- a/src/Mod/Path/PathScripts/PathPocketBase.py +++ b/src/Mod/Path/PathScripts/PathPocketBase.py @@ -71,17 +71,18 @@ class ObjectPocket(PathAreaOp.ObjectOp): # Pocket Properties obj.addProperty("App::PropertyEnumeration", "CutMode", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)")) - obj.CutMode = ['Climb', 'Conventional'] obj.addProperty("App::PropertyDistance", "ExtraOffset", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra offset to apply to the operation. Direction is operation dependent.")) obj.addProperty("App::PropertyEnumeration", "StartAt", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start pocketing at center or boundary")) - obj.StartAt = ['Center', 'Edge'] obj.addProperty("App::PropertyPercent", "StepOver", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Percent of cutter diameter to step over on each pass")) obj.addProperty("App::PropertyFloat", "ZigZagAngle", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern")) obj.addProperty("App::PropertyEnumeration", "OffsetPattern", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle'] obj.addProperty("App::PropertyBool", "MinTravel", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path")) obj.addProperty("App::PropertyBool", "KeepToolDown", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Attempts to avoid unnecessary retractions.")) + obj.CutMode = ['Climb', 'Conventional'] + obj.StartAt = ['Center', 'Edge'] + obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle'] + self.initPocketOp(obj) def areaOpRetractTool(self, obj): @@ -141,4 +142,7 @@ def SetupProperties(): setup.append('StepOver') setup.append('ZigZagAngle') setup.append('OffsetPattern') + setup.append('StartAt') + setup.append('MinTravel') + setup.append('KeepToolDown') return setup From 024d23bf319aec3ebbc6bb89ef70440046af31d7 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 30 May 2020 14:11:54 -0500 Subject: [PATCH 2/4] Path: Code cleanup Fix `If ... is True:` clauses. Remove unnecessary doc variables. --- src/Mod/Path/PathScripts/PathMillFace.py | 44 +++++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index b2f2f699c3..89e68d25f3 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -41,13 +41,10 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Mill Facing operation." __contributors__ = "russ4262 (Russell Johnson)" -__created__ = "2014" -__scriptVersion__ = "1d usable" -__lastModified__ = "2019-07-22 23:49 CST" PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) # Qt translation handling @@ -61,7 +58,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): def initPocketOp(self, obj): '''initPocketOp(obj) ... create facing specific properties''' obj.addProperty("App::PropertyEnumeration", "BoundaryShape", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Shape to use for calculating Boundary")) - obj.BoundaryShape = ['Perimeter', 'Boundbox', 'Stock'] + obj.BoundaryShape = ['Perimeter', 'Boundbox', 'Stock', 'Available'] obj.addProperty("App::PropertyBool", "ClearEdges", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clear edges of surface (Only applicable to BoundBox)")) if not hasattr(obj, 'ExcludeRaisedAreas'): @@ -103,6 +100,8 @@ class ObjectFace(PathPocketBase.ObjectPocket): # 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)) faces = [] @@ -119,20 +118,22 @@ class ObjectFace(PathPocketBase.ObjectPocket): 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 is True: + 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.error('The base subobject, "{0}," is not a face. Ignoring "{0}."'.format(sub)) + PathLog.warning('The base subobject, "{0}," is not a face. Ignoring "{0}."'.format(sub)) - if obj.ExcludeRaisedAreas is True and len(holes) > 0: + 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) @@ -152,8 +153,8 @@ class ObjectFace(PathPocketBase.ObjectPocket): bb = planeshape.BoundBox # Apply offset for clearing edges - offset = 0; - if obj.ClearEdges == True: + offset = 0 + if obj.ClearEdges: offset = self.radius + 0.1 bb.XMin = bb.XMin - offset @@ -164,7 +165,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): 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 is True and oneBase[1] is True: + if obj.ExcludeRaisedAreas and oneBase[1]: includedFaces = self.getAllIncludedFaces(oneBase[0], env, faceZ=minHeight) if len(includedFaces) > 0: includedShape = Part.makeCompound(includedFaces) @@ -174,7 +175,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): stock = PathUtils.findParentJob(obj).Stock.Shape env = stock - if obj.ExcludeRaisedAreas is True and oneBase[1] is True: + 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) @@ -184,13 +185,14 @@ class ObjectFace(PathPocketBase.ObjectPocket): else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) - if holeShape is not None: + if holeShape: PathLog.info("Processing holes...") holeEnv = PathUtils.getEnvelope(partshape=holeShape, depthparams=self.depthparams) newEnv = env.cut(holeEnv) - return [(newEnv, False)] + tup = newEnv, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value else: - return [(env, False)] + tup = env, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value + return [tup] def areaOpSetDefaultValues(self, obj, job): '''areaOpSetDefaultValues(obj, job) ... initialize mill facing properties''' @@ -215,7 +217,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): face = b.Shape.Faces[fi] for ei in range(0, len(face.Edges)): edge = face.Edges[ei] - if e.isSame(edge) is True: + if e.isSame(edge): if f is face: # Alternative: run loop to see if all edges are same pass # same source face, look for another @@ -226,7 +228,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): def getAllIncludedFaces(self, base, env, faceZ): included = [] - + eXMin = env.BoundBox.XMin eXMax = env.BoundBox.XMax eYMin = env.BoundBox.YMin @@ -257,12 +259,12 @@ class ObjectFace(PathPocketBase.ObjectPocket): fYMax = face.BoundBox.YMax # fZMin = face.BoundBox.ZMin fZMax = face.BoundBox.ZMax - + if fZMax > eZMin: - if isOverlap(fXMin, fXMax, eXMin, eXMax) is True: - if isOverlap(fYMin, fYMax, eYMin, eYMax) is True: + if isOverlap(fXMin, fXMax, eXMin, eXMax): + if isOverlap(fYMin, fYMax, eYMin, eYMax): incl = True - if incl is True: + if incl: included.append(face) return included From 4cd519107384fd1fb76cf3bf5c838d24db9980e1 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 30 May 2020 10:03:14 -0500 Subject: [PATCH 3/4] Path: Extend `ClearEdges` to `Perimeter` boundary usage. --- src/Mod/Path/PathScripts/PathMillFace.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 89e68d25f3..b8326f4cfb 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -182,8 +182,14 @@ class ObjectFace(PathPocketBase.ObjectPocket): includedShape = Part.makeCompound(includedFaces) includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams) env = stockEnv.cut(includedEnv) - else: - env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) + elif obj.BoundaryShape == 'Perimeter': + if obj.ClearEdges: + psZMin = planeshape.BoundBox.ZMin + ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.radius * 1.25, 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) if holeShape: PathLog.info("Processing holes...") From b77991018b7e63632b498d6b28b3c7baddc13489 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 30 May 2020 14:43:35 -0500 Subject: [PATCH 4/4] Path: Add new `Face Region` boundary option. New option allows access to previously inaccessible faces. Corrected property label names in UI panel, and added `Face Region` option to boundary shape list. --- .../Resources/panels/PageOpPocketFullEdit.ui | 27 +++++++++++-------- src/Mod/Path/PathScripts/PathMillFace.py | 19 ++++++++++--- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpPocketFullEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpPocketFullEdit.ui index 6b5878a9ff..a7630a0a94 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpPocketFullEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpPocketFullEdit.ui @@ -24,7 +24,7 @@ - + Tool Controller @@ -38,7 +38,7 @@ - + Coolant Mode @@ -64,12 +64,17 @@ - Perimeter + Boundbox - Boundbox + Face Region + + + + + Perimeter @@ -80,7 +85,7 @@ - + Boundary Shape @@ -96,7 +101,7 @@ QFormLayout::AllNonFixedFieldsGrow - + Cut Mode @@ -120,7 +125,7 @@ - + Pattern @@ -172,7 +177,7 @@ - + Angle @@ -186,7 +191,7 @@ - + Step Over Percent @@ -212,7 +217,7 @@ - + Material Allowance @@ -226,7 +231,7 @@ - + Enable Rotation diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index b8326f4cfb..09b93959cc 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -58,12 +58,12 @@ class ObjectFace(PathPocketBase.ObjectPocket): def initPocketOp(self, obj): '''initPocketOp(obj) ... create facing specific properties''' obj.addProperty("App::PropertyEnumeration", "BoundaryShape", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Shape to use for calculating Boundary")) - obj.BoundaryShape = ['Perimeter', 'Boundbox', 'Stock', 'Available'] obj.addProperty("App::PropertyBool", "ClearEdges", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clear edges of surface (Only applicable to BoundBox)")) - if not hasattr(obj, 'ExcludeRaisedAreas'): obj.addProperty("App::PropertyBool", "ExcludeRaisedAreas", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Exclude milling raised areas inside the face.")) + obj.BoundaryShape = ['Boundbox', 'Face Region', 'Perimeter', 'Stock'] + def pocketInvertExtraOffset(self): return True @@ -190,13 +190,26 @@ class ObjectFace(PathPocketBase.ObjectPocket): env = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams) else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) + elif obj.BoundaryShape == 'Face Region': + import PathScripts.PathSurfaceSupport as PathSurfaceSupport + baseShape = oneBase[0].Shape + psZMin = planeshape.BoundBox.ZMin + ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.tool.Diameter * 1.1, planeshape) + ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin)) + # custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value) # only an envelope + # ofstShapeEnv = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams) + # ofstShapeEnv.translate(FreeCAD.Vector(0.0, 0.0, -0.5)) + custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value + 0.1, obj.FinalDepth.Value - 0.1) # only an envelope + ofstShapeEnv = PathUtils.getEnvelope(partshape=ofstShape, depthparams=custDepthparams) + env = ofstShapeEnv.cut(baseShape) if holeShape: - PathLog.info("Processing holes...") + PathLog.debug("Processing holes and face ...") holeEnv = PathUtils.getEnvelope(partshape=holeShape, depthparams=self.depthparams) newEnv = env.cut(holeEnv) tup = newEnv, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value else: + PathLog.debug("Processing solid face ...") tup = env, False, 'pathMillFace', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value return [tup]