Improve code structure; correct 4th-axis depth issues

This commit is contained in:
Russell Johnson
2019-06-11 14:52:16 -05:00
parent 376486d621
commit a7202f151f
3 changed files with 947 additions and 401 deletions

View File

@@ -29,7 +29,6 @@
# PathGeom.combineConnectedShapes(vertical) algorithm grouping
# - Need to add face boundbox analysis code to vertical axis_angle
# section to identify highest zMax for all faces included in group
# - Need to implement judgeStartDepth() within rotational depth calculations
# - FUTURE: Re-iterate PathAreaOp.py need to relocate rotational settings
# to Job setup, under Machine settings tab
@@ -39,6 +38,7 @@ import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathPocketBase as PathPocketBase
import PathScripts.PathUtils as PathUtils
import TechDraw
import math
@@ -50,8 +50,8 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of shape based Pocket operation."
__contributors__ = "mlampert [FreeCAD], russ4262 (Russell Johnson)"
__created__ = "2017"
__scriptVersion__ = "2b testing"
__lastModified__ = "2019-06-03 03:18 CST"
__scriptVersion__ = "2e testing"
__lastModified__ = "2019-06-11 14:30 CST"
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
@@ -205,25 +205,28 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
'''Proxy object for Pocket operation.'''
def areaOpFeatures(self, obj):
# return super(self.__class__, self).areaOpFeatures(obj) | PathOp.FeatureLocations | PathOp.FeatureRotation
return super(self.__class__, self).areaOpFeatures(obj) | PathOp.FeatureLocations
def initPocketOp(self, obj):
'''initPocketOp(obj) ... setup receiver'''
if not hasattr(obj, 'UseOutline'):
obj.addProperty('App::PropertyBool', 'UseOutline', 'Pocket', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Uses the outline of the base geometry.'))
obj.UseOutline = False
if not hasattr(obj, 'ExtensionLengthDefault'):
obj.addProperty('App::PropertyDistance', 'ExtensionLengthDefault', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Default length of extensions.'))
if not hasattr(obj, 'ExtensionFeature'):
obj.addProperty('App::PropertyLinkSubListGlobal', 'ExtensionFeature', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of features to extend.'))
if not hasattr(obj, 'ExtensionCorners'):
obj.addProperty('App::PropertyBool', 'ExtensionCorners', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'When enabled connected extension edges are combined to wires.'))
# obj.ExtensionCorners = True
if not hasattr(obj, 'B_AxisErrorOverride'):
obj.addProperty('App::PropertyBool', 'B_AxisErrorOverride', 'Rotation', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Match B rotations to model (error in FreeCAD rendering).'))
if not hasattr(obj, 'ReverseDirection'):
obj.addProperty('App::PropertyBool', 'ReverseDirection', 'Rotation', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Reverse direction of pocket operation.'))
obj.addProperty('App::PropertyBool', 'ReverseDirection', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Reverse direction of pocket operation.'))
if not hasattr(obj, 'InverseAngle'):
obj.addProperty('App::PropertyBool', 'InverseAngle', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Inverse the angle. Example: -22.5 -> 22.5 degrees.'))
if not hasattr(obj, 'B_AxisErrorOverride'):
obj.addProperty('App::PropertyBool', 'B_AxisErrorOverride', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Match B rotations to model (error in FreeCAD rendering).'))
if not hasattr(obj, 'AttemptInverseAngle'):
obj.addProperty('App::PropertyBool', 'AttemptInverseAngle', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Attempt the inverse angle for face access if original rotation fails.'))
obj.setEditorMode('ExtensionFeature', 2)
@@ -237,161 +240,241 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
def areaOpShapes(self, obj):
'''areaOpShapes(obj) ... return shapes representing the solids to be removed.'''
PathLog.track()
PathLog.debug("areaOpShapes() in PathPocketShape.py")
PathLog.debug("----- areaOpShapes() in PathPocketShape.py")
import Draft
baseSubsTuples = []
subCount = 0
allTuples = []
self.tempNameList = []
self.delTempNameList = 0
def judgeFinalDepth(obj, fD):
if obj.FinalDepth.Value >= fD:
return obj.FinalDepth.Value
else:
return fD
def clasifySub(self, bs, sub):
face = bs.Shape.getElement(sub)
def planarFaceFromExtrusionEdges(clsd):
useFace = 'useFaceName'
minArea = 0.0
fCnt = 0
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:
mFF.translate(FreeCAD.Vector(0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin))
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)
return (planar, useFace)
def judgeStartDepth(obj, sD):
if obj.StartDepth.Value >= sD:
return obj.StartDepth.Value
else:
return sD
def sortTuplesByIndex(TupleList, tagIdx): # return (TagList, GroupList)
# Separate elements, regroup by orientation (axis_angle combination)
TagList = ['X34.2']
GroupList = [[(2.3, 3.4, 'X')]]
for tup in TupleList:
if tup[tagIdx] in TagList:
# Determine index of found string
i = 0
for orn in TagList:
if orn == tup[4]:
break
i += 1
GroupList[i].append(tup)
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
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:
TagList.append(tup[4]) # add orientation entry
GroupList.append([tup]) # add orientation entry
# Remove temp elements
TagList.pop(0)
GroupList.pop(0)
return (TagList, GroupList)
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')
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
(planar, useFace) = planarFaceFromExtrusionEdges(clsd)
# Save face object to self.horiz for processing or display error
if planar is True:
self.tempNameList.append(useFace)
self.delTempNameList += 1
uFace = FreeCAD.ActiveDocument.getObject(useFace)
self.horiz.append(uFace.Shape.Faces[0])
msg = "<b>Verify depth of pocket for '{}'.</b>".format(sub)
msg += "\n<br>Pocket is based on extruded surface."
msg += "\n<br>Bottom of pocket might be non-planar and/or not normal to spindle axis."
msg += "\n<br>\n<br><i>3D pocket bottom is NOT available in this operation</i>."
msg = translate('Path', msg)
PathLog.error(msg)
title = translate('Path', 'Depth Warning')
self.guiMessage(title, msg, False)
else:
PathLog.error("Could not create a planar face from edges in {}.".format(sub))
else:
PathLog.debug('type() == OTHER')
PathLog.debug(' -type(face.Surface): {}'.format(type(face.Surface)))
return False
if obj.Base:
PathLog.debug('obj.Base exists. Processing...')
PathLog.debug('Processing... obj.Base')
self.removalshapes = []
self.horiz = []
vertical = []
# ----------------------------------------------------------------------
if obj.EnableRotation != 'Off':
if obj.EnableRotation == 'Off':
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
baseSubsTuples.append((base, subList, 0.0, 'X', stock))
else:
for p in range(0, len(obj.Base)):
(base, subsList) = obj.Base[p]
for sub in subsList:
if 'Face' in sub:
strDep = obj.StartDepth.Value
finDep = obj.FinalDepth.Value
rtn = False
go = False
face = base.Shape.getElement(sub)
(rtn, angle, axis, praInfo) = self.pocketRotationAnalysis(obj, face, prnt=True)
PathLog.info("praInfo: \n" + str(praInfo))
if len(subsList) > 2:
# Check for loop of faces
(go, norm, surf) = self.checkForFacesLoop(base, subsList)
if rtn is True:
PathLog.debug(str(sub) + ": rotating model to make face normal at (0,0,1) ...")
if go is True:
PathLog.debug("Common Surface.Axis value found for loop faces.")
rtn = False
(rtn, angle, axis, praInfo) = self.pocketRotationAnalysis(obj, norm, surf, prnt=True)
# Create a temporary clone of model for rotational use.
rndAng = round(angle, 8)
if rndAng < 0.0: # neg sign is converted to underscore in clone name creation.
tag = axis + '_' + str(math.fabs(rndAng)).replace('.', '_')
if rtn is True:
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, len(subsList))
# 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
break
if rtn is False:
if obj.AttemptInverseAngle is True:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
tag = axis + str(rndAng).replace('.', '_')
clnNm = base.Name + '_' + tag
if clnNm not in self.cloneNames:
self.cloneNames.append(clnNm)
PathLog.debug("tmp clone created: " + str(clnNm))
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
newBase = FreeCAD.ActiveDocument.getObject(clnNm)
# Determine Z translation values
if axis == 'X':
bZ = math.sin(math.radians(angle)) * newBase.Placement.Base.y
vect = FreeCAD.Vector(1, 0, 0)
elif axis == 'Y':
bZ = math.sin(math.radians(angle)) * newBase.Placement.Base.x
if obj.B_AxisErrorOverride is True:
bZ = -1 * bZ
vect = FreeCAD.Vector(0, 1, 0)
# Rotate base to such that Surface.Axis of pocket bottom is Z=1
base = Draft.rotate(newBase, angle, center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False)
# face = base.Shape.getElement(sub)
else:
PathLog.debug(str(sub) + ": no rotation used")
axis = 'X'
angle = 0.0
tag = axis + str(angle).replace('.', '_')
# face = base.Shape.getElement(sub)
# Eif
PathLog.debug("base.Name: " + str(base.Name))
tup = base, sub, tag, angle, axis
allTuples.append(tup)
PathLog.error(" --Consider toggling the InverseAngle property and recomputing the operation.")
tup = clnBase, subsList, angle, axis, clnStock
else:
PathLog.error(" -Error with pocketRotationAnalysis()")
# Eif
subCount += 1
# Efor
# Efor
(hTags, hGrps) = sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
for o in range(0, len(hTags)):
PathLog.debug('hTag: {}'.format(hTags[o]))
subList = []
for (base, sub, tag, angle, axis) in hGrps[o]:
subList.append(sub)
pair = base, subList, angle, axis
baseSubsTuples.append(pair)
# Efor
else:
PathLog.info("Use Rotation feature(property) is 'Off'.")
for (base, subList) in obj.Base:
baseSubsTuples.append((base, subList, 'pathPocketShape', 0.0, 'X'))
allTuples.append(tup)
baseSubsTuples.append(tup)
# Eif
if go is False:
PathLog.debug("Will process subs individually ...")
for sub in subsList:
subCount += 1
if 'Face' in sub:
rtn = False
PathLog.debug(translate('Path', "Base Geometry sub: {}".format(sub)))
face = base.Shape.getElement(sub)
(norm, surf) = self.getFaceNormAndSurf(face)
(rtn, angle, axis, praInfo)= self.pocketRotationAnalysis(obj, norm, surf, prnt=True)
if rtn is True:
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount)
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = clnBase.Shape.getElement(sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.pocketRotationAnalysis(obj, norm, surf, prnt=False)
if rtn is True:
PathLog.error(" --Face not aligned after initial rotation.")
if obj.AttemptInverseAngle is True:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
PathLog.error(" --Consider toggling the InverseAngle property and recomputing the operation.")
else:
PathLog.debug(" --Face appears to be oriented correctly.")
tup = clnBase, [sub], angle, axis, clnStock
else:
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 object is not a face. Ignoring: {}".format(ignoreSub)))
# Eif
# Efor
# Efor
if False:
if False:
(Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
for o in range(0, len(Tags)):
subList = []
for (base, sub, tag, angle, axis, stock) in Grps[o]:
subList.append(sub)
pair = base, subList, angle, axis, stock
baseSubsTuples.append(pair)
if False:
for (bs, sb, tg, agl, ax, stk) in allTuples:
pair = bs, [sb], agl, ax, stk
baseSubsTuples.append(pair)
# ----------------------------------------------------------------------
# for o in obj.Base:
for o in baseSubsTuples:
PathLog.debug('Base item: {}'.format(o))
base = o[0]
self.horiz = []
self.vert = []
subBase = o[0]
angle = o[2]
axis = o[3]
stock = o[4]
for sub in o[1]:
if 'Face' in sub:
face = base.Shape.getElement(sub)
if type(face.Surface) == Part.Plane and PathGeom.isVertical(face.Surface.Axis):
# it's a flat horizontal face
self.horiz.append(face)
elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis):
# vertical cylinder wall
if any(e.isClosed() for e in face.Edges):
# complete cylinder
circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center)
disk = Part.Face(Part.Wire(circle))
self.horiz.append(disk)
else:
# partial cylinder wall
vertical.append(face)
elif type(face.Surface) == Part.Plane and PathGeom.isHorizontal(face.Surface.Axis):
vertical.append(face)
else:
PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (base.Label, sub))
if clasifySub(self, subBase, sub) is False:
PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub))
# --------- Originally NOT in FOR loop above --------------
self.vertical = PathGeom.combineConnectedShapes(vertical)
self.vertical = PathGeom.combineConnectedShapes(self.vert)
self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical]
for wire in self.vWires:
w = PathGeom.removeDuplicateEdges(wire)
face = Part.Face(w)
# face.tessellate(0.1)
if PathGeom.isRoughly(face.Area, 0):
PathLog.error(translate('PathPocket', 'Vertical faces do not form a loop - ignoring'))
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:
self.horiz.append(face)
@@ -403,12 +486,10 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
face = Part.Face(wire)
self.horiz.append(face)
self.exts.append(face)
# Efor
# move all horizontal faces to FinalDepth
for f in self.horiz:
# f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin))
finDep = judgeFinalDepth(obj, face.BoundBox.ZMin)
finDep = max(obj.FinalDepth.Value, f.BoundBox.ZMin)
f.translate(FreeCAD.Vector(0, 0, finDep - f.BoundBox.ZMin))
# check all faces and see if they are touching/overlapping and combine those into a compound
@@ -423,35 +504,18 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
else:
self.horizontal.append(shape)
# self.removalshapes = [(face.removeSplitter().extrude(extent), False) for face in self.horizontal]
for face in self.horizontal:
# Over-write default final depth value, leaves manual override by user
strDep = judgeStartDepth(obj, face.BoundBox.ZMax)
#strDep = judgeStartDepth(obj, face.BoundBox.ZMax + self.leadIn)
#if strDep < base.Shape.BoundBox.ZMax:
# strDep = base.Shape.BoundBox.ZMax
finDep = judgeFinalDepth(obj, face.BoundBox.ZMin)
if strDep <= finDep:
strDep = finDep + self.leadIn
# FreeCAD.Units.Quantity(z, FreeCAD.Units.Length)
# FreeCAD.Units.Quantity(self.form.ifWidth.text()).Value
title = translate("Path", "Depth Warning")
msg = "Start depth <= final depth.\nIncrease the start depth.\nPocket depth is {} mm.".format(finDep)
PathLog.error(msg)
self.guiMessage(title, msg) # GUI messages
else:
obj.StartDepth.Value = strDep
obj.FinalDepth.Value = finDep
# extrude all faces up to StartDepth and those are the removal shapes
# extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value)
(strDep, finDep) = self.calculateStartFinalDepths(obj, face, stock)
# PathLog.debug("Extent depths are str: {}, and fin: {}".format(strDep, finDep))
extent = FreeCAD.Vector(0, 0, strDep - finDep)
self.removalshapes.append((face.removeSplitter().extrude(extent), False, 'pathPocketShape', angle, axis))
self.removalshapes.append((face.removeSplitter().extrude(extent), False, 'pathPocketShape', angle, axis, strDep, finDep))
# Efor face
# Efor
else: # process the job base object as a whole
PathLog.debug("processing the whole job base object")
PathLog.debug('Processing... whole base')
finDep = obj.FinalDepth.Value
strDep = obj.StartDepth.Value
self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model]
stockBB = self.stock.Shape.BoundBox
@@ -462,24 +526,30 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2))
self.bodies.append(body)
# self.removalshapes.append((self.stock.Shape.cut(body), False))
self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X'))
self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep))
for (shape, hole, sub, angle, axis) in self.removalshapes:
for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes:
shape.tessellate(0.05) # originally 0.1
if self.removalshapes:
obj.removalshape = self.removalshapes[0][0]
if self.delTempNameList > 0:
for tmpNm in self.tempNameList:
FreeCAD.ActiveDocument.removeObject(tmpNm)
return self.removalshapes
def areaOpSetDefaultValues(self, obj, job):
'''areaOpSetDefaultValues(obj, job) ... set default values'''
obj.StepOver = 100
obj.ZigZagAngle = 45
if job and job.Stock:
bb = job.Stock.Shape.BoundBox
obj.OpFinalDepth = bb.ZMin
obj.OpStartDepth = bb.ZMax
obj.ExtensionCorners = True
obj.ExtensionCorners = False
obj.UseOutline = False
obj.ReverseDirection = False
obj.InverseAngle = False
obj.B_AxisErrorOverride = False
obj.AttemptInverseAngle = True
obj.setExpression('ExtensionLengthDefault', 'OpToolDiameter / 2')
def createExtension(self, obj, extObj, extFeature, extSub):
@@ -499,8 +569,168 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
PathLog.track(obj.Label, len(extensions))
obj.ExtensionFeature = [(ext.obj, ext.getSubLink()) for ext in extensions]
def checkForFacesLoop(self, base, subsList):
'''checkForFacesLoop(self, base, subsList)...
Accepts a list of face names for the given base.
Checks to determine if they are looped together.
'''
PathLog.track()
fCnt = 0
go = True
vertLoopFace = None
tempNameList = []
delTempNameList = 0
saSum = FreeCAD.Vector(0.0, 0.0, 0.0)
norm = FreeCAD.Vector(0.0, 0.0, 0.0)
surf = FreeCAD.Vector(0.0, 0.0, 0.0)
def makeTempExtrusion(base, sub, fCnt):
extName = 'tmpExtrude' + str(fCnt)
wireName = 'tmpWire' + str(fCnt)
wr = Part.Wire(Part.__sortEdges__(base.Shape.getElement(sub).Edges))
if wr.isNull():
PathLog.error('Now wire created from {}'.format(sub))
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)
tmpExt = FreeCAD.ActiveDocument.getObject(extName)
tmpExt.Base = tmpWire
tmpExt.DirMode = "Normal"
tmpExt.DirLink = None
tmpExt.LengthFwd = 10.0
tmpExt.LengthRev = 0.0
tmpExt.Solid = True
tmpExt.Reversed = False
tmpExt.Symmetric = False
tmpExt.TaperAngle = 0.0
tmpExt.TaperAngleRev = 0.0
tmpExt.recompute()
tmpExt.purgeTouched()
tmpWire.purgeTouched()
return (True, tmpWire, tmpExt)
def roundValue(val):
zTol = 1.0E-8
rndTol = 1.0 - zTol
# Convert VALxe-15 numbers to zero
if math.fabs(val) <= zTol:
return 0.0
# Convert VAL.99999999 to next integer
elif math.fabs(val % 1) > rndTol:
return round(val)
else:
return round(val, 8)
for sub in subsList:
if 'Face' in sub:
fCnt += 1
saSum = saSum.add(base.Shape.getElement(sub).Surface.Axis)
# PathLog.debug("saSum: {}".format(saSum))
if fCnt < 3:
go = False
# Determine if all faces combined point toward loop center = False
if PathGeom.isRoughly(0, saSum.x):
if PathGeom.isRoughly(0, saSum.y):
if PathGeom.isRoughly(0, saSum.z):
PathLog.debug("Combined subs suggest loop of faces. Checking ...")
go is True
if go is True:
lastExtrusion = None
matchList = []
go = False
# Cycle through subs, extruding to solid for each
for sub in subsList:
if 'Face' in sub:
fCnt += 1
go = False
# Extrude face to solid
(rtn, tmpWire, tmpExt) = makeTempExtrusion(base, sub, fCnt)
# If success, record new temporary objects for deletion
if rtn is True:
tempNameList.append(tmpExt.Name)
tempNameList.append(tmpWire.Name)
delTempNameList += 1
if lastExtrusion is None:
lastExtrusion = tmpExt
rtn = True
else:
go = False
break
# Cycle through faces on each extrusion, looking for common normal faces for rotation analysis
if len(matchList) == 0:
for fc in lastExtrusion.Shape.Faces:
(norm, raw) = self.getFaceNormAndSurf(fc)
rnded = FreeCAD.Vector(roundValue(raw.x), roundValue(raw.y), roundValue(raw.z))
if rnded.x == 0.0 or rnded.y == 0.0 or rnded.z == 0.0:
for fc2 in tmpExt.Shape.Faces:
(norm2, raw2) = self.getFaceNormAndSurf(fc2)
rnded2 = FreeCAD.Vector(roundValue(raw2.x), roundValue(raw2.y), roundValue(raw2.z))
if rnded == rnded2:
matchList.append(fc2)
go = True
else:
for m in matchList:
(norm, raw) = self.getFaceNormAndSurf(m)
rnded = FreeCAD.Vector(roundValue(raw.x), roundValue(raw.y), roundValue(raw.z))
for fc2 in tmpExt.Shape.Faces:
(norm2, raw2) = self.getFaceNormAndSurf(fc2)
rnded2 = FreeCAD.Vector(roundValue(raw2.x), roundValue(raw2.y), roundValue(raw2.z))
if rnded.x == 0.0 or rnded.y == 0.0 or rnded.z == 0.0:
if rnded == rnded2:
go = True
# Eif
if go is False:
break
# Eif
# Eif 'Face'
# Efor
if go is True:
go = False
if len(matchList) == 2:
saTotal = FreeCAD.Vector(0.0, 0.0, 0.0)
for fc in matchList:
(norm, raw) = self.getFaceNormAndSurf(fc)
rnded = FreeCAD.Vector(roundValue(raw.x), roundValue(raw.y), roundValue(raw.z))
if (rnded.y > 0.0 or rnded.z > 0.0) and vertLoopFace is None:
vertLoopFace = fc
saTotal = saTotal.add(rnded)
# PathLog.debug(" -saTotal: {}".format(saTotal))
if saTotal == FreeCAD.Vector(0.0, 0.0, 0.0):
if vertLoopFace is not None:
go = True
if go is True:
(norm, surf) = self.getFaceNormAndSurf(vertLoopFace)
else:
PathLog.debug(translate('Path', ' -Can not identify loop.'))
if delTempNameList > 0:
for tmpNm in tempNameList:
FreeCAD.ActiveDocument.removeObject(tmpNm)
return (go, norm, surf)
def SetupProperties():
return PathPocketBase.SetupProperties() + [ 'UseOutline', 'ExtensionCorners' ]
setup = PathPocketBase.SetupProperties()
setup.append('UseOutline')
setup.append('ExtensionLengthDefault')
setup.append('ExtensionFeature')
setup.append('ExtensionCorners')
setup.append("ReverseDirection")
setup.append("InverseAngle")
setup.append("B_AxisErrorOverride")
setup.append("AttemptInverseAngle")
return setup
def Create(name, obj = None):
'''Create(name) ... Creates and returns a Pocket operation.'''