From 66d27bbbefcd09123c17b62c2ab11ae2d8b9a06c Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 23 Jan 2022 00:01:34 -0600 Subject: [PATCH] Path: Fix overcut under model below selected faces Fixes address issues where incorrect paths are generated that include clearing paths below selected faces on underside of model. In forum presented at https://forum.freecadweb.org/viewtopic.php?f=15&t=64111 and https://forum.freecadweb.org/viewtopic.php?f=15&t=65421. --- src/Mod/Path/PathScripts/PathPocket.py | 96 ++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 571ccd143a..6f60763279 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -22,6 +22,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Part import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp import PathScripts.PathPocketBase as PathPocketBase @@ -30,16 +31,13 @@ import PathScripts.PathUtils as PathUtils # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader -Part = LazyLoader("Part", globals(), "Part") +PathGeom = LazyLoader("PathScripts.PathGeom", globals(), "PathScripts.PathGeom") __title__ = "Path 3D Pocket Operation" __author__ = "Yorik van Havre " __url__ = "https://www.freecadweb.org" __doc__ = "Class and implementation of the 3D Pocket operation." -__contributors__ = "russ4262 (Russell Johnson)" __created__ = "2014" -__scriptVersion__ = "2e" -__lastModified__ = "2020-02-13 17:22 CST" if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) @@ -144,6 +142,16 @@ class ObjectPocket(PathPocketBase.ObjectPocket): def pocketInvertExtraOffset(self): return False + def opUpdateDepths(self, obj): + """opUpdateDepths(obj) ... Implement special depths calculation.""" + # Set Final Depth to bottom of model if whole model is used + if not obj.Base or len(obj.Base) == 0: + if len(self.job.Model.Group) == 1: + finDep = self.job.Model.Group[0].Shape.BoundBox.ZMin + else: + finDep = min([m.Shape.BoundBox.ZMin for m in self.job.Model.Group]) + obj.setExpression("OpFinalDepth", "{} mm".format(finDep)) + def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" PathLog.track() @@ -197,8 +205,13 @@ class ObjectPocket(PathPocketBase.ObjectPocket): env = PathUtils.getEnvelope( base[0].Shape, subshape=shape, depthparams=self.depthparams ) - obj.removalshape = env.cut(base[0].Shape) - # obj.removalshape.tessellate(0.1) + rawRemovalShape = env.cut(base[0].Shape) + faceExtrusions = [ + f.extrude(FreeCAD.Vector(0.0, 0.0, 1.0)) for f in Faces + ] + obj.removalshape = _identifyRemovalSolids( + rawRemovalShape, faceExtrusions + ) removalshapes.append( (obj.removalshape, False, "3DPocket") ) # (shape, isHole, detail) @@ -213,8 +226,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): env = PathUtils.getEnvelope( base[0].Shape, subshape=shape, depthparams=self.depthparams ) - obj.removalshape = env.cut(base[0].Shape) - # obj.removalshape.tessellate(0.1) + rawRemovalShape = env.cut(base[0].Shape) + faceExtrusions = [shape.extrude(FreeCAD.Vector(0.0, 0.0, 1.0))] + obj.removalshape = _identifyRemovalSolids( + rawRemovalShape, faceExtrusions + ) removalshapes.append((obj.removalshape, False, "3DPocket")) else: # process the job base object as a whole @@ -227,15 +243,33 @@ class ObjectPocket(PathPocketBase.ObjectPocket): job.Stock.Shape, subshape=None, depthparams=self.depthparams ) - obj.removalshape = stockEnvShape.cut(base.Shape) - # obj.removalshape.tessellate(0.1) + rawRemovalShape = stockEnvShape.cut(base.Shape) else: env = PathUtils.getEnvelope( base.Shape, subshape=None, depthparams=self.depthparams ) - obj.removalshape = env.cut(base.Shape) - # obj.removalshape.tessellate(0.1) + rawRemovalShape = env.cut(base.Shape) + # Identify target removal shapes after cutting envelope with base shape + removalSolids = [ + s + for s in rawRemovalShape.Solids + if PathGeom.isRoughly( + s.BoundBox.ZMax, rawRemovalShape.BoundBox.ZMax + ) + ] + + # Fuse multiple solids + if len(removalSolids) > 1: + seed = removalSolids[0] + for tt in removalSolids[1:]: + fusion = seed.fuse(tt) + seed = fusion + removalShape = seed + else: + removalShape = removalSolids[0] + + obj.removalshape = removalShape removalshapes.append((obj.removalshape, False, "3DPocket")) return removalshapes @@ -845,6 +879,44 @@ class ObjectPocket(PathPocketBase.ObjectPocket): return (zmin, zmax) +def _identifyRemovalSolids(sourceShape, commonShapes): + """_identifyRemovalSolids(sourceShape, commonShapes) + Loops through solids in sourceShape to identify commonality with solids in commonShapes. + The sourceShape solids with commonality are returned as Part.Compound shape.""" + common = Part.makeCompound(commonShapes) + removalSolids = [s for s in sourceShape.Solids if s.common(common).Volume > 0.0] + return Part.makeCompound(removalSolids) + + +def _extrudeBaseDown(base): + """_extrudeBaseDown(base) + Extrudes and fuses all non-vertical faces downward to a level 1.0 mm below base ZMin.""" + allExtrusions = list() + zMin = base.Shape.BoundBox.ZMin + bbFace = PathGeom.makeBoundBoxFace(base.Shape.BoundBox, offset=5.0) + bbFace.translate( + FreeCAD.Vector(0.0, 0.0, float(int(base.Shape.BoundBox.ZMin - 5.0))) + ) + direction = FreeCAD.Vector(0.0, 0.0, -1.0) + + # Make projections of each non-vertical face and extrude it + for f in base.Shape.Faces: + fbb = f.BoundBox + if not PathGeom.isRoughly(f.normalAt(0, 0).z, 0.0): + pp = bbFace.makeParallelProjection(f.Wires[0], direction) + face = Part.Face(Part.Wire(pp.Edges)) + face.translate(FreeCAD.Vector(0.0, 0.0, fbb.ZMin)) + ext = face.extrude(FreeCAD.Vector(0.0, 0.0, zMin - fbb.ZMin - 1.0)) + allExtrusions.append(ext) + + # Fuse all extrusions together + seed = allExtrusions.pop() + fusion = seed.fuse(allExtrusions) + fusion.translate(FreeCAD.Vector(0.0, 0.0, zMin - fusion.BoundBox.ZMin - 1.0)) + + return fusion.cut(base.Shape) + + def SetupProperties(): return PathPocketBase.SetupProperties() + ["HandleMultipleFeatures"]