4th-axis update (#2311)

Improve property creation
Improve property setup
Implement PathLog.debug() for troubleshooting.
Improve default property values.
Remove unused and incomplete method
Remove unnecessary comments; fix final depth issue
4th-axis improvements
rotation method improvements
remove extra comment blocks
fix incorrect variable references
fix -0.0 re-introduction after initial filter
negative zero re-introduced causes problems with naming method for temp clones
Update faceRotationAnalaysis() method
update opFeatures()
Commented out call to PathOp.FeatureRotation
This feature not yet implemented.
delete call to removed method - self.reportThis()
Remove unnecessary comments
This commit is contained in:
Russell Johnson
2019-07-01 09:30:58 -05:00
committed by sliptonic
parent e9f3551f8f
commit 1ac8f4cfe9
2 changed files with 70 additions and 114 deletions

View File

@@ -21,16 +21,6 @@
# * USA *
# * *
# ***************************************************************************
# * *
# * Additional modifications and contributions beginning 2019 *
# * Focus: 4th-axis integration *
# * by Russell Johnson <russ4262@gmail.com> *
# * *
# ***************************************************************************
# SCRIPT NOTES:
# - FUTURE: Relocate rotational calculations to Job setup tool, creating a Machine section
# with axis & rotation toggles and associated min/max values
import FreeCAD
import Path
@@ -51,10 +41,10 @@ __title__ = "Base class for PathArea based operations."
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Base class and properties for Path.Area based operations."
__contributors__ = "mlampert [FreeCAD], russ4262 (Russell Johnson)"
__contributors__ = "russ4262 (Russell Johnson)"
__createdDate__ = "2017"
__scriptVersion__ = "2g testing"
__lastModified__ = "2019-06-13 15:37 CST"
__scriptVersion__ = "2h testing"
__lastModified__ = "2019-06-30 17:17 CST"
LOGLEVEL = False
@@ -64,9 +54,8 @@ if LOGLEVEL:
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -113,8 +102,6 @@ class ObjectOp(PathOp.ObjectOp):
# obj.Proxy = self
self.setupAdditionalProperties(obj)
self.initAreaOp(obj)
def setupAdditionalProperties(self, obj):
@@ -176,8 +163,6 @@ class ObjectOp(PathOp.ObjectOp):
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.areaOpOnDocumentRestored(obj)
@@ -225,7 +210,7 @@ class ObjectOp(PathOp.ObjectOp):
# 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]
# (clrOfset, safOfst) = opHeights[1]
PathLog.debug("opHeights[0]: " + str(opHeights[0]))
PathLog.debug("opHeights[1]: " + str(opHeights[1]))
@@ -334,9 +319,6 @@ class ObjectOp(PathOp.ObjectOp):
areaOpUseProjection(obj) ... return true if operation can use projection
instead.'''
PathLog.track()
# 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
@@ -365,7 +347,7 @@ class ObjectOp(PathOp.ObjectOp):
# 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]
(self.clrOfset, self.safOfst) = opHeights[1]
# Set clearnance and safe heights based upon rotation radii
if obj.EnableRotation == 'A(x)':
@@ -376,7 +358,7 @@ class ObjectOp(PathOp.ObjectOp):
self.strDep = max(self.xRotRad, self.yRotRad)
self.finDep = -1 * self.strDep
obj.ClearanceHeight.Value = self.strDep + self.safOfset
obj.ClearanceHeight.Value = self.strDep + self.clrOfset
obj.SafeHeight.Value = self.strDep + self.safOfst
if self.initWithRotation is False:
@@ -513,7 +495,7 @@ class ObjectOp(PathOp.ObjectOp):
self.useTempJobClones('Delete') # Delete temp job clone group and contents
self.guiMessage('title', None, show=True) # Process GUI messages to user
PathLog.debug("obj.Name: " + str(obj.Name))
PathLog.debug("obj.Name: " + str(obj.Name) + "\n\n")
return sims
def areaOpRetractTool(self, obj):
@@ -542,6 +524,7 @@ class ObjectOp(PathOp.ObjectOp):
Can safely be overwritten by subclasses.'''
return False
# Rotation-related methods
def opDetermineRotationRadii(self, obj):
'''opDetermineRotationRadii(obj)
Determine rotational radii for 4th-axis rotations, for clearance/safe heights '''
@@ -577,7 +560,7 @@ class ObjectOp(PathOp.ObjectOp):
zRotRad = math.sqrt(xlim**2 + ylim**2)
clrOfst = parentJob.SetupSheet.ClearanceHeightOffset.Value
safOfst = parentJob.SetupSheet.ClearanceHeightOffset.Value
safOfst = parentJob.SetupSheet.SafeHeightOffset.Value
return [(xRotRad, yRotRad, zRotRad), (clrOfst, safOfst)]
@@ -586,7 +569,7 @@ class ObjectOp(PathOp.ObjectOp):
Determine X and Y independent rotation necessary to make normalAt = Z=1 (0,0,1) '''
PathLog.track()
praInfo = "faceRotationAnalysis() in PathAreaOp.py"
praInfo = "faceRotationAnalysis()"
rtn = True
axis = 'X'
orientation = 'X'
@@ -676,24 +659,39 @@ class ObjectOp(PathOp.ObjectOp):
angle = -1 * angle
# Enforce enabled rotation in settings
praInfo += "\n -Initial orientation: {}".format(orientation)
if orientation == 'Y':
axis = 'X'
if obj.EnableRotation == 'B(y)': # Required axis disabled
rtn = False
else:
if angle == 180.0 or angle == -180.0:
axis = 'Y'
else:
rtn = False
elif orientation == 'X':
axis = 'Y'
if obj.EnableRotation == 'A(x)': # Required axis disabled
rtn = False
if angle == 180.0 or angle == -180.0:
axis = 'X'
else:
rtn = False
if math.fabs(angle) == 0.0:
angle = 0.0
rtn = False
if angle == 500.0:
angle == 0.0
rtn = False
if angle == 0.0:
rtn = False
if rtn is False:
if orientation == 'Z' and angle == 0.0 and obj.ReverseDirection is True:
if obj.EnableRotation == 'B(y)':
axis = 'Y'
rtn = True
if rtn is True:
self.rotateFlag = True
rtn = True
# rtn = True
if obj.ReverseDirection is True:
if angle < 180.0:
angle = angle + 180.0
@@ -717,18 +715,20 @@ class ObjectOp(PathOp.ObjectOp):
if msg is not None:
self.guiMsgs.append((title, msg))
if show is True:
if FreeCAD.GuiUp and len(self.guiMsgs) > 0:
# self.guiMsgs.pop(0) # remove formatted place holder.
from PySide.QtGui import QMessageBox
# from PySide import QtGui
for entry in self.guiMsgs:
(title, msg) = entry
QMessageBox.warning(None, title, msg)
# QtGui.QMessageBox.warning(None, title, msg)
self.guiMsgs = [] # Reset messages
return True
# Types: information, warning, critical, question
if len(self.guiMsgs) > 0:
if FreeCAD.GuiUp:
from PySide.QtGui import QMessageBox
for entry in self.guiMsgs:
(title, msg) = entry
QMessageBox.warning(None, title, msg)
self.guiMsgs = [] # Reset messages
return True
else:
for entry in self.guiMsgs:
(title, msg) = entry
PathLog.warning("{}:: {}".format(title, msg))
self.guiMsgs = [] # Reset messages
return True
return False
def visualAxis(self):
@@ -773,21 +773,6 @@ class ObjectOp(PathOp.ObjectOp):
cylGui.Visibility = False
vaGrp.addObject(cyl)
# 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))
# cyl.purgeTouched()
# if FreeCAD.GuiUp:
# cylGui = FreeCADGui.ActiveDocument.getObject(zAx)
# cylGui.ShapeColor = (0.000, 0.000, 0.498)
# cylGui.Transparency = 85
# cylGui.Visibility = False
# vaGrp.addObject(cyl)
def useTempJobClones(self, cloneName):
'''useTempJobClones(cloneName)
Manage use of temporary model clones for rotational operation calculations.
@@ -828,16 +813,18 @@ class ObjectOp(PathOp.ObjectOp):
self.cloneNames.append(clnNm)
self.cloneNames.append(stckClnNm)
if FreeCAD.ActiveDocument.getObject(clnNm):
FreeCAD.ActiveDocument.removeObject(clnNm)
FreeCAD.ActiveDocument.getObject(clnNm).Shape = base.Shape
else:
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
self.useTempJobClones(clnNm)
if FreeCAD.ActiveDocument.getObject(stckClnNm):
FreeCAD.ActiveDocument.removeObject(stckClnNm)
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
FreeCAD.ActiveDocument.addObject('Part::Feature', stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
FreeCAD.ActiveDocument.getObject(stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
else:
FreeCAD.ActiveDocument.addObject('Part::Feature', stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
self.useTempJobClones(stckClnNm)
if FreeCAD.GuiUp:
FreeCADGui.ActiveDocument.getObject(stckClnNm).Transparency = 90
FreeCADGui.ActiveDocument.getObject(clnNm).ShapeColor = (1.000, 0.667, 0.000)
self.useTempJobClones(clnNm)
self.useTempJobClones(stckClnNm)
clnBase = FreeCAD.ActiveDocument.getObject(clnNm)
clnStock = FreeCAD.ActiveDocument.getObject(stckClnNm)
tag = base.Name + '_' + tag
@@ -878,6 +865,8 @@ class ObjectOp(PathOp.ObjectOp):
if obj.InverseAngle is True:
angle = -1 * angle
if math.fabs(angle) == 0.0:
angle = 0.0
# Create a temporary clone of model for rotational use.
(clnBase, clnStock, tag) = self.cloneBaseAndStock(obj, base, angle, axis, subCount)
@@ -952,14 +941,18 @@ class ObjectOp(PathOp.ObjectOp):
GroupList.pop(0)
return (TagList, GroupList)
def warnDisabledAxis(self, obj, axis):
def warnDisabledAxis(self, obj, axis, sub=''):
'''warnDisabledAxis(self, obj, axis)
Provide user feedback if required axis is disabled'''
if axis == 'X' and obj.EnableRotation == 'B(y)':
PathLog.warning(translate('Path', "Part feature is inaccessible. Selected feature(s) require 'A(x)' for access."))
msg = translate('Path', "{}:: {} is inaccessible.".format(obj.Name, sub)) + " "
msg += translate('Path', "Selected feature(s) require 'Enable Rotation: A(x)' for access.")
PathLog.warning(msg)
return True
elif axis == 'Y' and obj.EnableRotation == 'A(x)':
PathLog.warning(translate('Path', "Part feature is inaccessible. Selected feature(s) require 'B(y)' for access."))
msg = translate('Path', "{}:: {} is inaccessible.".format(obj.Name, sub)) + " "
msg += translate('Path', "Selected feature(s) require 'Enable Rotation: B(y)' for access.")
PathLog.warning(msg)
return True
else:
return False

View File

@@ -21,15 +21,6 @@
# * USA *
# * *
# ***************************************************************************
# * *
# * Additional modifications and contributions beginning 2019 *
# * Focus: 4th-axis integration *
# * by Russell Johnson <russ4262@gmail.com> *
# * *
# ***************************************************************************
# SCRIPT NOTES:
# - Need test models for testing vertical faces scenarios.
import FreeCAD
import Part
@@ -49,8 +40,8 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of shape based Pocket operation."
__contributors__ = "russ4262 (Russell Johnson)"
__created__ = "2017"
__scriptVersion__ = "2g testing"
__lastModified__ = "2019-06-12 23:29 CST"
__scriptVersion__ = "2h testing"
__lastModified__ = "2019-06-30 17:19 CST"
LOGLEVEL = False
@@ -60,6 +51,7 @@ if LOGLEVEL:
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -371,7 +363,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if obj.Base:
PathLog.debug('Processing... obj.Base')
self.removalshapes = []
# ----------------------------------------------------------------------
if obj.EnableRotation == 'Off':
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
@@ -429,12 +420,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
for sub in subsList:
subCount += 1
if 'Face' in sub:
rtn = False
PathLog.debug(translate('Path', "Base Geometry sub: {}".format(sub)))
rtn = False
face = base.Shape.getElement(sub)
# --------------------------------------------------------
if type(face.Surface) == Part.SurfaceOfExtrusion:
# extrusion wall
PathLog.debug('analyzing type() == Part.SurfaceOfExtrusion')
@@ -447,7 +435,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
PathLog.debug(' -successful face created: {}'.format(useFace))
else:
PathLog.error(translate("Path", "Failed to create a planar face from edges in {}.".format(sub)))
# --------------------------------------------------------
(norm, surf) = self.getFaceNormAndSurf(face)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf)
@@ -482,24 +469,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
else:
ignoreSub = base.Name + '.' + sub
PathLog.error(translate('Path', "Selected feature 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 baseSubsTuples:
self.horiz = []
@@ -581,9 +550,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
# Adjust obj.FinalDepth.Value as needed.
if len(finalDepths) > 0:
finalDepths = min(finalDepths)
finalDep = min(finalDepths)
if subCount == 1:
obj.FinalDepth.Value = finDep
obj.FinalDepth.Value = finalDep
else:
# process the job base object as a whole
PathLog.debug(translate("Path", 'Processing model as a whole ...'))
@@ -598,7 +567,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1))
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', strDep, finDep))
for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes:
@@ -607,11 +575,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if self.removalshapes:
obj.removalshape = self.removalshapes[0][0]
# if PathLog.getLevel(PathLog.thisModule()) != 4:
# if self.delTempNameList > 0:
# for tmpNm in self.tempNameList:
# FreeCAD.ActiveDocument.removeObject(tmpNm)
return self.removalshapes
def areaOpSetDefaultValues(self, obj, job):
@@ -815,7 +778,7 @@ def SetupProperties():
return setup
def Create(name, obj=None):
def Create(name, obj = None):
'''Create(name) ... Creates and returns a Pocket operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject('Path::FeaturePython', name)