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

@@ -23,16 +23,16 @@
# ***************************************************************************
# SCRIPT NOTES:
# - Need to add "EnableRotation" property to task window UI, and attach appropriate onChange event handler
# - Consult FC community about wording for "EnableRotation" property
# - FUTURE: Relocate rotational calculations to Job setup tool, creating a Machine section
# with axis & rotation toggles and associated min/max values
# with axis & rotation toggles and associated min/max values
import FreeCAD
import FreeCADGui
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import Draft
# from PathScripts.PathUtils import waiting_effects
from PySide import QtCore
@@ -44,8 +44,8 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Base class and properties for Path.Area based operations."
__contributors__ = "mlampert [FreeCAD], russ4262 (Russell Johnson)"
__createdDate__ = "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())
@@ -68,9 +68,10 @@ class ObjectOp(PathOp.ObjectOp):
operations.'''
# These are static while document is open, if it contains a 3D Surface Op
initFinalDepth = None
initOpFinalDepth = None
initOpStartDepth = None
initWithRotation = False
defValsSet = False
docRestored = False
def opFeatures(self, obj):
@@ -112,15 +113,15 @@ class ObjectOp(PathOp.ObjectOp):
Can safely be overwritten by subclasses.'''
pass
def areaOpShapeForDepths(self, obj):
def areaOpShapeForDepths(self, obj, job):
'''areaOpShapeForDepths(obj) ... returns the shape used to make an initial calculation for the depths being used.
The default implementation returns the job's Base.Shape'''
job = PathUtils.findParentJob(obj)
if job and job.Base:
PathLog.debug("job=%s base=%s shape=%s" % (job, job.Base, job.Base.Shape))
return job.Base.Shape
if job:
PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label)
if job.Stock:
PathLog.debug("job=%s base=%s shape=%s" % (job, job.Stock, job.Stock.Shape))
return job.Stock.Shape
else:
PathLog.warning(translate("PathAreaOp", "job %s has no Base.") % job.Label)
else:
PathLog.warning(translate("PathAreaOp", "no job for op %s found.") % obj.Label)
return None
@@ -157,11 +158,14 @@ class ObjectOp(PathOp.ObjectOp):
for prop in ['AreaParams', 'PathParams', 'removalshape']:
if hasattr(obj, prop):
obj.setEditorMode(prop, 2)
self.initOpFinalDepth = obj.OpFinalDepth.Value
self.initOpStartDepth = obj.OpStartDepth.Value
self.docRestored = True
#PathLog.debug("Imported existing OpFinalDepth of " + str(self.initOpFinalDepth) + " for recompute() purposes.")
#PathLog.debug("Imported existing StartDepth of " + str(self.initOpStartDepth) + " for recompute() purposes.")
self.setupAdditionalProperties(obj)
self.docRestored = True
self.areaOpOnDocumentRestored(obj)
def areaOpOnDocumentRestored(self, obj):
@@ -174,50 +178,73 @@ class ObjectOp(PathOp.ObjectOp):
areaOpShapeForDepths() return value.
Do not overwrite, overwrite areaOpSetDefaultValues(obj, job) instead.'''
PathLog.debug("opSetDefaultValues(%s, %s)" % (obj.Label, job.Label))
if PathOp.FeatureDepths & self.opFeatures(obj):
try:
shape = self.areaOpShapeForDepths(obj)
except:
shape = None
maxDep = 1.0
minDep = 0.0
if obj.EnableRotation == 'Off':
bb = job.Stock.Shape.BoundBox
maxDep = bb.ZMax
minDep = bb.ZMin
else:
opHeights = self.opDetermineRotationRadii(obj) # return is list with tuples [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfst)]
(xRotRad, yRotRad, zRotRad) = opHeights[0]
# (clrOfst, safOfset) = opHeights[1]
maxDep = xRotRad
if yRotRad > xRotRad:
maxDep = yRotRad
minDep = -1 * maxDep
# Manage operation start and final depths
if self.docRestored is True:
PathLog.debug("doc restored")
obj.FinalDepth.Value = obj.OpFinalDepth.Value
else:
PathLog.debug("new operation")
obj.OpFinalDepth.Value = minDep
obj.OpStartDepth.Value = maxDep
if self.initOpFinalDepth is None and self.initFinalDepth is None:
self.initFinalDepth = minDep
self.initOpFinalDepth = minDep
else:
PathLog.debug("-initFinalDepth" + str(self.initFinalDepth))
PathLog.debug("-initOpFinalDepth" + str(self.initOpFinalDepth))
# Initial setting for EnableRotation is taken from Job settings/SetupSheet
# User may override on per-operation basis as needed.
if hasattr(job.SetupSheet, 'SetupEnableRotation'):
obj.EnableRotation = job.SetupSheet.SetupEnableRotation
else:
obj.EnableRotation = 'Off'
PathLog.debug("opSetDefaultValues(): Enable Rotation: {}".format(obj.EnableRotation))
if PathOp.FeatureDepths & self.opFeatures(obj):
try:
shape = self.areaOpShapeForDepths(obj, job)
except:
shape = None
# Set initial start and final depths
if shape is None:
PathLog.debug("shape is None")
startDepth = 1.0
finalDepth = 0.0
else:
bb = job.Stock.Shape.BoundBox
startDepth = bb.ZMax
finalDepth = bb.ZMin
# Adjust start and final depths if rotation is enabled
if obj.EnableRotation != 'Off':
self.initWithRotation = True
# Calculate rotational distances/radii
opHeights = self.opDetermineRotationRadii(obj) # return is list with tuples [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfset)]
(xRotRad, yRotRad, zRotRad) = opHeights[0]
# (self.safOfset, self.safOfst) = opHeights[1]
PathLog.debug("opHeights[0]: " + str(opHeights[0]))
PathLog.debug("opHeights[1]: " + str(opHeights[1]))
if obj.EnableRotation == 'A(x)':
startDepth = xRotRad
if obj.EnableRotation == 'B(y)':
startDepth = yRotRad
else:
startDepth = max(xRotRad, yRotRad)
finalDepth = -1 * startDepth
# Manage operation start and final depths
if self.docRestored is True: # This op is NOT the first in the Operations list
PathLog.debug("Doc restored")
obj.FinalDepth.Value = obj.OpFinalDepth.Value
obj.StartDepth.Value = obj.OpStartDepth.Value
else:
PathLog.debug("New operation")
obj.StartDepth.Value = startDepth
obj.FinalDepth.Value = finalDepth
obj.OpStartDepth.Value = startDepth
obj.OpFinalDepth.Value = finalDepth
# obj.OpStartDepth.UserString = str(startDepth) + ' mm' # Read-only
# obj.OpFinalDepth.UserString = str(finalDepth) + ' mm' # Read-only
if obj.EnableRotation != 'Off':
if self.initOpFinalDepth is None:
self.initOpFinalDepth = finalDepth
PathLog.debug("Saved self.initOpFinalDepth")
if self.initOpStartDepth is None:
self.initOpStartDepth = startDepth
PathLog.debug("Saved self.initOpStartDepth")
self.defValsSet = True
PathLog.debug("Default OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value))
PathLog.debug("Default Depths are Start: {}, and Final: {}".format(startDepth, finalDepth))
self.areaOpSetDefaultValues(obj, job)
@@ -292,17 +319,74 @@ class ObjectOp(PathOp.ObjectOp):
areaOpShapes(obj) ... the shape for path area to process
areaOpUseProjection(obj) ... return true if operation can use projection
instead.'''
PathLog.debug("\n\n----- opExecute() in PathAreaOp.py")
PathLog.track()
self.endVector = None
PathLog.debug("opExecute() in PathAreaOp.py")
PathLog.debug("\n\n----- opExecute() in PathAreaOp.py")
# PathLog.debug("OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value))
# PathLog.debug("Depths are Start: {}, and Final: {}".format(obj.StartDepth.Value, obj.FinalDepth.Value))
# PathLog.debug("initOpDepths are Start: {}, and Final: {}".format(self.initOpStartDepth, self.initOpFinalDepth))
# Instantiate class variables for operation reference
self.endVector = None
self.rotateFlag = False
self.leadIn = 2.0 # safOfset / 2.0
self.leadIn = 2.0 # self.safOfst / 2.0
self.cloneNames = []
self.guiMsgs = [] # list of message tuples (title, msg) to be displayed in GUI
self.stockBB = PathUtils.findParentJob(obj).Stock.Shape.BoundBox
# Initialize depthparams
# Import OpFinalDepth from pre-existing operation for recompute() scenarios
if self.defValsSet is True:
PathLog.debug("self.defValsSet is True.")
if self.initOpStartDepth is not None:
if self.initOpStartDepth != obj.OpStartDepth.Value:
obj.OpStartDepth.Value = self.initOpStartDepth
obj.StartDepth.Value = self.initOpStartDepth
if self.initOpFinalDepth is not None:
if self.initOpFinalDepth != obj.OpFinalDepth.Value:
obj.OpFinalDepth.Value = self.initOpFinalDepth
obj.FinalDepth.Value = self.initOpFinalDepth
self.defValsSet = False
if obj.EnableRotation != 'Off':
self.useRotJobClones('Start')
# Calculate operation heights based upon rotation radii
opHeights = self.opDetermineRotationRadii(obj)
(self.xRotRad, self.yRotRad, self.zRotRad) = opHeights[0]
(self.safOfset, self.safOfst) = opHeights[1]
# Set clearnance and safe heights based upon rotation radii
if obj.EnableRotation == 'A(x)':
self.strDep = self.xRotRad
elif obj.EnableRotation == 'B(y)':
self.strDep = self.yRotRad
else:
self.strDep = max(self.xRotRad, self.yRotRad)
self.finDep = -1 * self.strDep
obj.ClearanceHeight.Value = self.strDep + self.safOfset
obj.SafeHeight.Value = self.strDep + self.safOfst
# Set axial feed rates based upon horizontal feed rates
safeCircum = 2 * math.pi * obj.SafeHeight.Value
self.axialFeed = 360 / safeCircum * self.horizFeed
self.axialRapid = 360 / safeCircum * self.horizRapid
if self.initWithRotation == False:
if obj.FinalDepth.Value == obj.OpFinalDepth.Value:
obj.FinalDepth.Value = self.finDep
if obj.StartDepth.Value == obj.OpStartDepth.Value:
obj.StartDepth.Value = self.strDep
# Create visual axises for debugging purposes.
if PathLog.getLevel() == 2:
self.visualAxis()
else:
self.strDep = obj.StartDepth.Value
self.finDep = obj.FinalDepth.Value
# Initiate depthparams and calculate operation heights for rotational operation
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
self.depthparams = PathUtils.depth_params(
clearance_height=obj.ClearanceHeight.Value,
@@ -313,41 +397,21 @@ class ObjectOp(PathOp.ObjectOp):
final_depth=obj.FinalDepth.Value,
user_depths=None)
# Recalculate operation heights for rotational operation
if obj.EnableRotation != 'Off':
# Calculate operation heights based upon rotation radii
opHeights = self.opDetermineRotationRadii(obj) # return is [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfst)]
(xRotRad, yRotRad, zRotRad) = opHeights[0]
(clrOfst, safOfset) = opHeights[1]
# self.leadIn = 0.0 #safOfset / 2.0
# Set clearnance and safe heights based upon rotation radii
obj.ClearanceHeight.Value = xRotRad + clrOfst
obj.SafeHeight.Value = xRotRad + safOfset
if yRotRad > xRotRad:
obj.ClearanceHeight.Value = yRotRad + clrOfst
obj.SafeHeight.Value = yRotRad + safOfset
# Set axial feed rates based upon horizontal feed rates
safeCircum = 2 * math.pi * obj.SafeHeight.Value
self.axialFeed = 360 / safeCircum * self.horizFeed
self.axialRapid = 360 / safeCircum * self.horizRapid
# Set start point
if PathOp.FeatureStartPoint & self.opFeatures(obj) and obj.UseStartPoint:
start = obj.StartPoint
else:
start = None
aOS = self.areaOpShapes(obj) # list of tuples (shape, isHole, sub, angle, axis, tag)
aOS = self.areaOpShapes(obj) # list of tuples (shape, isHole, sub, angle, axis)
# Adjust tuples length received from other PathWB tools/operations beside PathPocketShape
shapes = []
for shp in aOS:
if len(shp) == 2:
(fc, iH) = shp
# fc, iH, sub, angle, axis
tup = fc, iH, 'notPocket', 0.0, 'X'
# fc, iH, sub, angle, axis
tup = fc, iH, 'otherOp', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
else:
shapes.append(shp)
@@ -363,25 +427,39 @@ class ObjectOp(PathOp.ObjectOp):
shapes = [j['shape'] for j in jobs]
# PathLog.debug("Pre_path depths are Start: {}, and Final: {}".format(obj.StartDepth.Value, obj.FinalDepth.Value))
sims = []
for (shape, isHole, sub, angle, axis) in shapes:
startDep = obj.StartDepth.Value # + safOfset
safeDep = obj.SafeHeight.Value
clearDep = obj.ClearanceHeight.Value
finalDep = obj.FinalDepth.Value # finDep
numShapes = len(shapes)
if numShapes == 1:
nextAxis = shapes[0][4]
elif numShapes > 1:
nextAxis = shapes[1][4]
else:
nextAxis = 'X'
for ns in range(0, numShapes):
(shape, isHole, sub, angle, axis, strDep, finDep) = shapes[ns]
if ns < numShapes - 1:
nextAxis = shapes[ns + 1][4]
else:
nextAxis = 'L'
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
self.depthparams = PathUtils.depth_params(
clearance_height=clearDep, # obj.ClearanceHeight.Value
safe_height=safeDep, # obj.SafeHeight.Value
start_depth=startDep,
clearance_height=obj.ClearanceHeight.Value,
safe_height=obj.SafeHeight.Value,
start_depth=strDep, # obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=finish_step, # obj.FinalDepth.Value
final_depth=finalDep,
z_finish_step=finish_step,
final_depth=finDep, # obj.FinalDepth.Value,
user_depths=None)
try:
(pp, sim) = self._buildPathArea(obj, shape, isHole, start, getsim)
except Exception as e:
FreeCAD.Console.PrintError(e)
FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.")
else:
ppCmds = pp.Commands
if obj.EnableRotation != 'Off' and self.rotateFlag is True:
# Rotate model to index for cut
@@ -392,16 +470,18 @@ class ObjectOp(PathOp.ObjectOp):
if obj.B_AxisErrorOverride is True:
angle = -1 * angle
# Rotate Model to correct angle
ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialFeed}))
ppCmds.insert(0, Path.Command('G1', {axisOfRot: angle, 'F': self.axialFeed}))
ppCmds.insert(0, Path.Command('N100', {}))
# Raise cutter to safe depth and return index to starting position
ppCmds.append(Path.Command('G0', {'Z': safeDep, 'F': self.vertRapid}))
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialFeed}))
ppCmds.insert(0, Path.Command('N200', {}))
ppCmds.append(Path.Command('G1', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
# ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
if axis != nextAxis:
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
# Save gcode commands to object command list
self.commandlist.extend(ppCmds)
sims.append(sim)
except Exception as e:
FreeCAD.Console.PrintError(e)
FreeCAD.Console.PrintError("Something unexpected happened. Check project and tool config.")
if self.areaOpRetractTool(obj):
self.endVector = None
@@ -409,16 +489,12 @@ class ObjectOp(PathOp.ObjectOp):
# Raise cutter to safe height and rotate back to original orientation
if self.rotateFlag is True:
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialFeed}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialFeed}))
if len(self.cloneNames) > 0:
for cn in self.cloneNames:
if FreeCAD.ActiveDocument.getObject(cn):
FreeCAD.ActiveDocument.removeObject(cn)
PathLog.debug("Removed temp clone: " + cn)
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
PathLog.debug("obj.Name: " + str(obj.Name))
if obj.EnableRotation != 'Off':
self.useRotJobClones('Delete')
self.guiMessage('title', None, show=True)
return sims
@@ -453,30 +529,30 @@ class ObjectOp(PathOp.ObjectOp):
Determine rotational radii for 4th-axis rotations, for clearance/safe heights '''
parentJob = PathUtils.findParentJob(obj)
bb = parentJob.Stock.Shape.BoundBox
# bb = parentJob.Stock.Shape.BoundBox
xlim = 0.0
ylim = 0.0
zlim = 0.0
# Determine boundbox radius based upon xzy limits data
if math.fabs(bb.ZMin) > math.fabs(bb.ZMax):
zlim = bb.ZMin
if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):
zlim = self.stockBB.ZMin
else:
zlim = bb.ZMax
zlim = self.stockBB.ZMax
if obj.EnableRotation != 'B(y)':
# Rotation is around X-axis, cutter moves along same axis
if math.fabs(bb.YMin) > math.fabs(bb.YMax):
ylim = bb.YMin
if math.fabs(self.stockBB.YMin) > math.fabs(self.stockBB.YMax):
ylim = self.stockBB.YMin
else:
ylim = bb.YMax
ylim = self.stockBB.YMax
if obj.EnableRotation != 'A(x)':
# Rotation is around Y-axis, cutter moves along same axis
if math.fabs(bb.XMin) > math.fabs(bb.XMax):
xlim = bb.XMin
if math.fabs(self.stockBB.XMin) > math.fabs(self.stockBB.XMax):
xlim = self.stockBB.XMin
else:
xlim = bb.XMax
xlim = self.stockBB.XMax
xRotRad = math.sqrt(ylim**2 + zlim**2)
yRotRad = math.sqrt(xlim**2 + zlim**2)
@@ -487,21 +563,20 @@ class ObjectOp(PathOp.ObjectOp):
return [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfst)]
def pocketRotationAnalysis(self, obj, face, prnt):
# def pocketRotationAnalysis(self, obj, objRef, sub, prnt):
'''pocketRotationAnalysis(self, obj, objRef, sub, prnt)
def pocketRotationAnalysis(self, obj, norm, surf, prnt):
'''pocketRotationAnalysis(self, obj, norm, surf, prnt)
Determine X and Y independent rotation necessary to make normalAt = Z=1 (0,0,1) '''
PathLog.track()
praInfo = "pocketRotationAnalysis() in PathAreaOp.py"
rtn = False
axis = 'X'
orientation = 'X'
angle = 500.0
zTol = 1.0E-9
rndTol = 1.0 - zTol
praInfo = "pocketRotationAnalysis() in PathAreaOp.py"
def roundRoughValues(val, zTol, rndTol):
def roundRoughValues(val):
zTol = 1.0E-9
rndTol = 1.0 - zTol
# Convert VALxe-15 numbers to zero
if math.fabs(val) <= zTol:
return 0.0
@@ -511,18 +586,14 @@ class ObjectOp(PathOp.ObjectOp):
else:
return val
# face = objRef.Shape.getElement(sub)
norm = face.normalAt(0, 0)
nX = roundRoughValues(norm.x, zTol, rndTol)
nY = roundRoughValues(norm.y, zTol, rndTol)
nZ = roundRoughValues(norm.z, zTol, rndTol)
nX = roundRoughValues(norm.x)
nY = roundRoughValues(norm.y)
nZ = roundRoughValues(norm.z)
praInfo += "\n -normalAt(0,0): " + str(nX) + ", " + str(nY) + ", " + str(nZ)
surf = face.Surface.Axis
saX = roundRoughValues(surf.x, zTol, rndTol)
saY = roundRoughValues(surf.y, zTol, rndTol)
saZ = roundRoughValues(surf.z, zTol, rndTol)
saX = roundRoughValues(surf.x)
saY = roundRoughValues(surf.y)
saZ = roundRoughValues(surf.z)
praInfo += "\n -Surface.Axis: " + str(saX) + ", " + str(saY) + ", " + str(saZ)
# Determine rotation needed and current orientation
@@ -601,13 +672,12 @@ class ObjectOp(PathOp.ObjectOp):
angle = angle + 180.0
else:
angle = angle - 180.0
praInfo += "\n -Suggested rotation: angle: " + str(angle) + ", axis: " + str(axis)
else:
praInfo += "\n - ... NO rotation triggered"
praInfo += "\n -Suggested rotation: angle: " + str(angle) + ", axis: " + str(axis)
if prnt is True:
# PathLog.info("praInfo: " + str(praInfo))
PathLog.debug("praInfo: " + str(praInfo))
PathLog.info("praInfo: " + str(praInfo))
return (rtn, angle, axis, praInfo)
def guiMessage(self, title, msg, show=False):
@@ -624,5 +694,227 @@ class ObjectOp(PathOp.ObjectOp):
# QtGui.QMessageBox.warning(None, title, msg)
self.guiMsgs = [] # Reset messages
return True
# Types: information, warning, critical, question
if False:
reply = QtGui.QMessageBox.question(None,"",message,
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
yes = 'yes'
if reply == QtGui.QMessageBox.No:
no = 'no'
if False:
msgBox = QtGui.QMessageBox()
msgBox.setText(translate("Arch","This mesh has more than 1000 facets."))
msgBox.setInformativeText(translate("Arch","This operation can take a long time. Proceed?"))
msgBox.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
msgBox.setDefaultButton(QtGui.QMessageBox.Cancel)
ret = msgBox.exec_()
if ret == QtGui.QMessageBox.Cancel:
return
return False
def visualAxis(self):
if not FreeCAD.ActiveDocument.getObject('xAxCyl'):
xAx = 'xAxCyl'
yAx = 'yAxCyl'
zAx = 'zAxCyl'
FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","visualAxis")
FreeCADGui.ActiveDocument.getObject('visualAxis').Visibility = False
FreeCAD.ActiveDocument.addObject("Part::Cylinder", xAx)
cyl = FreeCAD.ActiveDocument.getObject(xAx)
cyl.Label = xAx
cyl.Radius = self.xRotRad
cyl.Height = 0.01
cyl.Placement = FreeCAD.Placement(FreeCAD.Vector(0,0,0),FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
cyl.purgeTouched()
FreeCAD.ActiveDocument.getObject("visualAxis").addObject(cyl)
cylGui = FreeCADGui.ActiveDocument.getObject(xAx)
cylGui.ShapeColor = (0.667,0.000,0.000)
cylGui.Transparency = 80
cylGui.Visibility = False
FreeCAD.ActiveDocument.addObject("Part::Cylinder", yAx)
cyl = FreeCAD.ActiveDocument.getObject(yAx)
cyl.Label = yAx
cyl.Radius = self.yRotRad
cyl.Height = 0.01
cyl.Placement = FreeCAD.Placement(FreeCAD.Vector(0,0,0),FreeCAD.Rotation(FreeCAD.Vector(1,0,0),90))
FreeCAD.ActiveDocument.getObject("visualAxis").addObject(cyl)
cyl.purgeTouched()
cylGui = FreeCADGui.ActiveDocument.getObject(yAx)
cylGui.ShapeColor = (0.000,0.667,0.000)
cylGui.Transparency = 80
cylGui.Visibility = False
if False:
FreeCAD.ActiveDocument.addObject("Part::Cylinder", zAx)
cyl = FreeCAD.ActiveDocument.getObject(zAx)
cyl.Label = zAx
cyl.Radius = self.yRotRad
cyl.Height = 0.01
cyl.Placement = FreeCAD.Placement(FreeCAD.Vector(0,0,0),FreeCAD.Rotation(FreeCAD.Vector(1,0,0),90))
FreeCAD.ActiveDocument.getObject("visualAxis").addObject(cyl)
cyl.purgeTouched()
cylGui = FreeCADGui.ActiveDocument.getObject(zAx)
cylGui.ShapeColor = (0.000,0.000,0.498)
cylGui.Transparency = 80
cylGui.Visibility = False
def useRotJobClones(self, cloneName):
if FreeCAD.ActiveDocument.getObject('rotJobClones'):
if cloneName == 'Delete':
if PathLog.getLevel() != 2:
for cln in FreeCAD.ActiveDocument.getObject('rotJobClones').Group:
FreeCAD.ActiveDocument.removeObject(cln.Name)
FreeCAD.ActiveDocument.removeObject('rotJobClones')
return
if cloneName == 'Start':
for cln in FreeCAD.ActiveDocument.getObject('rotJobClones').Group:
FreeCAD.ActiveDocument.removeObject(cln.Name)
FreeCAD.ActiveDocument.removeObject('rotJobClones')
return
else:
FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","rotJobClones")
FreeCADGui.ActiveDocument.getObject('rotJobClones').Visibility = False
if cloneName != 'Start' and cloneName != 'Delete':
FreeCAD.ActiveDocument.getObject('rotJobClones').addObject(FreeCAD.ActiveDocument.getObject(cloneName))
FreeCADGui.ActiveDocument.getObject(cloneName).Visibility = False
def cloneBaseAndStock(self, obj, base, angle, axis, subCount):
# Create a temporary clone and stock 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 + '_' + axis + '_' + str(math.fabs(rndAng)).replace('.', '_')
else:
tag = axis + str(rndAng).replace('.', '_')
clnNm = obj.Name + '_base_' + '_' + str(subCount) + '_' + tag
stckClnNm = obj.Name + '_stock_' + '_' + str(subCount) + '_' + tag
if clnNm not in self.cloneNames:
self.cloneNames.append(clnNm)
self.cloneNames.append(stckClnNm)
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
FreeCAD.ActiveDocument.addObject('Part::Feature', stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
FreeCADGui.ActiveDocument.getObject(stckClnNm).Transparency=90
FreeCADGui.ActiveDocument.getObject(clnNm).ShapeColor = (1.000,0.667,0.000)
self.useRotJobClones(clnNm)
self.useRotJobClones(stckClnNm)
clnBase = FreeCAD.ActiveDocument.getObject(clnNm)
clnStock = FreeCAD.ActiveDocument.getObject(stckClnNm)
tag = base.Name + '_' + tag
return (clnBase, clnStock, tag)
def getFaceNormAndSurf(self, face):
'''getFaceNormAndSurf(self, face)
Return face.normalAt(0,0) or face.normal(0,0) and face.Surface.Axis vectors
'''
norm = FreeCAD.Vector(0.0, 0.0, 0.0)
surf = FreeCAD.Vector(0.0, 0.0, 0.0)
if hasattr(face, 'normalAt'):
n = face.normalAt(0, 0)
elif hasattr(face, 'normal'):
n = face.normal(0, 0)
if hasattr(face.Surface, 'Axis'):
s = face.Surface.Axis
else:
s = n
norm.x = n.x
norm.y = n.y
norm.z = n.z
surf.x = s.x
surf.y = s.y
surf.z = s.z
return (norm, surf)
def applyRotationalAnalysis(self, obj, base, angle, axis, subCount):
'''applyRotationalAnalysis(self, obj, base, angle, axis, subCount)
Create temp clone and stock and apply rotation to both.
Return new rotated clones
'''
if axis == 'X':
vect = FreeCAD.Vector(1, 0, 0)
elif axis == 'Y':
vect = FreeCAD.Vector(0, 1, 0)
if obj.InverseAngle is True:
angle = -1 * angle
# Create a temporary clone of model for rotational use.
(clnBase, clnStock, tag) = self.cloneBaseAndStock(obj, base, angle, axis, subCount)
# Rotate base to such that Surface.Axis of pocket bottom is Z=1
clnBase = Draft.rotate(clnBase, angle, center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False)
clnStock = Draft.rotate(clnStock, angle, center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False)
clnBase.purgeTouched()
clnStock.purgeTouched()
return (clnBase, angle, clnStock, tag)
def applyInverseAngle(self, obj, clnBase, clnStock, axis, angle):
if axis == 'X':
vect = FreeCAD.Vector(1, 0, 0)
elif axis == 'Y':
vect = FreeCAD.Vector(0, 1, 0)
# Rotate base to inverse of original angle
clnBase = Draft.rotate(clnBase, (-2 * angle), center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False)
clnStock = Draft.rotate(clnStock, (-2 * angle), center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False)
clnBase.purgeTouched()
clnStock.purgeTouched()
if obj.InverseAngle is False:
obj.InverseAngle = True
else:
obj.InverseAngle = False
angle = -1 * angle
PathLog.debug(" --Rotated to InverseAngle.")
return (clnBase, clnStock, angle)
def calculateStartFinalDepths(self, obj, shape, stock):
'''calculateStartFinalDepths(self, obj, shape, stock)
Calculate correct start and final depths for the face
'''
finDep = max(obj.FinalDepth.Value, shape.BoundBox.ZMin)
stockTop = stock.Shape.BoundBox.ZMax
if obj.EnableRotation == 'Off':
strDep = obj.StartDepth.Value
if strDep <= finDep:
strDep = stockTop
else:
strDep = min(obj.StartDepth.Value, stockTop)
if strDep <= finDep:
strDep = stockTop # self.strDep
msg = "Start depth <= face depth.\nIncreased to stock top."
# msg = translate('Path', msg + "\nFace depth is {} mm.".format(face.BoundBox.ZMax)
msg = translate('Path', msg)
PathLog.error(msg)
return (strDep, finDep)
def sortTuplesByIndex(self, TupleList, tagIdx):
'''sortTuplesByIndex(self, TupleList, tagIdx)
sort list of tuples based on tag index provided
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)
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)

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.'''

View File

@@ -33,7 +33,7 @@ import PathScripts.PathProfileBase as PathProfileBase
import PathScripts.PathUtils as PathUtils
import numpy
from PathScripts.PathUtils import depth_params
# from PathScripts.PathUtils import depth_params
from PySide import QtCore
if False:
@@ -51,8 +51,8 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Path Profile operation based on faces."
__created__ = "2014"
__scriptVersion__ = "1c testing"
__lastModified__ = "2019-06-03 03:53 CST"
__scriptVersion__ = "2e testing"
__lastModified__ = "2019-06-11 14:30 CST"
class ObjectProfile(PathProfileBase.ObjectProfile):
@@ -66,6 +66,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
def areaOpFeatures(self, obj):
'''baseObject() ... returns super of receiver
Used to call base implementation in overwritten functions.'''
# return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels | PathOp.FeatureRotation
return PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels
def initAreaOp(self, obj):
@@ -74,19 +75,22 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
obj.addProperty("App::PropertyBool", "processHoles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline"))
obj.addProperty("App::PropertyBool", "processPerimeter", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline"))
obj.addProperty("App::PropertyBool", "processCircles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes"))
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('PathPocketShape', 'Inverse the angle. Example: -22.5 -> 22.5 degrees.'))
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('PathPocketShape', 'Match B rotations to model (error in FreeCAD rendering).'))
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.'))
self.baseObject().initAreaOp(obj)
def areaOpShapes(self, obj):
'''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.'''
import math
import Draft
PathLog.track()
PathLog.debug("----- areaOpShapes() in PathProfileFaces.py")
if obj.UseComp:
self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")"))
@@ -97,152 +101,163 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
self.profileshape = []
baseSubsTuples = []
self.cloneNames = []
subCount = 0
allTuples = []
def judgeFinalDepth(obj, fD):
if obj.FinalDepth.Value >= fD:
return obj.FinalDepth.Value
else:
return fD
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:
#(shape, sub, angle, axis, tag, strDep, finDep) = tup
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)
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)
if obj.Base: # The user has selected subobjects from the base. Process each.
if obj.EnableRotation != 'Off':
for p in range(0, len(obj.Base)):
(base, subsList) = obj.Base[p]
for sub in subsList:
subCount += 1
shape = getattr(base.Shape, sub)
if isinstance(shape, Part.Face):
rtn = False
(rtn, angle, axis, praInfo) = self.pocketRotationAnalysis(obj, shape, prnt=True)
(norm, surf) = self.getFaceNormAndSurf(shape)
(rtn, angle, axis, praInfo)= self.pocketRotationAnalysis(obj, norm, surf, prnt=True)
PathLog.info("praInfo: \n" + str(praInfo))
if rtn is True:
PathLog.debug(str(sub) + ": rotating model to make face normal at (0,0,1) ...")
if obj.InverseAngle is True:
angle = -1 * angle
# 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 = base.Name + '__' + axis + '_' + str(math.fabs(rndAng)).replace('.', '_')
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount)
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = getattr(clnBase.Shape, 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:
msg = "Consider toggling the 'InverseAngle' property and recomputing."
PathLog.error(msg)
title = 'Rotation Warning'
self.guiMessage(title, msg, False)
else:
tag = base.Name + '__' + axis + str(rndAng).replace('.', '_')
clnNm = base.Name + '_' + tag
if clnNm not in self.cloneNames:
self.cloneNames.append(clnNm)
PathLog.info("tmp clone created: " + str(clnNm))
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
newBase = FreeCAD.ActiveDocument.getObject(clnNm)
PathLog.debug(" --Face appears to be oriented correctly.")
if axis == 'X':
vect = FreeCAD.Vector(1, 0, 0)
elif axis == 'Y':
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)
# shape = getattr(base.Shape, sub)
tup = clnBase, sub, tag, angle, axis, clnStock
else:
PathLog.debug(str(sub) + ": no rotation used")
axis = 'X'
angle = 0.0
tag = base.Name + '__' + axis + str(angle).replace('.', '_')
tag = base.Name + '_' + axis + str(angle).replace('.', '_')
stock = PathUtils.findParentJob(obj).Stock
tup = base, sub, tag, angle, axis, stock
# Eif
tup = base, sub, tag, angle, axis
allTuples.append(tup)
# Eif
subCount += 1
# Efor
# Efor
(hTags, hGrps) = sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
if subCount > 1:
msg = "Multiple faces in Base Geometry."
msg += " Depth settings will be applied to all faces."
msg = translate("Path", msg)
PathLog.error(msg)
#title = translate("Path", "Depth Warning")
#self.guiMessage(title, msg)
(Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
for o in range(0, len(hTags)):
PathLog.debug('hTag: {}'.format(hTags[o]))
for o in range(0, len(Tags)):
subList = []
for (base, sub, tag, angle, axis) in hGrps[o]:
for (base, sub, tag, angle, axis, stock) in Grps[o]:
subList.append(sub)
pair = base, subList, angle, axis
pair = base, subList, angle, axis, stock
baseSubsTuples.append(pair)
# Efor
else:
PathLog.info("Use Rotation feature(property) is 'Off'.")
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
baseSubsTuples.append((base, subList, 'pathProfileFaces', 0.0, 'X'))
baseSubsTuples.append((base, subList, 0.0, 'X', stock))
# for base in obj.Base:
for base in baseSubsTuples:
for (base, subsList, angle, axis, stock) in baseSubsTuples:
holes = []
faces = []
angle = base[2]
axis = base[3]
for sub in base[1]:
shape = getattr(base[0].Shape, sub)
for sub in subsList:
shape = getattr(base.Shape, sub)
if isinstance(shape, Part.Face):
faces.append(shape)
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
for wire in shape.Wires[1:]:
holes.append((base[0].Shape, wire))
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
finDep = judgeFinalDepth(obj, shape.BoundBox.ZMin)
self.depthparams = PathUtils.depth_params(
clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value,
start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value,
z_finish_step=finish_step, final_depth=finDep,
user_depths=None)
holes.append((base.Shape, wire))
else:
FreeCAD.Console.PrintWarning("found a base object which is not a face. Can't continue.")
return
ignoreSub = base.Name + '.' + sub
msg = translate('Path', "Found a selected object which is not a face. Ignoring: {}".format(ignoreSub))
PathLog.error(msg)
FreeCAD.Console.PrintWarning(msg)
# return
for shape, wire in holes:
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
drillable = PathUtils.isDrillable(shape, wire)
if (drillable and obj.processCircles) or (not drillable and obj.processHoles):
# Recalculate depthparams
(strDep, finDep) = self.calculateStartFinalDepths(obj, shape, stock)
PathLog.debug("Adjusted face depths strDep: {}, and finDep: {}".format(self.strDep, self.finDep))
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
self.depthparams = PathUtils.depth_params(
clearance_height=obj.ClearanceHeight.Value,
safe_height=obj.SafeHeight.Value,
start_depth=strDep, #obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=finish_step,
final_depth=finDep, #obj.FinalDepth.Value,
user_depths=None)
env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams)
PathLog.track()
# shapes.append((env, True))
shapes.append((env, True, 'pathProfileFaces', base[2], base[3]))
tup = env, True, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
if len(faces) > 0:
profileshape = Part.makeCompound(faces)
self.profileshape.append(profileshape)
if obj.processPerimeter:
env = PathUtils.getEnvelope(base[0].Shape, subshape=profileshape, depthparams=self.depthparams)
PathLog.track()
# shapes.append((env, False))
shapes.append((env, False, 'pathProfileFaces', base[2], base[3]))
if profileshape:
# Recalculate depthparams
(strDep, finDep) = self.calculateStartFinalDepths(obj, profileshape, stock)
PathLog.debug("Adjusted face depths strDep: {}, and finDep: {}".format(self.strDep, self.finDep))
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
self.depthparams = PathUtils.depth_params(
clearance_height=obj.ClearanceHeight.Value,
safe_height=obj.SafeHeight.Value,
start_depth=strDep, #obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=finish_step,
final_depth=finDep, #obj.FinalDepth.Value,
user_depths=None)
else:
strDep = obj.StartDepth.Value
finDep = obj.FinalDepth.Value
try:
env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=self.depthparams)
except Exception as ee:
PathLog.error(translate('Path', 'getEnvelope() failed to return an object.'))
else:
# shapes.append((env, False))
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
else:
for shape in faces:
# Recalculate depthparams
(strDep, finDep) = self.calculateStartFinalDepths(obj, shape, stock)
finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0
self.depthparams = PathUtils.depth_params(
clearance_height=obj.ClearanceHeight.Value,
safe_height=obj.SafeHeight.Value,
start_depth=strDep, #obj.StartDepth.Value,
step_down=obj.StepDown.Value,
z_finish_step=finish_step,
final_depth=finDep, #obj.FinalDepth.Value,
user_depths=None)
env = PathUtils.getEnvelope(base.Shape, subshape=shape, depthparams=self.depthparams)
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
# Eif
# Efor
else: # Try to build targets from the job base
if 1 == len(self.model) and hasattr(self.model[0], "Proxy"):
if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet
@@ -253,19 +268,22 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
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)
shapes.append((env, True))
# shapes.append((env, True))
tup = env, True, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
if obj.processPerimeter:
for shape in self.model[0].Proxy.getOutlines(self.model[0], transform=True):
for wire in shape.Wires:
f = Part.makeFace(wire, 'Part::FaceMakerSimple')
env = PathUtils.getEnvelope(self.model[0].Shape, subshape=f, depthparams=self.depthparams)
shapes.append((env, False))
# shapes.append((env, False))
tup = env, False, 'pathProfileFaces', 0.0, 'X', obj.StartDepth.Value, obj.FinalDepth.Value
shapes.append(tup)
self.removalshapes = shapes
PathLog.debug("%d shapes" % len(shapes))
# self.cloneNames = [] # Comment out to leave temp clones visible
return shapes
def areaOpSetDefaultValues(self, obj, job):
@@ -276,15 +294,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
obj.processCircles = False
obj.processPerimeter = True
obj.ReverseDirection = False
obj.InverseAngle = True
obj.InverseAngle = False
obj.AttemptInverseAngle = False
obj.B_AxisErrorOverride = False
def SetupProperties():
setup = []
setup = PathProfileBase.SetupProperties()
setup.append("processHoles")
setup.append("processPerimeter")
setup.append("processCircles")
return PathProfileBase.SetupProperties() + setup
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 Profile based on faces operation.'''