From 1f3b3ad4e4632eb2662bbeb7c17192d18047e217 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 3 Jun 2019 03:59:28 -0500 Subject: [PATCH] Integration of 4th-axis feature to PathProfileFaces 4th-axis integration should not affect current feature set --- src/Mod/Path/PathScripts/PathProfileFaces.py | 138 ++++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 73a8371683..5062c67d76 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -50,6 +50,9 @@ __title__ = "Path Profile Faces Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Path Profile operation based on faces." +__created__ = "2014" +__scriptVersion__ = "1c testing" +__lastModified__ = "2019-06-03 03:53 CST" class ObjectProfile(PathProfileBase.ObjectProfile): @@ -71,11 +74,20 @@ class ObjectProfile(PathProfileBase.ObjectProfile): obj.addProperty("App::PropertyBool", "processHoles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile holes as well as the outline")) obj.addProperty("App::PropertyBool", "processPerimeter", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the outline")) obj.addProperty("App::PropertyBool", "processCircles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes")) + if not hasattr(obj, 'ReverseDirection'): + obj.addProperty('App::PropertyBool', 'ReverseDirection', 'Rotation', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Reverse direction of pocket operation.')) + if not hasattr(obj, 'InverseAngle'): + obj.addProperty('App::PropertyBool', 'InverseAngle', 'Rotation', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Inverse the angle. Example: -22.5 -> 22.5 degrees.')) + if not hasattr(obj, 'B_AxisErrorOverride'): + obj.addProperty('App::PropertyBool', 'B_AxisErrorOverride', 'Rotation', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'Match B rotations to model (error in FreeCAD rendering).')) self.baseObject().initAreaOp(obj) def areaOpShapes(self, obj): '''areaOpShapes(obj) ... returns envelope for all base shapes or wires for Arch.Panels.''' + import math + import Draft + if obj.UseComp: self.commandlist.append(Path.Command("(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")")) else: @@ -84,10 +96,115 @@ class ObjectProfile(PathProfileBase.ObjectProfile): shapes = [] self.profileshape = [] + baseSubsTuples = [] + self.cloneNames = [] + subCount = 0 + allTuples = [] + + def judgeFinalDepth(obj, fD): + if obj.FinalDepth.Value >= fD: + return obj.FinalDepth.Value + else: + return fD + + def judgeStartDepth(obj, sD): + if obj.StartDepth.Value >= sD: + return obj.StartDepth.Value + else: + return sD + + def sortTuplesByIndex(TupleList, tagIdx): # return (TagList, GroupList) + # Separate elements, regroup by orientation (axis_angle combination) + TagList = ['X34.2'] + GroupList = [[(2.3, 3.4, 'X')]] + for tup in TupleList: + #(shape, sub, angle, axis, tag, strDep, finDep) = tup + if tup[tagIdx] in TagList: + # Determine index of found string + i = 0 + for orn in TagList: + if orn == tup[4]: + break + i += 1 + GroupList[i].append(tup) + else: + TagList.append(tup[4]) # add orientation entry + GroupList.append([tup]) # add orientation entry + # Remove temp elements + TagList.pop(0) + GroupList.pop(0) + return (TagList, GroupList) + if obj.Base: # The user has selected subobjects from the base. Process each. - for base in obj.Base: + if obj.EnableRotation != 'Off': + for p in range(0, len(obj.Base)): + (base, subsList) = obj.Base[p] + for sub in subsList: + shape = getattr(base.Shape, sub) + if isinstance(shape, Part.Face): + rtn = False + (rtn, angle, axis, praInfo) = self.pocketRotationAnalysis(obj, shape, prnt=True) + PathLog.info("praInfo: \n" + str(praInfo)) + if rtn is True: + PathLog.debug(str(sub) + ": rotating model to make face normal at (0,0,1) ...") + + if obj.InverseAngle is True: + angle = -1 * angle + + # Create a temporary clone of model for rotational use. + rndAng = round(angle, 8) + if rndAng < 0.0: # neg sign is converted to underscore in clone name creation. + tag = base.Name + '__' + axis + '_' + str(math.fabs(rndAng)).replace('.', '_') + else: + tag = base.Name + '__' + axis + str(rndAng).replace('.', '_') + clnNm = base.Name + '_' + tag + if clnNm not in self.cloneNames: + self.cloneNames.append(clnNm) + PathLog.info("tmp clone created: " + str(clnNm)) + FreeCAD.ActiveDocument.addObject('Part::Feature', clnNm).Shape = base.Shape + newBase = FreeCAD.ActiveDocument.getObject(clnNm) + + if axis == 'X': + vect = FreeCAD.Vector(1, 0, 0) + elif axis == 'Y': + vect = FreeCAD.Vector(0, 1, 0) + # Rotate base to such that Surface.Axis of pocket bottom is Z=1 + base = Draft.rotate(newBase, angle, center=FreeCAD.Vector(0.0, 0.0, 0.0), axis=vect, copy=False) + # shape = getattr(base.Shape, sub) + else: + PathLog.debug(str(sub) + ": no rotation used") + axis = 'X' + angle = 0.0 + tag = base.Name + '__' + axis + str(angle).replace('.', '_') + # Eif + tup = base, sub, tag, angle, axis + allTuples.append(tup) + # Eif + subCount += 1 + # Efor + # Efor + (hTags, hGrps) = sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) + subList = [] + for o in range(0, len(hTags)): + PathLog.debug('hTag: {}'.format(hTags[o])) + subList = [] + for (base, sub, tag, angle, axis) in hGrps[o]: + subList.append(sub) + pair = base, subList, angle, axis + baseSubsTuples.append(pair) + # Efor + else: + PathLog.info("Use Rotation feature(property) is 'Off'.") + for (base, subList) in obj.Base: + baseSubsTuples.append((base, subList, 'pathProfileFaces', 0.0, 'X')) + + # for base in obj.Base: + for base in baseSubsTuples: holes = [] faces = [] + angle = base[2] + axis = base[3] + for sub in base[1]: shape = getattr(base[0].Shape, sub) if isinstance(shape, Part.Face): @@ -95,6 +212,14 @@ 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[0].Shape, wire)) + + finish_step = obj.FinishDepth.Value if hasattr(obj, "FinishDepth") else 0.0 + finDep = judgeFinalDepth(obj, shape.BoundBox.ZMin) + self.depthparams = PathUtils.depth_params( + clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, + start_depth=obj.StartDepth.Value, step_down=obj.StepDown.Value, + z_finish_step=finish_step, final_depth=finDep, + user_depths=None) else: FreeCAD.Console.PrintWarning("found a base object which is not a face. Can't continue.") return @@ -105,7 +230,8 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if (drillable and obj.processCircles) or (not drillable and obj.processHoles): env = PathUtils.getEnvelope(shape, subshape=f, depthparams=self.depthparams) PathLog.track() - shapes.append((env, True)) + # shapes.append((env, True)) + shapes.append((env, True, 'pathProfileFaces', base[2], base[3])) if len(faces) > 0: profileshape = Part.makeCompound(faces) @@ -114,7 +240,8 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.processPerimeter: env = PathUtils.getEnvelope(base[0].Shape, subshape=profileshape, depthparams=self.depthparams) PathLog.track() - shapes.append((env, False)) + # shapes.append((env, False)) + shapes.append((env, False, 'pathProfileFaces', base[2], base[3])) else: # Try to build targets from the job base if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): @@ -137,6 +264,8 @@ class ObjectProfile(PathProfileBase.ObjectProfile): self.removalshapes = shapes PathLog.debug("%d shapes" % len(shapes)) + + # self.cloneNames = [] # Comment out to leave temp clones visible return shapes def areaOpSetDefaultValues(self, obj, job): @@ -146,6 +275,9 @@ class ObjectProfile(PathProfileBase.ObjectProfile): obj.processHoles = False obj.processCircles = False obj.processPerimeter = True + obj.ReverseDirection = False + obj.InverseAngle = True + obj.B_AxisErrorOverride = False def SetupProperties(): setup = []