MillFace: add feature - exclude raised areas

new feature enabled by name, excludes raised areas inside face, but mills over pockets and holes.

Apply 'ExcludeRaisedAreas' to all BoundaryShapes

Add algorithm to apply 'ExcludeRaisedAreas' to Stock and BoundBox
This commit is contained in:
Russell Johnson
2019-07-23 04:54:01 -05:00
parent 97054ee4be
commit 474492bcd1

View File

@@ -31,11 +31,16 @@ import PathScripts.PathPocketBase as PathPocketBase
import PathScripts.PathUtils as PathUtils
from PySide import QtCore
import numpy
__title__ = "Path Mill Face Operation"
__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"
LOGLEVEL = False
@@ -60,6 +65,9 @@ class ObjectFace(PathPocketBase.ObjectPocket):
obj.addProperty("App::PropertyEnumeration", "BoundaryShape", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Shape to use for calculating Boundary"))
obj.BoundaryShape = ['Perimeter', 'Boundbox', 'Stock']
if not hasattr(obj, 'ExcludeRaisedAreas'):
obj.addProperty("App::PropertyBool", "ExcludeRaisedAreas", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "Exclude milling raised areas inside the face."))
def pocketInvertExtraOffset(self):
return True
@@ -93,19 +101,46 @@ class ObjectFace(PathPocketBase.ObjectPocket):
def areaOpShapes(self, obj):
'''areaOpShapes(obj) ... return top face'''
# Facing is done either against base objects
holeShape = None
if obj.Base:
PathLog.debug("obj.Base: {}".format(obj.Base))
faces = []
holes = []
holeEnvs = []
oneBase = [obj.Base[0][0], True]
sub0 = getattr(obj.Base[0][0].Shape, obj.Base[0][1][0])
minHeight = sub0.BoundBox.ZMax
for b in obj.Base:
for sub in b[1]:
shape = getattr(b[0].Shape, sub)
if isinstance(shape, Part.Face):
faces.append(shape)
if shape.BoundBox.ZMin < minHeight:
minHeight = shape.BoundBox.ZMin
if oneBase[0] is not b[0]:
oneBase[1] = False
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
for wire in shape.Wires[1:]:
if obj.ExcludeRaisedAreas is True:
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.debug('The base subobject is not a face')
return
planeshape = Part.makeCompound(faces)
PathLog.error('The base subobject, "{}," is not a face. Ignoring "{}."'.format(sub, sub))
if obj.ExcludeRaisedAreas is True and len(holes) > 0:
for shape, wire in holes:
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
holeEnvs.append(env)
holeShape = Part.makeCompound(holeEnvs)
PathLog.debug("Working on a collection of faces {}".format(faces))
planeshape = Part.makeCompound(faces)
# If no base object, do planing of top surface of entire model
else:
@@ -118,18 +153,38 @@ 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:
includedFaces = self.getAllIncludedFaces(oneBase[0], env, faceZ=minHeight)
if len(includedFaces) > 0:
includedShape = Part.makeCompound(includedFaces)
includedEnv = PathUtils.getEnvelope(oneBase[0].Shape, subshape=includedShape, depthparams=self.depthparams)
env = env.cut(includedEnv)
elif obj.BoundaryShape == 'Stock':
stock = PathUtils.findParentJob(obj).Stock.Shape
env = stock
if obj.ExcludeRaisedAreas is True and oneBase[1] is True:
includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
if len(includedFaces) > 0:
stockEnv = PathUtils.getEnvelope(partshape=stock, depthparams=self.depthparams)
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)
return [(env, False)]
if holeShape is not None:
PathLog.info("Processing holes...")
holeEnv = PathUtils.getEnvelope(partshape=holeShape, depthparams=self.depthparams)
newEnv = env.cut(holeEnv)
return [(newEnv, False)]
else:
return [(env, False)]
def areaOpSetDefaultValues(self, obj, job):
'''areaOpSetDefaultValues(obj, job) ... initialize mill facing properties'''
obj.StepOver = 50
obj.ZigZagAngle = 45.0
obj.ExcludeRaisedAreas = False
# need to overwrite the default depth calculations for facing
if job and len(job.Model.Group) > 0:
@@ -141,10 +196,67 @@ class ObjectFace(PathPocketBase.ObjectPocket):
if len(obj.Base) >= 1:
obj.OpFinalDepth = Part.makeCompound(obj.Base).BoundBox.ZMax
def SetupProperties():
return PathPocketBase.SetupProperties() + [ "BoundaryShape" ]
def isPocket(self, b, f, w):
e = w.Edges[0]
for fi in range(0, len(b.Shape.Faces)):
face = b.Shape.Faces[fi]
for ei in range(0, len(face.Edges)):
edge = face.Edges[ei]
if e.isSame(edge) is True:
if f is face:
# Alternative: run loop to see if all edges are same
pass # same source face, look for another
else:
if face.CenterOfMass.z < f.CenterOfMass.z:
return True
return False
def Create(name, obj = None):
def getAllIncludedFaces(self, base, env, faceZ):
included = []
eXMin = env.BoundBox.XMin
eXMax = env.BoundBox.XMax
eYMin = env.BoundBox.YMin
eYMax = env.BoundBox.YMax
# eZMin = env.BoundBox.ZMin
eZMin = faceZ
# eZMax = env.BoundBox.ZMax
def isOverlap(fMn, fMx, eMn, eMx):
if fMx > eMn:
if fMx <= eMx:
return True
elif fMx >= eMx and fMn <= eMx:
return True
if fMn < eMx:
if fMn >= eMn:
return True
elif fMn <= eMn and fMx >= eMn:
return True
return False
for fi in range(0, len(base.Shape.Faces)):
incl = False
face = base.Shape.Faces[fi]
fXMin = face.BoundBox.XMin
fXMax = face.BoundBox.XMax
fYMin = face.BoundBox.YMin
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:
incl = True
if incl is True:
included.append(face)
return included
def SetupProperties():
return PathPocketBase.SetupProperties().extend(["BoundaryShape", "ExcludeRaisedAreas"])
def Create(name, obj=None):
'''Create(name) ... Creates and returns a Mill Facing operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)