Merge pull request #3545 from Russ4262/Multi-profile

[Path] Profile - New `ExpandProfile` feature for compound profile operations
This commit is contained in:
sliptonic
2020-06-19 11:09:04 -05:00
committed by GitHub
3 changed files with 318 additions and 136 deletions

View File

@@ -29,14 +29,13 @@ import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import PathScripts.PathGeom as PathGeom
import math
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Draft = LazyLoader('Draft', globals(), 'Draft')
Part = LazyLoader('Part', globals(), 'Part')
# from PathScripts.PathUtils import waiting_effects
from PySide import QtCore
if FreeCAD.GuiUp:
import FreeCADGui
@@ -234,6 +233,8 @@ class ObjectOp(PathOp.ObjectOp):
area.add(baseobject)
areaParams = self.areaOpAreaParams(obj, isHole) # pylint: disable=assignment-from-no-return
if hasattr(obj, 'ExpandProfile') and obj.ExpandProfile != 0:
areaParams = self.areaOpAreaParamsExpandProfile(obj, isHole) # pylint: disable=assignment-from-no-return
heights = [i for i in self.depthparams]
PathLog.debug('depths: {}'.format(heights))
@@ -283,8 +284,8 @@ class ObjectOp(PathOp.ObjectOp):
return pp, simobj
def _buildProfileOpenEdges(self, obj, baseShape, isHole, start, getsim):
'''_buildPathArea(obj, baseShape, isHole, start, getsim) ... internal function.'''
def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim):
'''_buildPathArea(obj, edgeList, isHole, start, getsim) ... internal function.'''
# pylint: disable=unused-argument
PathLog.track()
@@ -293,35 +294,36 @@ class ObjectOp(PathOp.ObjectOp):
PathLog.debug('depths: {}'.format(heights))
lstIdx = len(heights) - 1
for i in range(0, len(heights)):
hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges))
hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin))
for baseShape in edgeList:
hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges))
hWire.translate(FreeCAD.Vector(0, 0, heights[i] - hWire.BoundBox.ZMin))
pathParams = {} # pylint: disable=assignment-from-no-return
pathParams['shapes'] = [hWire]
pathParams['feedrate'] = self.horizFeed
pathParams['feedrate_v'] = self.vertFeed
pathParams['verbose'] = True
pathParams['resume_height'] = obj.SafeHeight.Value
pathParams['retraction'] = obj.ClearanceHeight.Value
pathParams['return_end'] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
pathParams = {} # pylint: disable=assignment-from-no-return
pathParams['shapes'] = [hWire]
pathParams['feedrate'] = self.horizFeed
pathParams['feedrate_v'] = self.vertFeed
pathParams['verbose'] = True
pathParams['resume_height'] = obj.SafeHeight.Value
pathParams['retraction'] = obj.ClearanceHeight.Value
pathParams['return_end'] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
if self.endVector is None:
V = hWire.Wires[0].Vertexes
lv = len(V) - 1
pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z)
if obj.Direction == 'CCW':
pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z)
else:
pathParams['start'] = self.endVector
if self.endVector is None:
V = hWire.Wires[0].Vertexes
lv = len(V) - 1
pathParams['start'] = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z)
if obj.Direction == 'CCW':
pathParams['start'] = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z)
else:
pathParams['start'] = self.endVector
obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'})
PathLog.debug("Path with params: {}".format(obj.PathParams))
obj.PathParams = str({key: value for key, value in pathParams.items() if key != 'shapes'})
PathLog.debug("Path with params: {}".format(obj.PathParams))
(pp, end_vector) = Path.fromShapes(**pathParams)
paths.extend(pp.Commands)
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
(pp, end_vector) = Path.fromShapes(**pathParams)
paths.extend(pp.Commands)
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
self.endVector = end_vector # pylint: disable=attribute-defined-outside-init
@@ -410,11 +412,17 @@ class ObjectOp(PathOp.ObjectOp):
shapes.append(shp)
if len(shapes) > 1:
jobs = [{
'x': s[0].BoundBox.XMax,
'y': s[0].BoundBox.YMax,
'shape': s
} for s in shapes]
jobs = list()
for s in shapes:
if s[2] == 'OpenEdge':
shp = Part.makeCompound(s[0])
else:
shp = s[0]
jobs.append({
'x': shp.BoundBox.XMax,
'y': shp.BoundBox.YMax,
'shape': s
})
jobs = PathUtils.sort_jobs(jobs, ['x', 'y'])

View File

@@ -41,10 +41,10 @@ ArchPanel = LazyLoader('ArchPanel', globals(), 'ArchPanel')
Part = LazyLoader('Part', globals(), 'Part')
__title__ = "Path Profile Faces Operation"
__title__ = "Path Profile Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Path Profile operation based on faces."
__doc__ = "Path Profile operation based on entire model, selected faces or selected edges."
__contributors__ = "Schildkroet"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
@@ -101,6 +101,10 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return [
("App::PropertyEnumeration", "Direction", "Profile",
QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)")),
("App::PropertyLength", "ExpandProfile", "Profile",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Extend the profile clearing beyond the Extra Offset.")),
("App::PropertyPercent", "ExpandProfileStepOver", "Profile",
QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")),
("App::PropertyEnumeration", "HandleMultipleFeatures", "Profile",
QtCore.QT_TRANSLATE_NOOP("PathPocket", "Choose how to process multiple Base Geometry features.")),
("App::PropertyEnumeration", "JoinType", "Profile",
@@ -147,6 +151,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return {
'AttemptInverseAngle': True,
'Direction': 'CW',
'ExpandProfile': 0.0,
'ExpandProfileStepOver': 100,
'HandleMultipleFeatures': 'Individually',
'InverseAngle': False,
'JoinType': 'Round',
@@ -240,7 +246,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
params['Coplanar'] = 0
params['SectionCount'] = -1
offset = 0.0
offset = obj.OffsetExtra.Value # 0.0
if obj.UseComp:
offset = self.radius + obj.OffsetExtra.Value
if obj.Side == 'Inside':
@@ -257,6 +263,29 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return params
def areaOpAreaParamsExpandProfile(self, obj, isHole):
'''areaOpPathParamsExpandProfile(obj, isHole) ... return dictionary with area parameters for expaned profile'''
params = {}
params['Fill'] = 1
params['Coplanar'] = 0
params['PocketMode'] = 1
params['SectionCount'] = -1
# params['Angle'] = obj.ZigZagAngle
# params['FromCenter'] = (obj.StartAt == "Center")
params['PocketStepover'] = self.tool.Diameter * (float(obj.ExpandProfileStepOver) / 100.0)
extraOffset = obj.OffsetExtra.Value
if False: # self.pocketInvertExtraOffset(): # Method simply returns False
extraOffset = 0.0 - extraOffset
params['PocketExtraOffset'] = extraOffset
params['ToolRadius'] = self.radius
# Pattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
params['PocketMode'] = 2 # Pattern.index(obj.OffsetPattern) + 1
params['JoinType'] = 0 # jointype = ['Round', 'Square', 'Miter']
return params
def areaOpPathParams(self, obj, isHole):
'''areaOpPathParams(obj, isHole) ... returns dictionary with path parameters.
Do not overwrite.'''
@@ -283,7 +312,9 @@ class ObjectProfile(PathAreaOp.ObjectOp):
def areaOpUseProjection(self, obj):
'''areaOpUseProjection(obj) ... returns True'''
return True
if obj.ExpandProfile.Value == 0.0:
return True
return False
def opUpdateDepths(self, obj):
if hasattr(obj, 'Base') and obj.Base.__len__() == 0:
@@ -299,11 +330,12 @@ class ObjectProfile(PathAreaOp.ObjectOp):
allTuples = list()
edgeFaces = list()
subCount = 0
self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False
self.inaccessibleMsg = translate('PathProfile', 'The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.')
self.profileshape = list() # pylint: disable=attribute-defined-outside-init
self.offsetExtra = obj.OffsetExtra.Value # abs(obj.OffsetExtra.Value)
self.offsetExtra = obj.OffsetExtra.Value
self.expandProfile = None
if PathLog.getLevel(PathLog.thisModule()) == 4:
if self.isDebug:
for grpNm in ['tmpDebugGrp', 'tmpDebugGrp001']:
if hasattr(FreeCAD.ActiveDocument, grpNm):
for go in FreeCAD.ActiveDocument.getObject(grpNm).Group:
@@ -313,6 +345,11 @@ class ObjectProfile(PathAreaOp.ObjectOp):
tmpGrpNm = self.tmpGrp.Name
self.JOB = PathUtils.findParentJob(obj)
if obj.ExpandProfile.Value != 0.0:
import PathScripts.PathSurfaceSupport as PathSurfaceSupport
self.PathSurfaceSupport = PathSurfaceSupport
self.expandProfile = True
if obj.UseComp:
self.useComp = True
self.ofstRadius = self.radius + self.offsetExtra
@@ -337,7 +374,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
tup = self._analyzeFace(obj, base, sub, shape, subCount)
allTuples.append(tup)
if subCount > 1:
if subCount > 1 and obj.HandleMultipleFeatures == 'Collectively':
msg = translate('PathProfile', "Multiple faces in Base Geometry.") + " "
msg += translate('PathProfile', "Depth settings will be applied to all faces.")
FreeCAD.Console.PrintWarning(msg)
@@ -353,7 +390,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
baseSubsTuples.append(pair)
# Efor
else:
PathLog.debug(translate("Path", "EnableRotation property is 'Off'."))
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
baseSubsTuples.append((base, subList, 0.0, 'X', stock))
@@ -365,7 +401,6 @@ class ObjectProfile(PathAreaOp.ObjectOp):
holes = []
faces = []
faceDepths = []
startDepths = []
for sub in subsList:
shape = getattr(base.Shape, sub)
@@ -383,62 +418,99 @@ class ObjectProfile(PathAreaOp.ObjectOp):
msg = translate('PathProfile', "Found a selected object which is not a face. Ignoring:")
# FreeCAD.Console.PrintWarning(msg + " {}\n".format(ignoreSub))
# Set initial Start and Final Depths and recalculate depthparams
# Identify initial Start and Final Depths
finDep = obj.FinalDepth.Value
strDep = obj.StartDepth.Value
startDepths.append(strDep)
self.depthparams = self._customDepthParams(obj, strDep, finDep)
for shape, wire in holes:
for baseShape, wire in holes:
cont = False
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
drillable = PathUtils.isDrillable(shape, wire)
if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
drillable = PathUtils.isDrillable(baseShape, wire)
ot = self._openingType(obj, baseShape, f, strDep, finDep)
if len(faces) > 0:
profileshape = Part.makeCompound(faces)
self.profileshape.append(profileshape)
if obj.processCircles:
if drillable:
if ot < 1:
cont = True
if obj.processHoles:
if not drillable:
if ot < 1:
cont = True
if cont:
if self.expandProfile:
shapeEnv = self._getExpandedProfileEnvelope(obj, f, True, obj.StartDepth.Value, finDep)
else:
shapeEnv = PathUtils.getEnvelope(baseShape, subshape=f, depthparams=self.depthparams)
if shapeEnv:
self._addDebugObject('HoleShapeEnvelope', shapeEnv)
# env = PathUtils.getEnvelope(baseShape, subshape=f, depthparams=self.depthparams)
tup = shapeEnv, True, 'pathProfile', angle, axis, strDep, finDep
shapes.append(tup)
if obj.processPerimeter:
if obj.HandleMultipleFeatures == 'Collectively':
custDepthparams = self.depthparams
cont = True
if len(faces) > 0:
profileshape = Part.makeCompound(faces)
if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
if profileshape.BoundBox.ZMin > obj.FinalDepth.Value:
finDep = profileshape.BoundBox.ZMin
envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
custDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
try:
env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams)
# env = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams)
if self.expandProfile:
shapeEnv = self._getExpandedProfileEnvelope(obj, shape, False, obj.StartDepth.Value, finDep)
else:
shapeEnv = PathUtils.getEnvelope(profileshape, depthparams=custDepthparams)
except Exception as ee: # pylint: disable=broad-except
# PathUtils.getEnvelope() failed to return an object.
msg = translate('Path', 'Unable to create path for face(s).')
PathLog.error(msg + '\n{}'.format(ee))
else:
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
cont = False
if cont:
self._addDebugObject('CollectCutShapeEnv', shapeEnv)
tup = shapeEnv, False, 'pathProfile', angle, axis, strDep, finDep
shapes.append(tup)
elif obj.HandleMultipleFeatures == 'Individually':
for shape in faces:
finalDep = obj.FinalDepth.Value
custDepthparams = self.depthparams
if obj.Side == 'Inside':
if finalDep < shape.BoundBox.ZMin:
# Recalculate depthparams
finalDep = shape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep)
env = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
shapes.append(tup)
if self.expandProfile:
shapeEnv = self._getExpandedProfileEnvelope(obj, shape, False, obj.StartDepth.Value, finalDep)
else:
shapeEnv = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
else: # Try to build targets from the job base
if shapeEnv:
self._addDebugObject('IndivCutShapeEnv', shapeEnv)
tup = shapeEnv, False, 'pathProfile', angle, axis, strDep, finalDep
shapes.append(tup)
else: # Try to build targets from the job models
# No base geometry selected, so treating operation like a exterior contour operation
self.opUpdateDepths(obj)
obj.Side = 'Outside' # Force outside for whole model profile
if 1 == len(self.model) and hasattr(self.model[0], "Proxy"):
if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet
# Cancel ExpandProfile feature. Unavailable for ArchPanels.
if obj.ExpandProfile.Value != 0.0:
obj.ExpandProfile.Value == 0.0
msg = translate('PathProfile', 'No ExpandProfile support for ArchPanel models.')
FreeCAD.Console.PrintWarning(msg + '\n')
modelProxy = self.model[0].Proxy
# Process circles and holes if requested by user
if obj.processCircles or obj.processHoles:
@@ -448,7 +520,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams)
tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
tup = env, True, 'pathProfile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
# Process perimeter if requested by user
@@ -457,18 +529,21 @@ class ObjectProfile(PathAreaOp.ObjectOp):
for wire in shape.Wires:
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams)
tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
tup = env, False, 'pathProfile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')])
# shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')])
PathLog.debug('Single model processed.')
shapes.extend(self._processEachModel(obj))
else:
shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')])
# shapes.extend([(PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams), False) for base in self.model if hasattr(base, 'Shape')])
shapes.extend(self._processEachModel(obj))
self.removalshapes = shapes # pylint: disable=attribute-defined-outside-init
PathLog.debug("%d shapes" % len(shapes))
# Delete the temporary objects
if PathLog.getLevel(PathLog.thisModule()) == 4:
if self.isDebug:
if FreeCAD.GuiUp:
import FreeCADGui
FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False
@@ -529,6 +604,106 @@ class ObjectProfile(PathAreaOp.ObjectOp):
return tup
def _openingType(self, obj, baseShape, face, strDep, finDep):
# Test if solid geometry above opening
extDistPos = strDep - face.BoundBox.ZMin
if extDistPos > 0:
extFacePos = face.extrude(FreeCAD.Vector(0.0, 0.0, extDistPos))
cmnPos = baseShape.common(extFacePos)
if cmnPos.Volume > 0:
# Signifies solid protrusion above,
# or overhang geometry above opening
return 1
# Test if solid geometry below opening
extDistNeg = finDep - face.BoundBox.ZMin
if extDistNeg < 0:
extFaceNeg = face.extrude(FreeCAD.Vector(0.0, 0.0, extDistNeg))
cmnNeg = baseShape.common(extFaceNeg)
if cmnNeg.Volume == 0:
# No volume below signifies
# an unobstructed/nonconstricted opening through baseShape
return 0
else:
# Could be a pocket,
# or a constricted/narrowing hole through baseShape
return -1
msg = translate('PathProfile', 'failed to return opening type.')
PathLog.debug('_openingType() ' + msg)
return -2
# Method for expanded profile
def _getExpandedProfileEnvelope(self, obj, faceShape, isHole, strDep, finalDep):
shapeZ = faceShape.BoundBox.ZMin
def calculateOffsetValue(obj, isHole):
offset = obj.ExpandProfile.Value + obj.OffsetExtra.Value # 0.0
if obj.UseComp:
offset = obj.OffsetExtra.Value + self.tool.Diameter
offset += obj.ExpandProfile.Value
if isHole:
if obj.Side == 'Outside':
offset = 0 - offset
else:
if obj.Side == 'Inside':
offset = 0 - offset
return offset
faceEnv = self.PathSurfaceSupport.getShapeEnvelope(faceShape)
# newFace = self.PathSurfaceSupport.getSliceFromEnvelope(faceEnv)
newFace = self.PathSurfaceSupport.getShapeSlice(faceEnv)
# Compute necessary offset
offsetVal = calculateOffsetValue(obj, isHole)
expandedFace = self.PathSurfaceSupport.extractFaceOffset(newFace, offsetVal, newFace)
if expandedFace:
if shapeZ != 0.0:
expandedFace.translate(FreeCAD.Vector(0.0, 0.0, shapeZ))
newFace.translate(FreeCAD.Vector(0.0, 0.0, shapeZ))
if isHole:
if obj.Side == 'Outside':
newFace = newFace.cut(expandedFace)
else:
newFace = expandedFace.cut(newFace)
else:
if obj.Side == 'Inside':
newFace = newFace.cut(expandedFace)
else:
newFace = expandedFace.cut(newFace)
if finalDep - shapeZ != 0:
newFace.translate(FreeCAD.Vector(0.0, 0.0, finalDep - shapeZ))
if strDep - finalDep != 0:
if newFace.Area > 0:
return newFace.extrude(FreeCAD.Vector(0.0, 0.0, strDep - finalDep))
else:
PathLog.debug('No expanded profile face shape.\n')
return False
else:
PathLog.debug(translate('PathProfile', 'Failed to extract offset(s) for expanded profile.') + '\n')
PathLog.debug(translate('PathProfile', 'Failed to expand profile.') + '\n')
return False
# Method to handle each model as a whole, when no faces are selected
# It includes ExpandProfile implementation
def _processEachModel(self, obj):
shapeTups = list()
for base in self.model:
if hasattr(base, 'Shape'):
env = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthparams)
if self.expandProfile:
eSlice = self.PathSurfaceSupport.getCrossSection(env) # getSliceFromEnvelope(env)
eSlice.translate(FreeCAD.Vector(0.0, 0.0, base.Shape.BoundBox.ZMin - env.BoundBox.ZMin))
self._addDebugObject('ModelSlice', eSlice)
shapeEnv = self._getExpandedProfileEnvelope(obj, eSlice, False, obj.StartDepth.Value, obj.FinalDepth.Value)
else:
shapeEnv = env
if shapeEnv:
shapeTups.append((shapeEnv, False))
return shapeTups
# Edges pre-processing
def _processEdges(self, obj):
import DraftGeomUtils
@@ -536,6 +711,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
basewires = list()
delPairs = list()
ezMin = None
self.cutOut = self.tool.Diameter * (float(obj.ExpandProfileStepOver) / 100.0)
for p in range(0, len(obj.Base)):
(base, subsList) = obj.Base[p]
tmpSubs = list()
@@ -561,6 +738,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
for base, wires in basewires:
for wire in wires:
if wire.isClosed():
# Attempt to profile a closed wire
# f = Part.makeFace(wire, 'Part::FaceMakerSimple')
# if planar error, Comment out previous line, uncomment the next two
(origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value)
@@ -570,10 +749,15 @@ class ObjectProfile(PathAreaOp.ObjectOp):
zShift = ezMin - f.BoundBox.ZMin
newPlace = FreeCAD.Placement(FreeCAD.Vector(0, 0, zShift), f.Placement.Rotation)
f.Placement = newPlace
env = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams)
# shapes.append((env, False))
tup = env, False, 'Profile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
if self.expandProfile:
shapeEnv = self._getExpandedProfileEnvelope(obj, Part.Face(f), False, obj.StartDepth.Value, ezMin)
else:
shapeEnv = PathUtils.getEnvelope(base.Shape, subshape=f, depthparams=self.depthparams)
if shapeEnv:
tup = shapeEnv, False, 'Profile', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
PathLog.error(self.inaccessibleMsg)
else:
@@ -583,28 +767,37 @@ class ObjectProfile(PathAreaOp.ObjectOp):
msg += translate('PathProfile', 'Please set to an acceptable value greater than zero.')
PathLog.error(msg)
else:
cutWireObjs = False
flattened = self._flattenWire(obj, wire, obj.FinalDepth.Value)
if flattened:
cutWireObjs = False
openEdges = list()
passOffsets = [self.ofstRadius]
(origWire, flatWire) = flattened
if PathLog.getLevel(PathLog.thisModule()) == 4:
os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpFlatWire')
os.Shape = flatWire
os.purgeTouched()
self.tmpGrp.addObject(os)
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
if cutShp:
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
self._addDebugObject('FlatWire', flatWire)
if cutWireObjs:
for cW in cutWireObjs:
# shapes.append((cW, False))
# self.profileEdgesIsOpen = True
tup = cW, False, 'OpenEdge', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
PathLog.error(self.inaccessibleMsg)
if self.expandProfile:
# Identify list of pass offset values for expanded profile paths
regularOfst = self.ofstRadius
targetOfst = regularOfst + obj.ExpandProfile.Value
while regularOfst < targetOfst:
regularOfst += self.cutOut
passOffsets.insert(0, regularOfst)
for po in passOffsets:
self.ofstRadius = po
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
if cutShp:
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
if cutWireObjs:
for cW in cutWireObjs:
openEdges.append(cW)
else:
PathLog.error(self.inaccessibleMsg)
tup = openEdges, False, 'OpenEdge', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
PathLog.error(self.inaccessibleMsg)
# Eif
@@ -711,13 +904,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Cut model(selected edges) from extended edges boundbox
cutArea = extBndboxEXT.cut(base.Shape)
if PathLog.getLevel(PathLog.thisModule()) == 4:
CA = FCAD.addObject('Part::Feature', 'tmpCutArea')
CA.Shape = cutArea
CA.recompute()
CA.purgeTouched()
self.tmpGrp.addObject(CA)
self._addDebugObject('CutArea', cutArea)
# Get top and bottom faces of cut area (CA), and combine faces when necessary
topFc = list()
@@ -838,12 +1025,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Add path stops at ends of wire
cutShp = workShp.cut(pathStops)
if PathLog.getLevel(PathLog.thisModule()) == 4:
cs = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCutShape')
cs.Shape = cutShp
cs.recompute()
cs.purgeTouched()
self.tmpGrp.addObject(cs)
self._addDebugObject('CutShape', cutShp)
return cutShp
@@ -896,12 +1078,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
PathLog.error('No area to offset shape returned.\n{}'.format(ee))
return False
if PathLog.getLevel(PathLog.thisModule()) == 4:
os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpOffsetShape')
os.Shape = ofstShp
os.recompute()
os.purgeTouched()
self.tmpGrp.addObject(os)
self._addDebugObject('OffsetShape', ofstShp)
numOSWires = len(ofstShp.Wires)
for w in range(0, numOSWires):
@@ -917,12 +1094,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
min0 = N[4]
min0i = n
(w0, vi0, pnt0, vrt0, d0) = NEAR0[0] # min0i
if PathLog.getLevel(PathLog.thisModule()) == 4:
near0 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear0')
near0.Shape = Part.makeLine(cent0, pnt0)
near0.recompute()
near0.purgeTouched()
self.tmpGrp.addObject(near0)
near0Shp = Part.makeLine(cent0, pnt0)
self._addDebugObject('Near0', near0Shp)
NEAR1 = self._findNearestVertex(ofstShp, cent1)
min1i = 0
@@ -933,17 +1106,13 @@ class ObjectProfile(PathAreaOp.ObjectOp):
min1 = N[4]
min1i = n
(w1, vi1, pnt1, vrt1, d1) = NEAR1[0] # min1i
if PathLog.getLevel(PathLog.thisModule()) == 4:
near1 = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpNear1')
near1.Shape = Part.makeLine(cent1, pnt1)
near1.recompute()
near1.purgeTouched()
self.tmpGrp.addObject(near1)
near1Shp = Part.makeLine(cent1, pnt1)
self._addDebugObject('Near1', near1Shp)
if w0 != w1:
PathLog.warning('Offset wire endpoint indexes are not equal - w0, w1: {}, {}'.format(w0, w1))
if False and PathLog.getLevel(PathLog.thisModule()) == 4:
if self.isDebug and False:
PathLog.debug('min0i is {}.'.format(min0i))
PathLog.debug('min1i is {}.'.format(min1i))
PathLog.debug('NEAR0[{}] is {}.'.format(w0, NEAR0[w0]))
@@ -1012,12 +1181,13 @@ class ObjectProfile(PathAreaOp.ObjectOp):
PathLog.debug('_extractFaceOffset()')
areaParams = {}
JOB = PathUtils.findParentJob(obj)
tolrnc = JOB.GeometryTolerance.Value
if self.useComp is True:
offset = self.ofstRadius # + tolrnc
else:
offset = self.offsetExtra # + tolrnc
# JOB = PathUtils.findParentJob(obj)
# tolrnc = JOB.GeometryTolerance.Value
# if self.useComp:
# offset = self.ofstRadius # + tolrnc
# else:
# offset = self.offsetExtra # + tolrnc
offset = self.ofstRadius
if isHole is False:
offset = 0 - offset
@@ -1191,7 +1361,8 @@ class ObjectProfile(PathAreaOp.ObjectOp):
# Efor
# Eif
if False and PathLog.getLevel(PathLog.thisModule()) == 4:
# Remove `and False` when debugging open edges, as needed
if self.isDebug and False:
PathLog.debug('grps[0]: {}'.format(grps[0]))
PathLog.debug('grps[1]: {}'.format(grps[1]))
PathLog.debug('wireIdxs[0]: {}'.format(wireIdxs[0]))
@@ -1382,12 +1553,7 @@ class ObjectProfile(PathAreaOp.ObjectOp):
wire = Part.Wire([L1, L2, L3, L4, L5])
# Eif
face = Part.Face(wire)
if PathLog.getLevel(PathLog.thisModule()) == 4:
os = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp' + lbl)
os.Shape = face
os.recompute()
os.purgeTouched()
self.tmpGrp.addObject(os)
self._addDebugObject(lbl, face)
return face
@@ -1422,6 +1588,13 @@ class ObjectProfile(PathAreaOp.ObjectOp):
dist += elen
return midPnt
# Method to add temporary debug object
def _addDebugObject(self, objName, objShape):
if self.isDebug:
O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmp_' + objName)
O.Shape = objShape
O.purgeTouched()
self.tmpGrp.addObject(O)
def SetupProperties():

View File

@@ -51,6 +51,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''
def initPage(self, obj):
self.setTitle("Profile - " + obj.Label)
self.updateVisibility()
def profileFeatures(self):