Merge pull request #3231 from Russ4262/4th_axis_fixes

[Path] 4th-axis rotational alignment improvements;  Code clean-up
This commit is contained in:
sliptonic
2020-03-31 08:17:18 -05:00
committed by GitHub
4 changed files with 124 additions and 64 deletions

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
@@ -49,6 +48,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule())
if LOGLEVEL is PathLog.Level.DEBUG:
PathLog.trackModule()
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -66,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp):
'''opFeatures(obj) ... returns the base features supported by all Path.Area based operations.
The standard feature list is OR'ed with the return value of areaOpFeatures().
Do not overwrite, implement areaOpFeatures(obj) instead.'''
# return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant
def areaOpFeatures(self, obj):
@@ -304,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp):
pathParams['return_end'] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
#if not self.areaOpRetractTool(obj):
# pathParams['threshold'] = 2.001 * self.radius
if self.endVector is None:
V = hWire.Wires[0].Vertexes
@@ -374,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp):
obj.ClearanceHeight.Value = strDep + self.clrOfset
obj.SafeHeight.Value = strDep + self.safOfst
#if self.initWithRotation is False:
# if obj.FinalDepth.Value == obj.OpFinalDepth.Value:
# obj.FinalDepth.Value = finDep
# if obj.StartDepth.Value == obj.OpStartDepth.Value:
# obj.StartDepth.Value = strDep
# Create visual axes when debugging.
if PathLog.getLevel(PathLog.thisModule()) == 4:
self.visualAxis()
@@ -467,10 +458,14 @@ class ObjectOp(PathOp.ObjectOp):
# Rotate Model to correct angle
ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid}))
# Raise cutter to safe depth and return index to starting position
ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
if axis != nextAxis:
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
# Raise cutter to safe height
ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
# Return index to starting position if axis of rotation changes.
if numShapes > 1:
if ns != numShapes - 1:
if axis != nextAxis:
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
# Eif
# Save gcode commands to object command list
@@ -483,9 +478,43 @@ class ObjectOp(PathOp.ObjectOp):
# Raise cutter to safe height and rotate back to original orientation
if self.rotateFlag is True:
resetAxis = False
lastJobOp = None
nextJobOp = None
opIdx = 0
JOB = PathUtils.findParentJob(obj)
jobOps = JOB.Operations.Group
numJobOps = len(jobOps)
for joi in range(0, numJobOps):
jo = jobOps[joi]
if jo.Name == obj.Name:
opIdx = joi
lastOpIdx = opIdx - 1
nextOpIdx = opIdx + 1
if lastOpIdx > -1:
lastJobOp = jobOps[lastOpIdx]
if nextOpIdx < numJobOps:
nextJobOp = jobOps[nextOpIdx]
if lastJobOp is not None:
if hasattr(lastJobOp, 'EnableRotation'):
PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation))
if lastJobOp.EnableRotation != obj.EnableRotation:
resetAxis = True
if ns == numShapes - 1: # If last shape, check next op EnableRotation setting
if nextJobOp is not None:
if hasattr(nextJobOp, 'EnableRotation'):
PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation))
if nextJobOp.EnableRotation != obj.EnableRotation:
resetAxis = True
# Raise to safe height if rotation activated
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
# reset rotational axises if necessary
if resetAxis is True:
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
self.useTempJobClones('Delete') # Delete temp job clone group and contents
self.guiMessage('title', None, show=True) # Process GUI messages to user
@@ -531,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp):
Determine rotational radii for 4th-axis rotations, for clearance/safe heights '''
parentJob = PathUtils.findParentJob(obj)
# 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(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):

View File

@@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
'''areaOpShapes(obj) ... return top face'''
# Facing is done either against base objects
holeShape = None
if obj.Base:
PathLog.debug("obj.Base: {}".format(obj.Base))
faces = []
@@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket):
# Find the correct shape depending on Boundary shape.
PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape))
bb = planeshape.BoundBox
# Apply offset for clearing edges
offset = 0;
if obj.ClearEdges == True:
offset = self.radius + 0.1
bb.XMin = bb.XMin - offset
bb.YMin = bb.YMin - offset
bb.XMax = bb.XMax + offset
bb.YMax = bb.YMax + offset
if obj.BoundaryShape == 'Boundbox':
bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1))
env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams)
@@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
elif obj.BoundaryShape == 'Stock':
stock = PathUtils.findParentJob(obj).Stock.Shape
env = stock
if obj.ExcludeRaisedAreas is True and oneBase[1] is True:
includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
if len(includedFaces) > 0:
@@ -269,7 +269,6 @@ def SetupProperties():
setup.append("BoundaryShape")
setup.append("ExcludeRaisedAreas")
setup.append("ClearEdges")
return setup
@@ -278,5 +277,4 @@ def Create(name, obj=None):
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectFace(obj, name)
return obj

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * Copyright (c) 2020 Schildkroet *
# * *
# * This program is free software; you can redistribute it and/or modify *
@@ -42,7 +41,7 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of shape based Pocket operation."
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
@@ -435,7 +434,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if obj.Base:
PathLog.debug('Processing... obj.Base')
self.removalshapes = [] # pylint: disable=attribute-defined-outside-init
# ----------------------------------------------------------------------
if obj.EnableRotation == 'Off':
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
@@ -450,11 +449,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
(isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)
if isLoop is True:
PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.")
PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.")
rtn = False
subCount += 1
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.info("angle: {}; axis: {}".format(angle, axis))
PathLog.debug("angle: {}; axis: {}".format(angle, axis))
if rtn is True:
faceNums = ""
@@ -471,15 +470,17 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
rtn = False
PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied."))
break
if rtn is False:
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
PathLog.debug("Applying the inverse angle.")
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if angle < -180.0:
if angle < 0.0:
angle += 360.0
tup = clnBase, subsList, angle, axis, clnStock
@@ -518,6 +519,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
(norm, surf) = self.getFaceNormAndSurf(face)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("initial {}".format(praInfo))
if rtn is True:
faceNum = sub.replace('Face', '')
@@ -525,20 +527,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = clnBase.Shape.getElement(sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
(rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("follow-up {}".format(praInfo2))
if abs(praAngle) == 180.0:
rtn = False
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp is False')
angle -= 180.0
if rtn is True:
PathLog.debug("Face not aligned after initial rotation.")
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
PathLog.debug("Applying the inverse angle.")
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp is False')
angle += 180.0
else:
PathLog.debug("Face appears to be oriented correctly.")
if angle < -180.0:
if angle < 0.0:
angle += 360.0
tup = clnBase, [sub], angle, axis, clnStock
@@ -650,8 +663,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if shpZMin > obj.FinalDepth.Value:
afD = shpZMin
if sD <= afD:
PathLog.error('Start Depth is lower than face depth.')
sD = afD + 1.0
msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ')
PathLog.warning(msg + ' {} mm.'.format(sD))
else:
face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin))

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * Copyright (c) 2020 Schildkroet *
# * *
# * This program is free software; you can redistribute it and/or modify *
@@ -43,6 +42,7 @@ __doc__ = "Path Profile operation based on faces."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -132,22 +132,42 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
rtn = False
(norm, surf) = self.getFaceNormAndSurf(shape)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo))
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 = getattr(clnBase.Shape, sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
(rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2))
if abs(praAngle) == 180.0:
rtn = False
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp 1 is False')
angle -= 180.0
if rtn is True:
PathLog.error(translate("Path", "Face appears misaligned after initial rotation."))
if obj.AttemptInverseAngle is True and obj.InverseAngle is False:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp 2 is False')
angle += 180.0
else:
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.error(msg)
PathLog.debug(' isFaceUp')
else:
PathLog.debug("Face appears to be oriented correctly.")
if angle < 0.0:
angle += 360.0
tup = clnBase, sub, tag, angle, axis, clnStock
else:
if self.warnDisabledAxis(obj, axis) is False:
@@ -157,21 +177,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
tag = base.Name + '_' + axis + str(angle).replace('.', '_')
stock = PathUtils.findParentJob(obj).Stock
tup = base, sub, tag, angle, axis, stock
allTuples.append(tup)
if subCount > 1:
msg = translate('Path', "Multiple faces in Base Geometry.") + " "
msg += translate('Path', "Depth settings will be applied to all faces.")
PathLog.warning(msg)
(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)
# Efor
@@ -196,7 +216,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
for wire in shape.Wires[1:]:
holes.append((base.Shape, wire))
# Add face depth to list
faceDepths.append(shape.BoundBox.ZMin)
else:
@@ -205,13 +225,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
PathLog.error(msg)
FreeCAD.Console.PrintWarning(msg)
# Set initial Start and Final Depths and recalculate depthparams
finDep = obj.FinalDepth.Value
strDep = obj.StartDepth.Value
if strDep > stock.Shape.BoundBox.ZMax:
strDep = stock.Shape.BoundBox.ZMax
startDepths.append(strDep)
self.depthparams = self._customDepthParams(obj, strDep, finDep)
@@ -230,31 +249,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
if obj.processPerimeter:
if obj.HandleMultipleFeatures == 'Collectively':
custDepthparams = self.depthparams
if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
if profileshape.BoundBox.ZMin > obj.FinalDepth.Value:
finDep = profileshape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope
envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
try:
env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams)
# env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams)
env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams)
except Exception: # pylint: disable=broad-except
# PathUtils.getEnvelope() failed to return an object.
PathLog.error(translate('Path', 'Unable to create path for face(s).'))
else:
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
elif obj.HandleMultipleFeatures == 'Individually':
for shape in faces:
profShape = Part.makeCompound([shape])
# profShape = Part.makeCompound([shape])
finalDep = obj.FinalDepth.Value
custDepthparams = self.depthparams
if obj.Side == 'Inside':
if finalDep < shape.BoundBox.ZMin:
# Recalculate depthparams
finalDep = shape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep, finalDep - 0.5)
env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep)
# env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
env = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
shapes.append(tup)
@@ -262,11 +284,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
startDepth = max(startDepths)
if obj.StartDepth.Value > startDepth:
obj.StartDepth.Value = startDepth
else: # Try to build targets from the job base
if 1 == len(self.model):
if hasattr(self.model[0], "Proxy"):
PathLog.info("hasattr() Proxy")
PathLog.debug("hasattr() Proxy")
if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet
if obj.processCircles or obj.processHoles:
for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True):
@@ -302,7 +324,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
obj.InverseAngle = False
obj.AttemptInverseAngle = True
obj.LimitDepthToFace = True
obj.HandleMultipleFeatures = 'Collectively'
obj.HandleMultipleFeatures = 'Individually'
def SetupProperties():
@@ -321,6 +343,5 @@ def Create(name, obj=None):
'''Create(name) ... Creates and returns a Profile based on faces operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectProfile(obj, name)
return obj