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:
committed by
sliptonic
parent
e9f3551f8f
commit
1ac8f4cfe9
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user