Merge pull request #4071 from Russ4262/fix_rotational_drilling_depth

Path: Fix Drilling Op issues when using rotation feature
This commit is contained in:
sliptonic
2020-12-02 10:22:01 -06:00
committed by GitHub
2 changed files with 293 additions and 148 deletions

View File

@@ -19,12 +19,6 @@
# * USA *
# * *
# ***************************************************************************
# * *
# * Additional modifications and contributions beginning 2019 *
# * Focus: 4th-axis integration *
# * by Russell Johnson <russ4262@gmail.com> *
# * *
# ***************************************************************************
import FreeCAD
import PathScripts.PathLog as PathLog
@@ -50,9 +44,6 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Base class an implementation for operations on circular holes."
__contributors__ = "russ4262 (Russell Johnson)"
__created__ = "2017"
__scriptVersion__ = "2b"
__lastModified__ = "2020-02-13 17:11 CST"
# Qt translation handling
@@ -253,97 +244,69 @@ class ObjectOp(PathOp.ObjectOp):
baseSubsTuples.append((base, subList, 0.0, 'A', stock))
else:
for p in range(0, len(obj.Base)):
(base, subsList) = obj.Base[p]
for sub in subsList:
if self.isHoleEnabled(obj, base, sub):
shape = getattr(base.Shape, sub)
rtn = False
(norm, surf) = self.getFaceNormAndSurf(shape)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
if rtn is True:
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount)
# Verify faces are correctly oriented - InverseAngle might be necessary
PathLog.debug("Verifying {} orientation: running faceRotationAnalysis() again.".format(sub))
faceIA = getattr(clnBase.Shape, sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
if rtn is True:
msg = obj.Name + ":: "
msg += translate("Path", "{} might be misaligned after initial rotation.".format(sub)) + " "
if obj.AttemptInverseAngle is True and obj.InverseAngle is False:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
msg += translate("Path", "Rotated to 'InverseAngle' to attempt access.")
else:
if len(subsList) == 1:
msg += translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
else:
msg += translate("Path", "Consider transferring '{}' to independent operation.".format(sub))
PathLog.warning(msg)
# title = translate("Path", 'Rotation Warning')
# self.guiMessage(title, msg, False)
else:
PathLog.debug("Face appears to be oriented correctly.")
cmnt = "{}: {} @ {}; ".format(sub, axis, str(round(angle, 5)))
if cmnt not in obj.Comment:
obj.Comment += cmnt
tup = clnBase, sub, tag, angle, axis, clnStock
allTuples.append(tup)
else:
if self.warnDisabledAxis(obj, axis, sub) is True:
pass # Skip drill feature due to access issue
else:
PathLog.debug(str(sub) + ": No rotation used")
axis = 'X'
angle = 0.0
tag = base.Name + '_' + axis + str(angle).replace('.', '_')
stock = PathUtils.findParentJob(obj).Stock
tup = base, sub, tag, angle, axis, stock
allTuples.append(tup)
# Eif
# Eif
subCount += 1
# Efor
# Efor
(Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
for o in range(0, len(Tags)):
PathLog.debug('hTag: {}'.format(Tags[o]))
subList = []
for (base, sub, tag, angle, axis, stock) in Grps[o]:
subList.append(sub)
pair = base, subList, angle, axis, stock
baseSubsTuples.append(pair)
# Efor
(bst, at) = self.process_base_geometry_with_rotation(obj, p, subCount)
allTuples.extend(at)
baseSubsTuples.extend(bst)
for base, subs, angle, axis, stock in baseSubsTuples:
# rotate shorter angle in opposite direction
if angle > 180:
angle -= 360
elif angle < -180:
angle += 360
# Re-analyze rotated model for drillable holes
if obj.EnableRotation != 'Off':
rotated_features = self.findHoles(obj, base)
for sub in subs:
PathLog.debug('sub, angle, axis: {}, {}, {}'.format(sub, angle, axis))
if self.isHoleEnabled(obj, base, sub):
pos = self.holePosition(obj, base, sub)
if pos:
# Default is treat selection as 'Face' shape
holeBtm = base.Shape.getElement(sub).BoundBox.ZMin
# Identify face to which edge belongs
sub_shape = base.Shape.getElement(sub)
# Default is to treat selection as 'Face' shape
holeBtm = sub_shape.BoundBox.ZMin
if obj.EnableRotation != 'Off':
# Update Start and Final depths due to rotation, if auto defaults are active
parent_face = self._find_parent_face_of_edge(rotated_features, sub_shape)
if parent_face:
PathLog.debug('parent_face found')
holeBtm = parent_face.BoundBox.ZMin
if obj.OpStartDepth == obj.StartDepth:
obj.StartDepth.Value = parent_face.BoundBox.ZMax
PathLog.debug('new StartDepth: {}'.format(obj.StartDepth.Value))
if obj.OpFinalDepth == obj.FinalDepth:
obj.FinalDepth.Value = holeBtm
PathLog.debug('new FinalDepth: {}'.format(holeBtm))
else:
PathLog.debug('NO parent_face identified')
if base.Shape.getElement(sub).ShapeType == 'Edge':
msg = translate("Path", "Verify Final Depth of holes based on edges. {} depth is: {} mm".format(sub, round(holeBtm, 4))) + " "
msg += translate("Path", "Always select the bottom edge of the hole when using an edge.")
PathLog.warning(msg)
# Warn user if Final Depth set lower than bottom of hole
if finDep < holeBtm:
if obj.FinalDepth.Value < holeBtm:
msg = translate("Path", "Final Depth setting is below the hole bottom for {}.".format(sub)) + ' '
msg += translate("Path", "{} depth is calculated at {} mm".format(sub, round(holeBtm, 4)))
PathLog.warning(msg)
holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub),
'angle': angle, 'axis': axis, 'trgtDep': finDep,
'angle': angle, 'axis': axis, 'trgtDep': obj.FinalDepth.Value,
'stkTop': stock.Shape.BoundBox.ZMax})
# haveLocations are populated from user-provided (x, y) coordinates
# provided by the user in the Base Locations tab of the Task Editor window
if haveLocations(self, obj):
for location in obj.Locations:
# holes.append({'x': location.x, 'y': location.y, 'r': 0, 'angle': 0.0, 'axis': 'X', 'holeBtm': obj.FinalDepth.Value})
holes.append({'x': location.x, 'y': location.y, 'r': 0,
'angle': 0.0, 'axis': 'X', 'trgtDep': finDep,
'angle': 0.0, 'axis': 'X', 'trgtDep': obj.FinalDepth.Value,
'stkTop': PathUtils.findParentJob(obj).Stock.Shape.BoundBox.ZMax})
if len(holes) > 0:
@@ -435,13 +398,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
xRotRad = 0.01
yRotRad = 0.01
zRotRad = 0.01
# Determine boundbox radius based upon xzy limits data
if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):
@@ -463,10 +421,8 @@ class ObjectOp(PathOp.ObjectOp):
else:
xlim = self.stockBB.XMax
if ylim != 0.0:
xRotRad = math.sqrt(ylim**2 + zlim**2)
if xlim != 0.0:
yRotRad = math.sqrt(xlim**2 + zlim**2)
xRotRad = math.sqrt(ylim**2 + zlim**2)
yRotRad = math.sqrt(xlim**2 + zlim**2)
zRotRad = math.sqrt(xlim**2 + ylim**2)
clrOfst = parentJob.SetupSheet.ClearanceHeightOffset.Value
@@ -552,9 +508,9 @@ class ObjectOp(PathOp.ObjectOp):
if saX < 0.0:
angle = angle + 180.0
elif saZ == 0.0:
if saY != 0.0:
angle = math.degrees(math.atan(saX / saY))
orientation = "Y"
# if saY != 0.0:
angle = math.degrees(math.atan(saX / saY))
orientation = "Y"
if saX + nX == 0.0:
angle = -1 * angle
@@ -600,9 +556,8 @@ class ObjectOp(PathOp.ObjectOp):
axis = 'Y'
rtn = True
if rtn is True:
if rtn:
self.rotateFlag = True # pylint: disable=attribute-defined-outside-init
# rtn = True
if obj.ReverseDirection is True:
if angle < 180.0:
angle = angle + 180.0
@@ -616,8 +571,6 @@ class ObjectOp(PathOp.ObjectOp):
else:
praInfo += "\n - ... NO rotation triggered"
PathLog.debug("\n" + str(praInfo))
return (rtn, angle, axis, praInfo)
def guiMessage(self, title, msg, show=False):
@@ -646,17 +599,19 @@ class ObjectOp(PathOp.ObjectOp):
'''visualAxis()
Create visual X & Y axis for use in orientation of rotational operations
Triggered only for PathLog.debug'''
fcad = FreeCAD.ActiveDocument
if not FreeCAD.ActiveDocument.getObject('xAxCyl'):
if not fcad.getObject('xAxCyl'):
xAx = 'xAxCyl'
yAx = 'yAxCyl'
FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "visualAxis")
# zAx = 'zAxCyl'
visual_axis_obj = fcad.addObject("App::DocumentObjectGroup", "visualAxis")
if FreeCAD.GuiUp:
FreeCADGui.ActiveDocument.getObject('visualAxis').Visibility = False
vaGrp = FreeCAD.ActiveDocument.getObject("visualAxis")
vaGrp = fcad.getObject("visualAxis")
FreeCAD.ActiveDocument.addObject("Part::Cylinder", xAx)
cyl = FreeCAD.ActiveDocument.getObject(xAx)
fcad.addObject("Part::Cylinder", xAx)
cyl = fcad.getObject(xAx)
cyl.Label = xAx
cyl.Radius = self.xRotRad
cyl.Height = 0.01
@@ -669,8 +624,8 @@ class ObjectOp(PathOp.ObjectOp):
cylGui.Visibility = False
vaGrp.addObject(cyl)
FreeCAD.ActiveDocument.addObject("Part::Cylinder", yAx)
cyl = FreeCAD.ActiveDocument.getObject(yAx)
fcad.addObject("Part::Cylinder", yAx)
cyl = fcad.getObject(yAx)
cyl.Label = yAx
cyl.Radius = self.yRotRad
cyl.Height = 0.01
@@ -682,28 +637,33 @@ class ObjectOp(PathOp.ObjectOp):
cylGui.Transparency = 85
cylGui.Visibility = False
vaGrp.addObject(cyl)
visual_axis_obj.purgeTouched()
def useTempJobClones(self, cloneName):
'''useTempJobClones(cloneName)
Manage use of temporary model clones for rotational operation calculations.
Clones are stored in 'rotJobClones' group.'''
if FreeCAD.ActiveDocument.getObject('rotJobClones'):
fcad = FreeCAD.ActiveDocument
if fcad.getObject('rotJobClones'):
if cloneName == 'Start':
if PathLog.getLevel(PathLog.thisModule()) < 4:
for cln in FreeCAD.ActiveDocument.getObject('rotJobClones').Group:
FreeCAD.ActiveDocument.removeObject(cln.Name)
for cln in fcad.getObject('rotJobClones').Group:
fcad.removeObject(cln.Name)
elif cloneName == 'Delete':
if PathLog.getLevel(PathLog.thisModule()) < 4:
for cln in FreeCAD.ActiveDocument.getObject('rotJobClones').Group:
FreeCAD.ActiveDocument.removeObject(cln.Name)
FreeCAD.ActiveDocument.removeObject('rotJobClones')
for cln in fcad.getObject('rotJobClones').Group:
fcad.removeObject(cln.Name)
fcad.removeObject('rotJobClones')
else:
fcad.getObject('rotJobClones').purgeTouched()
else:
FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "rotJobClones")
fcad.addObject("App::DocumentObjectGroup", "rotJobClones")
if FreeCAD.GuiUp:
FreeCADGui.ActiveDocument.getObject('rotJobClones').Visibility = False
if cloneName != 'Start' and cloneName != 'Delete':
FreeCAD.ActiveDocument.getObject('rotJobClones').addObject(FreeCAD.ActiveDocument.getObject(cloneName))
fcad.getObject('rotJobClones').addObject(fcad.getObject(cloneName))
if FreeCAD.GuiUp:
FreeCADGui.ActiveDocument.getObject(cloneName).Visibility = False
@@ -712,6 +672,7 @@ class ObjectOp(PathOp.ObjectOp):
Method called to create a temporary clone of the base and parent Job stock.
Clones are destroyed after usage for calculations related to rotational operations.'''
# Create a temporary clone and stock of model for rotational use.
fcad = FreeCAD.ActiveDocument
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('.', '_')
@@ -722,21 +683,21 @@ class ObjectOp(PathOp.ObjectOp):
if clnNm not in self.cloneNames:
self.cloneNames.append(clnNm)
self.cloneNames.append(stckClnNm)
if FreeCAD.ActiveDocument.getObject(clnNm):
FreeCAD.ActiveDocument.getObject(clnNm).Shape = base.Shape
if fcad.getObject(clnNm):
fcad.getObject(clnNm).Shape = base.Shape
else:
FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape
fcad.addObject('Part::Feature', clnNm).Shape = base.Shape
self.useTempJobClones(clnNm)
if FreeCAD.ActiveDocument.getObject(stckClnNm):
FreeCAD.ActiveDocument.getObject(stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
if fcad.getObject(stckClnNm):
fcad.getObject(stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
else:
FreeCAD.ActiveDocument.addObject('Part::Feature', stckClnNm).Shape = PathUtils.findParentJob(obj).Stock.Shape
fcad.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)
clnBase = FreeCAD.ActiveDocument.getObject(clnNm)
clnStock = FreeCAD.ActiveDocument.getObject(stckClnNm)
clnBase = fcad.getObject(clnNm)
clnStock = fcad.getObject(stckClnNm)
tag = base.Name + '_' + tag
return (clnBase, clnStock, tag)
@@ -747,10 +708,6 @@ class ObjectOp(PathOp.ObjectOp):
norm = FreeCAD.Vector(0.0, 0.0, 0.0)
surf = FreeCAD.Vector(0.0, 0.0, 0.0)
if face.ShapeType == 'Edge':
edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([face])))
face = edgToFace
if hasattr(face, 'normalAt'):
n = face.normalAt(0, 0)
elif hasattr(face, 'normal'):
@@ -759,7 +716,6 @@ class ObjectOp(PathOp.ObjectOp):
s = face.Surface.Axis
else:
s = n
norm.x = n.x
norm.y = n.y
norm.z = n.z
@@ -778,11 +734,6 @@ class ObjectOp(PathOp.ObjectOp):
elif axis == 'Y':
vect = FreeCAD.Vector(0, 1, 0)
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)
@@ -808,8 +759,10 @@ class ObjectOp(PathOp.ObjectOp):
clnStock.purgeTouched()
# Update property and angle values
obj.InverseAngle = True
obj.AttemptInverseAngle = False
# obj.AttemptInverseAngle = False
angle = -1 * angle
PathLog.debug(translate("Path", "Rotated to inverse angle."))
return (clnBase, clnStock, angle)
def sortTuplesByIndex(self, TupleList, tagIdx):
@@ -852,3 +805,183 @@ class ObjectOp(PathOp.ObjectOp):
return True
else:
return False
def isFaceUp(self, base, face):
'''isFaceUp(base, face) ...
When passed a base object and face shape, returns True if face is up.
This method is used to identify correct rotation of a model.
'''
# verify face is normal to Z+-
(norm, surf) = self.getFaceNormAndSurf(face)
if round(abs(norm.z), 8) != 1.0 or round(abs(surf.z), 8) != 1.0:
PathLog.debug('isFaceUp - face not oriented normal to Z+-')
return False
curve = face.OuterWire.Edges[0].Curve
if curve.TypeId == "Part::GeomCircle":
center = curve.Center
radius = curve.Radius * 1.
face = Part.Face(Part.Wire(Part.makeCircle(radius, center)))
up = face.extrude(FreeCAD.Vector(0.0, 0.0, 5.0))
dwn = face.extrude(FreeCAD.Vector(0.0, 0.0, -5.0))
upCmn = base.Shape.common(up)
dwnCmn = base.Shape.common(dwn)
# Identify orientation based on volumes of common() results
if len(upCmn.Edges) > 0:
PathLog.debug('isFaceUp - HAS up edges\n')
if len(dwnCmn.Edges) > 0:
PathLog.debug('isFaceUp - up and dwn edges\n')
dVol = round(dwnCmn.Volume, 6)
uVol = round(upCmn.Volume, 6)
if uVol > dVol:
return False
return True
else:
if round(upCmn.Volume, 6) == 0.0:
return True
return False
elif len(dwnCmn.Edges) > 0:
PathLog.debug('isFaceUp - HAS dwn edges only\n')
dVol = round(dwnCmn.Volume, 6)
if dVol == 0.0:
return False
return True
PathLog.debug('isFaceUp - exit True')
return True
def process_base_geometry_with_rotation(self, obj, p, subCount):
'''process_base_geometry_with_rotation(obj, p, subCount)...
This method is the control method for analyzing the selected features,
determining their rotational needs, and creating clones as needed
for rotational access for the pocketing operation.
Requires the object, obj.Base index (p), and subCount reference arguments.
Returns two lists of tuples for continued processing into paths.
'''
baseSubsTuples = []
allTuples = []
(base, subsList) = obj.Base[p]
PathLog.debug(translate('Path', "Processing subs individually ..."))
for sub in subsList:
subCount += 1
tup = self.process_nonloop_sublist(obj, base, sub)
if tup:
allTuples.append(tup)
baseSubsTuples.append(tup)
return (baseSubsTuples, allTuples)
def process_nonloop_sublist(self, obj, base, sub):
'''process_nonloop_sublist(obj, sub)...
Process sublist with non-looped set of features when rotation is enabled.
'''
rtn = False
face = base.Shape.getElement(sub)
if sub[:4] != 'Face':
if face.ShapeType == 'Edge':
edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([face])))
face = edgToFace
else:
ignoreSub = base.Name + '.' + sub
PathLog.error(translate('Path', "Selected feature is not a Face. Ignoring: {}".format(ignoreSub)))
return False
(norm, surf) = self.getFaceNormAndSurf(face)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("initial rotational analysis: {}".format(praInfo))
clnBase = base
faceIA = clnBase.Shape.getElement(sub)
if faceIA.ShapeType == 'Edge':
edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
faceIA = edgToFace
if rtn is True:
faceNum = sub.replace('Face', '')
PathLog.debug("initial applyRotationalAnalysis")
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, faceNum)
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = clnBase.Shape.getElement(sub)
if faceIA.ShapeType == 'Edge':
edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
faceIA = edgToFace
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("follow-up rotational analysis: {}".format(praInfo2))
isFaceUp = self.isFaceUp(clnBase, faceIA)
PathLog.debug('... initial isFaceUp: {}'.format(isFaceUp))
if isFaceUp:
rtn = False
PathLog.debug('returning analysis: {}, {}'.format(praAngle, praAxis))
return (clnBase, [sub], angle, axis, clnStock)
if round(abs(praAngle), 8) == 180.0:
rtn = False
if not isFaceUp:
PathLog.debug('initial isFaceUp is False')
angle = 0.0
# Eif
if rtn:
# initial rotation failed, attempt inverse rotation if user requests it
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.") + ' 2')
if obj.AttemptInverseAngle:
PathLog.debug(translate("Path", "Applying inverse angle automatically."))
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
if obj.InverseAngle:
PathLog.debug(translate("Path", "Applying inverse angle manually."))
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
faceIA = clnBase.Shape.getElement(sub)
if faceIA.ShapeType == 'Edge':
edgToFace = Part.Face(Part.Wire(Part.__sortEdges__([faceIA])))
faceIA = edgToFace
if not self.isFaceUp(clnBase, faceIA):
angle += 180.0
# Normalize rotation angle
if angle < 0.0:
angle += 360.0
elif angle > 360.0:
angle -= 360.0
return (clnBase, [sub], angle, axis, clnStock)
if not self.warnDisabledAxis(obj, axis):
PathLog.debug(str(sub) + ": No rotation used")
axis = 'X'
angle = 0.0
stock = PathUtils.findParentJob(obj).Stock
return (base, [sub], angle, axis, stock)
def _find_parent_face_of_edge(self, rotated_features, test_shape):
'''_find_parent_face_of_edge(rotated_features, test_shape)...
Compare test_shape with each within rotated_features to identify
and return the parent face of the test_shape, if it exists.'''
for (base, sub) in rotated_features:
sub_shape = base.Shape.getElement(sub)
if test_shape.isSame(sub_shape):
return sub_shape
elif test_shape.isEqual(sub_shape):
return sub_shape
else:
for e in sub_shape.Edges:
if test_shape.isSame(e):
return sub_shape
elif test_shape.isEqual(e):
return sub_shape
return False

View File

@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * 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 *
@@ -21,12 +20,6 @@
# * USA *
# * *
# ***************************************************************************
# * *
# * Additional modifications and contributions beginning 2019 *
# * Focus: 4th-axis integration *
# * by Russell Johnson <russ4262@gmail.com> *
# * *
# ***************************************************************************
from __future__ import print_function
@@ -43,6 +36,8 @@ __title__ = "Path Drilling Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Path Drilling operation."
__contributors__ = "russ4262 (Russell Johnson)"
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
@@ -124,7 +119,6 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
params = {}
params['X'] = p['x']
params['Y'] = p['y']
params.update(cmdParams)
if obj.EnableRotation != 'Off':
angle = p['angle']
axis = p['axis']
@@ -153,7 +147,14 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
# Prepare for drilling cycle
self.commandlist.append(Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'X': p['x'], 'Y': p['y'], 'F': self.horizRapid}))
self.commandlist.append(Path.Command('G1', {'Z': p['stkTop'], 'F': self.vertFeed}))
self.commandlist.append(Path.Command('G1', {'Z': obj.StartDepth.Value, 'F': self.vertFeed}))
# Update retract height due to rotation
self.opSetDefaultRetractHeight(obj)
cmdParams['R'] = obj.RetractHeight.Value
# Update changes to parameters
params.update(cmdParams)
# Perform canned drilling cycle
self.commandlist.append(Path.Command(cmd, params))
@@ -171,23 +172,34 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(Path.Command('G0', {lastAxis: 0.0, 'F': self.axialRapid}))
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj, job) ... set default value for RetractHeight'''
def opSetDefaultRetractHeight(self, obj, job=None):
'''opSetDefaultRetractHeight(obj, job) ... set default Retract Height value'''
parentJob = PathUtils.findParentJob(obj)
has_job = True
if not job:
job = PathUtils.findParentJob(obj)
has_job = False
if hasattr(parentJob.SetupSheet, 'RetractHeight'):
obj.RetractHeight = parentJob.SetupSheet.RetractHeight
if hasattr(job.SetupSheet, 'RetractHeight'):
obj.RetractHeight = job.SetupSheet.RetractHeight
elif self.applyExpression(obj, 'RetractHeight', 'OpStartDepth+1mm'):
obj.RetractHeight = 10
if has_job:
obj.RetractHeight = 10
else:
obj.RetractHeight.Value = obj.StartDepth.Value + 1.0
if hasattr(parentJob.SetupSheet, 'PeckDepth'):
obj.PeckDepth = parentJob.SetupSheet.PeckDepth
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj, job) ... Set default property values'''
self.opSetDefaultRetractHeight(obj, job)
if hasattr(job.SetupSheet, 'PeckDepth'):
obj.PeckDepth = job.SetupSheet.PeckDepth
elif self.applyExpression(obj, 'PeckDepth', 'OpToolDiameter*0.75'):
obj.PeckDepth = 1
if hasattr(parentJob.SetupSheet, 'DwellTime'):
obj.DwellTime = parentJob.SetupSheet.DwellTime
if hasattr(job.SetupSheet, 'DwellTime'):
obj.DwellTime = job.SetupSheet.DwellTime
else:
obj.DwellTime = 1
@@ -198,8 +210,8 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
# Initial setting for EnableRotation is taken from Job SetupSheet
# User may override on per-operation basis as needed.
if hasattr(parentJob.SetupSheet, 'SetupEnableRotation'):
obj.EnableRotation = parentJob.SetupSheet.SetupEnableRotation
if hasattr(job.SetupSheet, 'SetupEnableRotation'):
obj.EnableRotation = job.SetupSheet.SetupEnableRotation
else:
obj.EnableRotation = 'Off'