Merge pull request #5424 from Russ4262/fix/3D_pocket_overcut

Path: Fixes 3D Pocket overcut on underside of selected faces
This commit is contained in:
sliptonic
2022-02-22 08:52:12 -06:00
committed by GitHub

View File

@@ -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 <yorik@uncreated.net>"
__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())
@@ -69,6 +67,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
"Choose how to process multiple Base Geometry features.",
),
)
if not hasattr(obj, "AdaptivePocketStart"):
obj.addProperty(
"App::PropertyBool",
@@ -143,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()
@@ -196,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)
@@ -212,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
@@ -226,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
@@ -293,7 +328,10 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
except Exception as ee:
PathLog.warning(ee)
PathLog.error(
"A planar adaptive start is unavailable. The non-planar will be attempted."
translate(
"Path",
"A planar adaptive start is unavailable. The non-planar will be attempted.",
)
)
tryNonPlanar = True
else:
@@ -306,7 +344,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
) # NON-planar face method
except Exception as eee:
PathLog.warning(eee)
PathLog.error("The non-planar adaptive start is also unavailable.")
PathLog.error(
translate(
"Path", "The non-planar adaptive start is also unavailable."
)
+ "(1)"
)
isHighFacePlanar = False
else:
makeHighFace = 2
@@ -331,7 +374,12 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
highFace.Shape.BoundBox.ZMin - mn,
)
)
PathLog.error("The non-planar adaptive start is also unavailable.")
PathLog.error(
translate(
"Path", "The non-planar adaptive start is also unavailable."
)
+ "(2)"
)
isHighFacePlanar = False
makeHighFace = 0
else:
@@ -831,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"]