From aa2d35cad8f313926db72470cb9592efbe38405f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 20 Nov 2020 19:15:16 -0600 Subject: [PATCH 1/7] Path: Apply the user's Final Depth value --- src/Mod/Path/PathScripts/PathCircularHoleBase.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 052c7eb1a5..a41c30d868 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -330,20 +330,21 @@ class ObjectOp(PathOp.ObjectOp): 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 Base Locations tab as (x,y) coordinates 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: From 693db4e4b602c67289486ec169789a8f4a58e9a3 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Nov 2020 22:51:44 -0600 Subject: [PATCH 2/7] Path: Synchronize methods with same in PathAreaOp module The affected methods exist in the PathAreaOp module. This commit synchronizes the versions here with those. --- .../Path/PathScripts/PathCircularHoleBase.py | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index a41c30d868..37e4a87848 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -436,13 +436,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): @@ -464,10 +459,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 @@ -553,9 +546,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 @@ -601,9 +594,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 @@ -617,8 +609,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): @@ -651,7 +641,8 @@ class ObjectOp(PathOp.ObjectOp): if not FreeCAD.ActiveDocument.getObject('xAxCyl'): xAx = 'xAxCyl' yAx = 'yAxCyl' - FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "visualAxis") + # zAx = 'zAxCyl' + VA = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "visualAxis") if FreeCAD.GuiUp: FreeCADGui.ActiveDocument.getObject('visualAxis').Visibility = False vaGrp = FreeCAD.ActiveDocument.getObject("visualAxis") @@ -683,6 +674,7 @@ class ObjectOp(PathOp.ObjectOp): cylGui.Transparency = 85 cylGui.Visibility = False vaGrp.addObject(cyl) + VA.purgeTouched() def useTempJobClones(self, cloneName): '''useTempJobClones(cloneName) @@ -698,6 +690,8 @@ class ObjectOp(PathOp.ObjectOp): for cln in FreeCAD.ActiveDocument.getObject('rotJobClones').Group: FreeCAD.ActiveDocument.removeObject(cln.Name) FreeCAD.ActiveDocument.removeObject('rotJobClones') + else: + FreeCAD.ActiveDocument.getObject('rotJobClones').purgeTouched() else: FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "rotJobClones") if FreeCAD.GuiUp: @@ -748,10 +742,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'): @@ -760,7 +750,6 @@ class ObjectOp(PathOp.ObjectOp): s = face.Surface.Axis else: s = n - norm.x = n.x norm.y = n.y norm.z = n.z @@ -779,10 +768,11 @@ 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 + # Commented out to fix PocketShape InverseAngle rotation problem + # 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) @@ -809,8 +799,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): From 43cd864f8ff1bd0656929bec57181190ef0e8d5c Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Nov 2020 23:08:32 -0600 Subject: [PATCH 3/7] Path: Clean up comments and simplify one code block --- src/Mod/Path/PathScripts/PathCircularHoleBase.py | 12 ++---------- src/Mod/Path/PathScripts/PathDrilling.py | 13 ++++--------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 37e4a87848..66a0441048 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -19,12 +19,6 @@ # * USA * # * * # *************************************************************************** -# * * -# * Additional modifications and contributions beginning 2019 * -# * Focus: 4th-axis integration * -# * by Russell Johnson * -# * * -# *************************************************************************** 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 @@ -339,7 +330,8 @@ class ObjectOp(PathOp.ObjectOp): 'angle': angle, 'axis': axis, 'trgtDep': obj.FinalDepth.Value, 'stkTop': stock.Shape.BoundBox.ZMax}) - # haveLocations are populated from Base Locations tab as (x,y) coordinates + # 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}) diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index dcd95705f7..a646479e8b 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2014 Yorik van Havre * -# * 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 * -# * * -# *************************************************************************** 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()) @@ -198,8 +193,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' From d52a96f2b5426ee413384a6e3458b1c894b355a4 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Nov 2020 23:07:06 -0600 Subject: [PATCH 4/7] Path: Fix rotational errors and organize/simplify some rotational code Fix rotational alignment and drilling depth issues for rotational drilling ops. Issues identified in forum at https://forum.freecadweb.org/viewtopic.php?style=3&f=15&t=52262. Path: --- .../Path/PathScripts/PathCircularHoleBase.py | 280 ++++++++++++++---- src/Mod/Path/PathScripts/PathDrilling.py | 41 ++- 2 files changed, 244 insertions(+), 77 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 66a0441048..3fe35a55ca 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -244,77 +244,47 @@ 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.") @@ -837,3 +807,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 diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index a646479e8b..3be3a859cb 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -119,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'] @@ -148,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)) @@ -166,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 From 43d54e7f18b6ad3294b597f5526f05781dbb6976 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Nov 2020 21:08:52 -0600 Subject: [PATCH 5/7] Path: Simplify ActiveDocument references --- .../Path/PathScripts/PathCircularHoleBase.py | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 3fe35a55ca..4d777ceb26 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -599,18 +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' # zAx = 'zAxCyl' - VA = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "visualAxis") + VA = 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 @@ -623,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 @@ -642,25 +643,27 @@ class ObjectOp(PathOp.ObjectOp): '''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: - FreeCAD.ActiveDocument.getObject('rotJobClones').purgeTouched() + 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 @@ -669,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('.', '_') @@ -679,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) From 99beebc2afa4d59614bf92886d16d09022d9d3ee Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Nov 2020 21:05:56 -0600 Subject: [PATCH 6/7] Path: Replace all-caps variable name --- src/Mod/Path/PathScripts/PathCircularHoleBase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 4d777ceb26..58a98a1f8d 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -605,7 +605,7 @@ class ObjectOp(PathOp.ObjectOp): xAx = 'xAxCyl' yAx = 'yAxCyl' # zAx = 'zAxCyl' - VA = fcad.addObject("App::DocumentObjectGroup", "visualAxis") + visual_axis_obj = fcad.addObject("App::DocumentObjectGroup", "visualAxis") if FreeCAD.GuiUp: FreeCADGui.ActiveDocument.getObject('visualAxis').Visibility = False vaGrp = fcad.getObject("visualAxis") @@ -637,7 +637,7 @@ class ObjectOp(PathOp.ObjectOp): cylGui.Transparency = 85 cylGui.Visibility = False vaGrp.addObject(cyl) - VA.purgeTouched() + visual_axis_obj.purgeTouched() def useTempJobClones(self, cloneName): '''useTempJobClones(cloneName) From 2461d942af861928db71176438bb5055dc596d0a Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Nov 2020 21:09:34 -0600 Subject: [PATCH 7/7] Path: Delete unnecessary comment block --- src/Mod/Path/PathScripts/PathCircularHoleBase.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index 58a98a1f8d..c88cdf90d4 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -734,12 +734,6 @@ class ObjectOp(PathOp.ObjectOp): elif axis == 'Y': vect = FreeCAD.Vector(0, 1, 0) - # Commented out to fix PocketShape InverseAngle rotation problem - # 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)