diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py
index c8a468680980..d68e4676dd52 100644
--- a/src/Mod/Path/PathScripts/PathAreaOp.py
+++ b/src/Mod/Path/PathScripts/PathAreaOp.py
@@ -290,7 +290,6 @@ def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim):
paths = []
heights = [i for i in self.depthparams]
PathLog.debug('depths: {}'.format(heights))
- lstIdx = len(heights) - 1
for i in range(0, len(heights)):
for baseShape in edgeList:
hWire = Part.Wire(Part.__sortEdges__(baseShape.Edges))
@@ -323,14 +322,8 @@ def _buildProfileOpenEdges(self, obj, edgeList, isHole, start, getsim):
paths.extend(pp.Commands)
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
- self.endVector = end_vector # pylint: disable=attribute-defined-outside-init
-
+ self.endVector = end_vector
simobj = None
- if getsim and False:
- areaParams['ToolRadius'] = self.radius - self.radius * .005
- area.setParams(**areaParams)
- sec = area.makeSections(mode=0, project=False, heights=heights)[-1].getShape()
- simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax))
return paths, simobj
@@ -356,6 +349,8 @@ def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ
self.useTempJobClones('Delete') # Clear temporary group and recreate for temp job clones
self.rotStartDepth = None # pylint: disable=attribute-defined-outside-init
+ start_depth = obj.StartDepth.Value
+ final_depth = obj.FinalDepth.Value
if obj.EnableRotation != 'Off':
# Calculate operation heights based upon rotation radii
opHeights = self.opDetermineRotationRadii(obj)
@@ -364,28 +359,28 @@ def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ
# Set clearance and safe heights based upon rotation radii
if obj.EnableRotation == 'A(x)':
- strDep = self.xRotRad
+ start_depth = self.xRotRad
elif obj.EnableRotation == 'B(y)':
- strDep = self.yRotRad
+ start_depth = self.yRotRad
else:
- strDep = max(self.xRotRad, self.yRotRad)
- finDep = -1 * strDep
+ start_depth = max(self.xRotRad, self.yRotRad)
+ final_depth = -1 * start_depth
- self.rotStartDepth = strDep
- obj.ClearanceHeight.Value = strDep + self.clrOfset
- obj.SafeHeight.Value = strDep + self.safOfst
+ self.rotStartDepth = start_depth
+ # The next two lines are improper code.
+ # The ClearanceHeight and SafeHeight need to be set in opSetDefaultValues() method.
+ # They should not be redefined here, so this entire `if...:` statement needs relocated.
+ obj.ClearanceHeight.Value = start_depth + self.clrOfset
+ obj.SafeHeight.Value = start_depth + self.safOfst
# Create visual axes when debugging.
if PathLog.getLevel(PathLog.thisModule()) == 4:
self.visualAxis()
- else:
- strDep = obj.StartDepth.Value
- finDep = obj.FinalDepth.Value
- # Set axial feed rates based upon horizontal feed rates
- safeCircum = 2 * math.pi * obj.SafeHeight.Value
- self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init
- self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init
+ # Set axial feed rates based upon horizontal feed rates
+ safeCircum = 2 * math.pi * obj.SafeHeight.Value
+ self.axialFeed = 360 / safeCircum * self.horizFeed # pylint: disable=attribute-defined-outside-init
+ self.axialRapid = 360 / safeCircum * self.horizRapid # pylint: disable=attribute-defined-outside-init
# Initiate depthparams and calculate operation heights for rotational operation
self.depthparams = self._customDepthParams(obj, obj.StartDepth.Value, obj.FinalDepth.Value)
@@ -403,8 +398,8 @@ def opExecute(self, obj, getsim=False): # pylint: disable=arguments-differ
for shp in aOS:
if len(shp) == 2:
(fc, iH) = shp
- # fc, iH, sub, angle, axis, strtDep, finDep
- tup = fc, iH, 'otherOp', 0.0, 'S', obj.StartDepth.Value, obj.FinalDepth.Value
+ # fc, iH, sub, angle, axis, strtDep, finDep
+ tup = fc, iH, 'otherOp', 0.0, 'S', start_depth, final_depth
shapes.append(tup)
else:
shapes.append(shp)
@@ -985,19 +980,43 @@ def warnDisabledAxis(self, obj, axis, sub=''):
return False
def isFaceUp(self, base, face):
+ '''isFaceUp(base, face) ...
+ When passed a base object and face shape, returns True if face is up.
+ This method is used to identify correct rotation of a model.
+ '''
+ # verify face is normal to Z+-
+ (norm, surf) = self.getFaceNormAndSurf(face)
+ if round(abs(norm.z), 8) != 1.0 or round(abs(surf.z), 8) != 1.0:
+ PathLog.debug('isFaceUp - face not oriented normal to Z+-')
+ return False
+
up = face.extrude(FreeCAD.Vector(0.0, 0.0, 5.0))
dwn = face.extrude(FreeCAD.Vector(0.0, 0.0, -5.0))
upCmn = base.Shape.common(up)
dwnCmn = base.Shape.common(dwn)
+
# Identify orientation based on volumes of common() results
- if len(upCmn.Edges) > 0 and round(upCmn.Volume, 6) == 0.0:
- return True
- elif len(dwnCmn.Edges) > 0 and round(dwnCmn.Volume, 6) == 0.0:
- return False
- if (len(upCmn.Edges) > 0 and len(dwnCmn.Edges) > 0 and
- round(dwnCmn.Volume, 6) > round(upCmn.Volume, 6)):
+ if len(upCmn.Edges) > 0:
+ PathLog.debug('isFaceUp - HAS up edges\n')
+ if len(dwnCmn.Edges) > 0:
+ PathLog.debug('isFaceUp - up and dwn edges\n')
+ dVol = round(dwnCmn.Volume, 6)
+ uVol = round(upCmn.Volume, 6)
+ if uVol > dVol:
+ return False
+ return True
+ else:
+ if round(upCmn.Volume, 6) == 0.0:
+ return True
+ return False
+ elif len(dwnCmn.Edges) > 0:
+ PathLog.debug('isFaceUp - HAS dwn edges only\n')
+ dVol = round(dwnCmn.Volume, 6)
+ if dVol == 0.0:
+ return False
return True
- return False
+ PathLog.debug('isFaceUp - exit True\n')
+ return True
def _customDepthParams(self, obj, strDep, finDep):
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py
index 139b06d6846c..afdac350daab 100644
--- a/src/Mod/Path/PathScripts/PathPocketShape.py
+++ b/src/Mod/Path/PathScripts/PathPocketShape.py
@@ -321,265 +321,27 @@ def areaOpShapes(self, obj):
PathLog.track()
PathLog.debug("----- areaOpShapes() in PathPocketShape.py")
+ self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False
baseSubsTuples = []
- subCount = 0
allTuples = []
-
- def planarFaceFromExtrusionEdges(face, trans):
- useFace = 'useFaceName'
- minArea = 0.0
- fCnt = 0
- clsd = []
- planar = False
- # Identify closed edges
- for edg in face.Edges:
- if edg.isClosed():
- PathLog.debug(' -e.isClosed()')
- clsd.append(edg)
- planar = True
-
- # Attempt to create planar faces and select that with smallest area for use as pocket base
- if planar is True:
- planar = False
- for edg in clsd:
- fCnt += 1
- fName = sub + '_face_' + str(fCnt)
- # Create planar face from edge
- mFF = Part.Face(Part.Wire(Part.__sortEdges__([edg])))
- if mFF.isNull():
- PathLog.debug('Face(Part.Wire()) failed')
- else:
- if trans is True:
- mFF.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin))
-
- if FreeCAD.ActiveDocument.getObject(fName):
- FreeCAD.ActiveDocument.removeObject(fName)
-
- tmpFace = FreeCAD.ActiveDocument.addObject('Part::Feature', fName).Shape = mFF
- tmpFace = FreeCAD.ActiveDocument.getObject(fName)
- tmpFace.purgeTouched()
-
- if minArea == 0.0:
- minArea = tmpFace.Shape.Face1.Area
- useFace = fName
- planar = True
- elif tmpFace.Shape.Face1.Area < minArea:
- minArea = tmpFace.Shape.Face1.Area
- FreeCAD.ActiveDocument.removeObject(useFace)
- useFace = fName
- else:
- FreeCAD.ActiveDocument.removeObject(fName)
-
- if useFace != 'useFaceName':
- self.useTempJobClones(useFace)
-
- return (planar, useFace)
-
- def clasifySub(self, bs, sub):
- face = bs.Shape.getElement(sub)
-
- if type(face.Surface) == Part.Plane:
- PathLog.debug('type() == Part.Plane')
- if PathGeom.isVertical(face.Surface.Axis):
- PathLog.debug(' -isVertical()')
- # it's a flat horizontal face
- self.horiz.append(face)
- return True
-
- elif PathGeom.isHorizontal(face.Surface.Axis):
- PathLog.debug(' -isHorizontal()')
- self.vert.append(face)
- return True
-
- else:
- return False
-
- elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis):
- PathLog.debug('type() == Part.Cylinder')
- # vertical cylinder wall
- if any(e.isClosed() for e in face.Edges):
- PathLog.debug(' -e.isClosed()')
- # complete cylinder
- circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center)
- disk = Part.Face(Part.Wire(circle))
- disk.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin))
- self.horiz.append(disk)
- return True
-
- else:
- PathLog.debug(' -none isClosed()')
- # partial cylinder wall
- self.vert.append(face)
- return True
-
- elif type(face.Surface) == Part.SurfaceOfExtrusion:
- # extrusion wall
- PathLog.debug('type() == Part.SurfaceOfExtrusion')
- # Attempt to extract planar face from surface of extrusion
- (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=True)
- # Save face object to self.horiz for processing or display error
- if planar is True:
- uFace = FreeCAD.ActiveDocument.getObject(useFace)
- self.horiz.append(uFace.Shape.Faces[0])
- msg = translate('Path', "Verify depth of pocket for '{}'.".format(sub))
- msg += translate('Path', "\n
Pocket is based on extruded surface.")
- msg += translate('Path', "\n
Bottom of pocket might be non-planar and/or not normal to spindle axis.")
- msg += translate('Path', "\n
\n
3D pocket bottom is NOT available in this operation.")
- PathLog.warning(msg)
- # title = translate('Path', 'Depth Warning')
- # self.guiMessage(title, msg, False)
- else:
- PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))
-
- else:
- PathLog.debug(' -type(face.Surface): {}'.format(type(face.Surface)))
- return False
+ subCount = 0
if obj.Base:
- PathLog.debug('Processing... obj.Base')
+ PathLog.debug('Processing obj.Base')
self.removalshapes = [] # pylint: disable=attribute-defined-outside-init
if obj.EnableRotation == 'Off':
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
- baseSubsTuples.append((base, subList, 0.0, 'X', stock))
+ tup = (base, subList, 0.0, 'X', stock)
+ baseSubsTuples.append(tup)
else:
- PathLog.debug('Rotation is active...')
+ PathLog.debug('... Rotation is active')
+ # method call here
for p in range(0, len(obj.Base)):
- (base, subsList) = obj.Base[p]
- isLoop = False
-
- # First, check all subs collectively for loop of faces
- if len(subsList) > 2:
- (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)
-
- if isLoop is True:
- PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.")
- rtn = False
- subCount += 1
- (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
- PathLog.debug("angle: {}; axis: {}".format(angle, axis))
-
- if rtn is True:
- faceNums = ""
- for f in subsList:
- faceNums += '_' + f.replace('Face', '')
- (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNums) # pylint: disable=unused-variable
-
- # Verify faces are correctly oriented - InverseAngle might be necessary
- PathLog.debug("Checking if faces are oriented correctly after rotation...")
- for sub in subsList:
- face = clnBase.Shape.getElement(sub)
- if type(face.Surface) == Part.Plane:
- if not PathGeom.isHorizontal(face.Surface.Axis):
- rtn = False
- PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied."))
- break
-
- if rtn is False:
- PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.") + ' 1')
- if obj.InverseAngle:
- (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
- else:
- if obj.AttemptInverseAngle is True:
- (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
- else:
- msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
- PathLog.warning(msg)
-
- if angle < 0.0:
- angle += 360.0
-
- tup = clnBase, subsList, angle, axis, clnStock
- else:
- if self.warnDisabledAxis(obj, axis) is False:
- PathLog.debug("No rotation used")
- axis = 'X'
- angle = 0.0
- stock = PathUtils.findParentJob(obj).Stock
- tup = base, subsList, angle, axis, stock
- # Eif
-
- allTuples.append(tup)
- baseSubsTuples.append(tup)
- # Eif
-
- if isLoop is False:
- PathLog.debug(translate('Path', "Processing subs individually ..."))
- for sub in subsList:
- subCount += 1
- if 'Face' in sub:
- rtn = False
- face = base.Shape.getElement(sub)
- if type(face.Surface) == Part.SurfaceOfExtrusion:
- # extrusion wall
- PathLog.debug('analyzing type() == Part.SurfaceOfExtrusion')
- # Attempt to extract planar face from surface of extrusion
- (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=False)
- # Save face object to self.horiz for processing or display error
- if planar is True:
- base = FreeCAD.ActiveDocument.getObject(useFace)
- sub = 'Face1'
- PathLog.debug(' -successful face created: {}'.format(useFace))
- else:
- PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))
-
- (norm, surf) = self.getFaceNormAndSurf(face)
- (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
- PathLog.debug("initial {}".format(praInfo))
-
- if rtn is True:
- faceNum = sub.replace('Face', '')
- (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNum)
- # Verify faces are correctly oriented - InverseAngle might be necessary
- faceIA = clnBase.Shape.getElement(sub)
- (norm, surf) = self.getFaceNormAndSurf(faceIA)
- (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
- PathLog.debug("follow-up {}".format(praInfo2))
-
- if abs(praAngle) == 180.0:
- rtn = False
- if self.isFaceUp(clnBase, faceIA) is False:
- PathLog.debug('isFaceUp is False')
- angle -= 180.0
-
- if rtn is True:
- PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.") + ' 2')
- if obj.InverseAngle:
- (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
- if self.isFaceUp(clnBase, faceIA) is False:
- PathLog.debug('isFaceUp is False')
- angle += 180.0
- else:
- if obj.AttemptInverseAngle is True:
- (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
- else:
- msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
- PathLog.warning(msg)
-
- if self.isFaceUp(clnBase, faceIA) is False:
- PathLog.debug('isFaceUp is False')
- angle += 180.0
- else:
- PathLog.debug("Face appears to be oriented correctly.")
-
- if angle < 0.0:
- angle += 360.0
-
- tup = clnBase, [sub], angle, axis, clnStock
- else:
- if self.warnDisabledAxis(obj, axis) is False:
- PathLog.debug(str(sub) + ": No rotation used")
- axis = 'X'
- angle = 0.0
- stock = PathUtils.findParentJob(obj).Stock
- tup = base, [sub], angle, axis, stock
- # Eif
- allTuples.append(tup)
- baseSubsTuples.append(tup)
- else:
- ignoreSub = base.Name + '.' + sub
- PathLog.error(translate('Path', "Selected feature is not a Face. Ignoring: {}".format(ignoreSub)))
+ (bst, at) = self.process_base_geometry_with_rotation(obj, p, subCount)
+ allTuples.extend(at)
+ baseSubsTuples.extend(bst)
for o in baseSubsTuples:
self.horiz = [] # pylint: disable=attribute-defined-outside-init
@@ -588,11 +350,11 @@ def clasifySub(self, bs, sub):
subsList = o[1]
angle = o[2]
axis = o[3]
- stock = o[4]
+ # stock = o[4]
for sub in subsList:
if 'Face' in sub:
- if clasifySub(self, subBase, sub) is False:
+ if not self.clasifySub(subBase, sub):
PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub))
if obj.EnableRotation != 'Off':
PathLog.warning(translate('PathPocket', 'Face might not be within rotation accessibility limits.'))
@@ -614,15 +376,11 @@ def clasifySub(self, bs, sub):
if PathGeom.isRoughly(face.Area, 0):
msg = translate('PathPocket', 'Vertical faces do not form a loop - ignoring')
PathLog.error(msg)
- # title = translate("Path", "Face Selection Warning")
- # self.guiMessage(title, msg, True)
else:
face.translate(FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin))
self.horiz.append(face)
msg = translate('Path', 'Verify final depth of pocket shaped by vertical faces.')
PathLog.warning(msg)
- # title = translate('Path', 'Depth Warning')
- # self.guiMessage(title, msg, False)
# add faces for extensions
self.exts = [] # pylint: disable=attribute-defined-outside-init
@@ -633,10 +391,6 @@ def clasifySub(self, bs, sub):
self.horiz.append(face)
self.exts.append(face)
- # move all horizontal faces to FinalDepth
- # for f in self.horiz:
- # f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin))
-
# check all faces and see if they are touching/overlapping and combine those into a compound
self.horizontal = [] # pylint: disable=attribute-defined-outside-init
for shape in PathGeom.combineConnectedShapes(self.horiz):
@@ -654,48 +408,56 @@ def clasifySub(self, bs, sub):
else:
self.horizontal.append(shape)
+ # move all horizontal faces to FinalDepth
# extrude all faces up to StartDepth and those are the removal shapes
start_dep = obj.StartDepth.Value
clrnc = 0.5
+ # self._addDebugObject('subBase', subBase.Shape)
for face in self.horizontal:
- adj_final_dep = obj.FinalDepth.Value
+ isFaceUp = True
+ invZ = 0.0
useAngle = angle
- shpZMin = face.BoundBox.ZMin
- shpZMinVal = shpZMin
- PathLog.debug('self.horizontal pre-shpZMin: {}'.format(shpZMin))
- isFaceUp = self.isFaceUp(subBase, face)
- if not isFaceUp:
- useAngle += 180.0
- invZ = (-2 * shpZMin) - clrnc
- face.translate(FreeCAD.Vector(0.0, 0.0, invZ))
- shpZMin = -1 * shpZMin
- else:
- face.translate(FreeCAD.Vector(0.0, 0.0, -1 * clrnc))
- PathLog.debug('self.horizontal post-shpZMin: {}'.format(shpZMin))
-
- if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
- if shpZMinVal > obj.FinalDepth.Value:
- PathLog.debug('shpZMin > obj.FinalDepth.Value')
- adj_final_dep = shpZMinVal # shpZMin
- if start_dep <= adj_final_dep:
- start_dep = adj_final_dep + 1.0
- msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ')
- PathLog.warning(msg + ' {} mm.'.format(start_dep))
- PathLog.debug('LimitDepthToFace adj_final_dep: {}'.format(adj_final_dep))
- else:
- translation = obj.FinalDepth.Value - shpZMin
+ faceZMin = face.BoundBox.ZMin
+ adj_final_dep = obj.FinalDepth.Value
+ trans = obj.FinalDepth.Value - face.BoundBox.ZMin
+ PathLog.debug('face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin))
+
+ if obj.EnableRotation != 'Off':
+ PathLog.debug('... running isFaceUp()')
+ isFaceUp = self.isFaceUp(subBase, face)
+ # Determine if face is really oriented toward Z+ (rotational purposes)
+ # ignore for cylindrical faces
if not isFaceUp:
- # Check if the `isFaceUp` returned correctly
- zDestination = face.BoundBox.ZMin + translation
- if (round(start_dep - obj.FinalDepth.Value, 6) !=
- round(start_dep - zDestination, 6)):
- shpZMin = -1 * shpZMin
- face.translate(FreeCAD.Vector(0, 0, translation))
-
- extent = FreeCAD.Vector(0, 0, abs(start_dep - shpZMin) + clrnc) # adj_final_dep + clrnc)
- extShp = face.removeSplitter().extrude(extent)
+ PathLog.debug('... NOT isFaceUp')
+ useAngle += 180.0
+ invZ = (-2 * face.BoundBox.ZMin)
+ face.translate(FreeCAD.Vector(0.0, 0.0, invZ))
+ faceZMin = face.BoundBox.ZMin # reset faceZMin
+ PathLog.debug('... face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin))
+ else:
+ PathLog.debug('... isFaceUp')
+ if useAngle > 180.0:
+ useAngle -= 360.0
+
+ # Apply LimitDepthToFace property for rotational operations
+ if obj.LimitDepthToFace:
+ if obj.FinalDepth.Value < face.BoundBox.ZMin:
+ PathLog.debug('obj.FinalDepth.Value < face.BoundBox.ZMin')
+ # Raise FinalDepth to face depth
+ adj_final_dep = faceZMin # face.BoundBox.ZMin # faceZMin
+ # Ensure StartDepth is above FinalDepth
+ if start_dep <= adj_final_dep:
+ start_dep = adj_final_dep + 1.0
+ msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ')
+ PathLog.warning(msg + ' {} mm.'.format(start_dep))
+ PathLog.debug('LimitDepthToFace adj_final_dep: {}'.format(adj_final_dep))
+ # Eif
+
+ face.translate(FreeCAD.Vector(0.0, 0.0, adj_final_dep - faceZMin - clrnc))
+ zExtVal = start_dep - adj_final_dep + (2 * clrnc)
+ extShp = face.removeSplitter().extrude(FreeCAD.Vector(0, 0, zExtVal))
self.removalshapes.append((extShp, False, 'pathPocketShape', useAngle, axis, start_dep, adj_final_dep))
- PathLog.debug("Extent values are strDep: {}, finDep: {}, extrd: {}".format(start_dep, adj_final_dep, extent))
+ PathLog.debug("Extent values are strDep: {}, finDep: {}, extrd: {}".format(start_dep, adj_final_dep, zExtVal))
# Efor face
# Efor
@@ -777,10 +539,10 @@ def makeTempExtrusion(base, sub, fCnt):
return (False, 0, 0)
else:
tmpWire = FreeCAD.ActiveDocument.addObject('Part::Feature', wireName).Shape = wr
- tmpWire = FreeCAD.ActiveDocument.getObject(wireName)
- tmpExt = FreeCAD.ActiveDocument.addObject('Part::Extrusion', extName)
+ tmpWireObj = FreeCAD.ActiveDocument.getObject(wireName)
+ tmpExtObj = FreeCAD.ActiveDocument.addObject('Part::Extrusion', extName)
tmpExt = FreeCAD.ActiveDocument.getObject(extName)
- tmpExt.Base = tmpWire
+ tmpExt.Base = tmpWireObj
tmpExt.DirMode = "Normal"
tmpExt.DirLink = None
tmpExt.LengthFwd = 10.0
@@ -793,8 +555,8 @@ def makeTempExtrusion(base, sub, fCnt):
tmpExt.recompute()
tmpExt.purgeTouched()
- tmpWire.purgeTouched()
- return (True, tmpWire, tmpExt)
+ tmpWireObj.purgeTouched()
+ return (True, tmpWireObj, tmpExt)
def roundValue(precision, val):
# Convert VALxe-15 numbers to zero
@@ -910,6 +672,310 @@ def roundValue(precision, val):
return (go, norm, surf)
+ def planarFaceFromExtrusionEdges(self, face, trans):
+ '''planarFaceFromExtrusionEdges(face, trans)...
+ Use closed edges to create a temporary face for use in the pocketing operation.
+ '''
+ useFace = 'useFaceName'
+ minArea = 0.0
+ fCnt = 0
+ clsd = []
+ planar = False
+ # Identify closed edges
+ for edg in face.Edges:
+ if edg.isClosed():
+ PathLog.debug(' -e.isClosed()')
+ clsd.append(edg)
+ planar = True
+
+ # Attempt to create planar faces and select that with smallest area for use as pocket base
+ if planar is True:
+ planar = False
+ for edg in clsd:
+ fCnt += 1
+ fName = sub + '_face_' + str(fCnt)
+ # Create planar face from edge
+ mFF = Part.Face(Part.Wire(Part.__sortEdges__([edg])))
+ if mFF.isNull():
+ PathLog.debug('Face(Part.Wire()) failed')
+ else:
+ if trans is True:
+ mFF.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin))
+
+ if FreeCAD.ActiveDocument.getObject(fName):
+ FreeCAD.ActiveDocument.removeObject(fName)
+
+ tmpFaceObj = FreeCAD.ActiveDocument.addObject('Part::Feature', fName).Shape = mFF
+ tmpFace = FreeCAD.ActiveDocument.getObject(fName)
+ tmpFace.purgeTouched()
+
+ if minArea == 0.0:
+ minArea = tmpFace.Shape.Face1.Area
+ useFace = fName
+ planar = True
+ elif tmpFace.Shape.Face1.Area < minArea:
+ minArea = tmpFace.Shape.Face1.Area
+ FreeCAD.ActiveDocument.removeObject(useFace)
+ useFace = fName
+ else:
+ FreeCAD.ActiveDocument.removeObject(fName)
+
+ if useFace != 'useFaceName':
+ self.useTempJobClones(useFace)
+
+ return (planar, useFace)
+
+ def clasifySub(self, bs, sub):
+ '''clasifySub(bs, sub)...
+ Given a base and a sub-feature name, returns True
+ if the sub-feature is a horizontally oriented flat face.
+ '''
+ face = bs.Shape.getElement(sub)
+
+ if type(face.Surface) == Part.Plane:
+ PathLog.debug('type() == Part.Plane')
+ if PathGeom.isVertical(face.Surface.Axis):
+ PathLog.debug(' -isVertical()')
+ # it's a flat horizontal face
+ self.horiz.append(face)
+ return True
+
+ elif PathGeom.isHorizontal(face.Surface.Axis):
+ PathLog.debug(' -isHorizontal()')
+ self.vert.append(face)
+ return True
+
+ else:
+ return False
+
+ elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis):
+ PathLog.debug('type() == Part.Cylinder')
+ # vertical cylinder wall
+ if any(e.isClosed() for e in face.Edges):
+ PathLog.debug(' -e.isClosed()')
+ # complete cylinder
+ circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center)
+ disk = Part.Face(Part.Wire(circle))
+ disk.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin))
+ self.horiz.append(disk)
+ return True
+
+ else:
+ PathLog.debug(' -none isClosed()')
+ # partial cylinder wall
+ self.vert.append(face)
+ return True
+
+ elif type(face.Surface) == Part.SurfaceOfExtrusion:
+ # extrusion wall
+ PathLog.debug('type() == Part.SurfaceOfExtrusion')
+ # Attempt to extract planar face from surface of extrusion
+ (planar, useFace) = self.planarFaceFromExtrusionEdges(face, trans=True)
+ # Save face object to self.horiz for processing or display error
+ if planar is True:
+ uFace = FreeCAD.ActiveDocument.getObject(useFace)
+ self.horiz.append(uFace.Shape.Faces[0])
+ msg = translate('Path', "Verify depth of pocket for '{}'.".format(sub))
+ msg += translate('Path', "\n
Pocket is based on extruded surface.")
+ msg += translate('Path', "\n
Bottom of pocket might be non-planar and/or not normal to spindle axis.")
+ msg += translate('Path', "\n
\n
3D pocket bottom is NOT available in this operation.")
+ PathLog.warning(msg)
+ else:
+ PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))
+
+ else:
+ PathLog.debug(' -type(face.Surface): {}'.format(type(face.Surface)))
+ return False
+
+ # Process obj.Base with rotation enabled
+ def process_base_geometry_with_rotation(self, obj, p, subCount):
+ '''process_base_geometry_with_rotation(obj, p, subCount)...
+ This method is the control method for analyzing the selected features,
+ determining their rotational needs, and creating clones as needed
+ for rotational access for the pocketing operation.
+
+ Requires the object, obj.Base index (p), and subCount reference arguments.
+ Returns two lists of tuples for continued processing into pocket paths.
+ '''
+ baseSubsTuples = []
+ allTuples = []
+ isLoop = False
+
+ (base, subsList) = obj.Base[p]
+
+ # First, check all subs collectively for loop of faces
+ if len(subsList) > 2:
+ (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)
+
+ if isLoop:
+ PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.")
+ subCount += 1
+ tup = self.process_looped_sublist(obj, norm, surf)
+ if tup:
+ allTuples.append(tup)
+ baseSubsTuples.append(tup)
+ # Eif
+
+ if not isLoop:
+ PathLog.debug(translate('Path', "Processing subs individually ..."))
+ for sub in subsList:
+ subCount += 1
+ tup = self.process_nonloop_sublist(obj, base, sub)
+ if tup:
+ allTuples.append(tup)
+ baseSubsTuples.append(tup)
+ # Eif
+
+ return (baseSubsTuples, allTuples)
+
+ def process_looped_sublist(self, obj, norm, surf):
+ '''process_looped_sublist(obj, norm, surf)...
+ Process set of looped faces when rotation is enabled.
+ '''
+ PathLog.debug(translate("Path", "Selected faces form loop. Processing looped faces."))
+ rtn = False
+ (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
+
+ if rtn is True:
+ faceNums = ""
+ for f in subsList:
+ faceNums += '_' + f.replace('Face', '')
+ (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNums) # pylint: disable=unused-variable
+
+ # Verify faces are correctly oriented - InverseAngle might be necessary
+ PathLog.debug("Checking if faces are oriented correctly after rotation.")
+ for sub in subsList:
+ face = clnBase.Shape.getElement(sub)
+ if type(face.Surface) == Part.Plane:
+ if not PathGeom.isHorizontal(face.Surface.Axis):
+ rtn = False
+ PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied."))
+ break
+
+ if rtn is False:
+ PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.") + ' 1')
+ if obj.InverseAngle:
+ (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
+ else:
+ if obj.AttemptInverseAngle is True:
+ (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
+ else:
+ msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
+ PathLog.warning(msg)
+
+ if angle < 0.0:
+ angle += 360.0
+
+ tup = clnBase, subsList, angle, axis, clnStock
+ else:
+ if self.warnDisabledAxis(obj, axis) is False:
+ PathLog.debug("No rotation used")
+ axis = 'X'
+ angle = 0.0
+ stock = PathUtils.findParentJob(obj).Stock
+ tup = base, subsList, angle, axis, stock
+ # Eif
+ return tup
+
+ def process_nonloop_sublist(self, obj, base, sub):
+ '''process_nonloop_sublist(obj, sub)...
+ Process sublist with non-looped set of features when rotation is enabled.
+ '''
+
+ if sub[:4] != 'Face':
+ ignoreSub = base.Name + '.' + sub
+ PathLog.error(translate('Path', "Selected feature is not a Face. Ignoring: {}".format(ignoreSub)))
+ return False
+
+ rtn = False
+ face = base.Shape.getElement(sub)
+ if type(face.Surface) == Part.SurfaceOfExtrusion:
+ # extrusion wall
+ PathLog.debug('analyzing type() == Part.SurfaceOfExtrusion')
+ # Attempt to extract planar face from surface of extrusion
+ (planar, useFace) = self.planarFaceFromExtrusionEdges(face, trans=False)
+ # Save face object to self.horiz for processing or display error
+ if planar is True:
+ base = FreeCAD.ActiveDocument.getObject(useFace)
+ sub = 'Face1'
+ PathLog.debug(' -successful face created: {}'.format(useFace))
+ else:
+ PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))
+
+ (norm, surf) = self.getFaceNormAndSurf(face)
+ (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
+ PathLog.debug("initial {}".format(praInfo))
+
+ clnBase = base
+ faceIA = clnBase.Shape.getElement(sub)
+
+ if rtn is True:
+ faceNum = sub.replace('Face', '')
+ PathLog.debug("initial applyRotationalAnalysis")
+ (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNum)
+ # Verify faces are correctly oriented - InverseAngle might be necessary
+ faceIA = clnBase.Shape.getElement(sub)
+ (norm, surf) = self.getFaceNormAndSurf(faceIA)
+ (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
+ PathLog.debug("follow-up {}".format(praInfo2))
+
+ isFaceUp = self.isFaceUp(clnBase, faceIA)
+ if isFaceUp:
+ rtn = False
+
+ if round(abs(praAngle), 8) == 180.0:
+ rtn = False
+ if not isFaceUp:
+ PathLog.debug('initial isFaceUp is False')
+ angle = 0.0
+ # Eif
+
+ if rtn:
+ # initial rotation failed, attempt inverse rotation if user requests it
+ PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.") + ' 2')
+ if obj.AttemptInverseAngle:
+ PathLog.debug(translate("Path", "Applying inverse angle automatically."))
+ (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
+ else:
+ if obj.InverseAngle:
+ PathLog.debug(translate("Path", "Applying inverse angle manually."))
+ (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
+ else:
+ msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
+ PathLog.warning(msg)
+
+ faceIA = clnBase.Shape.getElement(sub)
+ if not self.isFaceUp(clnBase, faceIA):
+ angle += 180.0
+
+ # Normalize rotation angle
+ if angle < 0.0:
+ angle += 360.0
+ elif angle > 360.0:
+ angle -= 360.0
+
+ return (clnBase, [sub], angle, axis, clnStock)
+
+ if not self.warnDisabledAxis(obj, axis):
+ PathLog.debug(str(sub) + ": No rotation used")
+ axis = 'X'
+ angle = 0.0
+ stock = PathUtils.findParentJob(obj).Stock
+ return (base, [sub], angle, axis, stock)
+
+ # Method to add temporary debug object
+ def _addDebugObject(self, objName, objShape):
+ '''_addDebugObject(objName, objShape)...
+ Is passed a desired debug object's desired name and shape.
+ This method creates a FreeCAD object for debugging purposes.
+ The created object must be deleted manually from the object tree
+ by the user.
+ '''
+ if self.isDebug:
+ O = FreeCAD.ActiveDocument.addObject('Part::Feature', 'debug_' + objName)
+ O.Shape = objShape
+ O.purgeTouched()
+
def SetupProperties():
setup = PathPocketBase.SetupProperties()