Improve code structure; correct 4th-axis depth issues
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.'''
|
||||
|
||||
@@ -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.'''
|
||||
|
||||
Reference in New Issue
Block a user