From ac4978cbf937dc47b621b2aee8d6ebc63d7eb8b8 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 17:17:29 -0600 Subject: [PATCH 1/8] translation fixes --- src/Mod/Path/PathScripts/PathHelix.py | 381 +++++++++++++++-------- src/Mod/Path/PathScripts/PathHelixGui.py | 31 +- 2 files changed, 278 insertions(+), 134 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index cdf3a24faf..644570f8ad 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -20,18 +20,17 @@ # * * # *************************************************************************** +from Generators import helix_generator +from PathScripts.PathUtils import fmt +from PathScripts.PathUtils import sort_locations +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD +import Part import Path - import PathScripts.PathCircularHoleBase as PathCircularHoleBase import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp -from PathScripts.PathUtils import fmt -from PathScripts.PathUtils import findParentJob -from PathScripts.PathUtils import sort_locations -from PySide import QtCore - __title__ = "Path Helix Drill Operation" __author__ = "Lorenz Hüdepohl" __url__ = "https://www.freecadweb.org" @@ -42,167 +41,287 @@ __scriptVersion__ = "1b testing" __lastModified__ = "2019-07-12 09:50 CST" -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +translate = FreeCAD.Qt.translate class ObjectHelix(PathCircularHoleBase.ObjectOp): - '''Proxy class for Helix operations.''' + """Proxy class for Helix operations.""" + + @classmethod + def helixOpPropertyEnumerations(self, dataType="data"): + """helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. + Args: + dataType = 'data', 'raw', 'translated' + Notes: + 'data' is list of internal string literals used in code + 'raw' is list of (translated_text, data_string) tuples + 'translated' is list of translated string literals + """ + + # Enumeration lists for App::PropertyEnumeration properties + enums = { + "Direction": [ + (translate("Path_Helix", "CW"), "CW"), + (translate("Path_Helix", "CCW"), "CCW"), + ], # this is the direction that the profile runs + "StartSide": [ + (translate("PathProfile", "Outside"), "Outside"), + (translate("PathProfile", "Inside"), "Inside"), + ], # side of profile that cutter is on in relation to direction of profile + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + PathLog.debug(enums) + + for k, v in enumerate(enums): + # data[k] = [tup[idx] for tup in v] + data.append((v, [tup[idx] for tup in enums[v]])) + PathLog.debug(data) + + return data def circularHoleFeatures(self, obj): - '''circularHoleFeatures(obj) ... enable features supported by Helix.''' - return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces + """circularHoleFeatures(obj) ... enable features supported by Helix.""" + return ( + PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces + ) def initCircularHoleOperation(self, obj): - '''initCircularHoleOperation(obj) ... create helix specific properties.''' - obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill", translate("PathHelix", "The direction of the circular cuts, ClockWise (CW), or CounterClockWise (CCW)")) - obj.Direction = ['CW', 'CCW'] + """initCircularHoleOperation(obj) ... create helix specific properties.""" + obj.addProperty( + "App::PropertyEnumeration", + "Direction", + "Helix Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "The direction of the circular cuts, ClockWise (CW), or CounterClockWise (CCW)", + ), + ) + # obj.Direction = ["CW", "CCW"] - obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill", translate("PathHelix", "Start cutting from the inside or outside")) - obj.StartSide = ['Inside', 'Outside'] + obj.addProperty( + "App::PropertyEnumeration", + "StartSide", + "Helix Drill", + QT_TRANSLATE_NOOP( + "App::Property", "Start cutting from the inside or outside" + ), + ) + # obj.StartSide = ["Inside", "Outside"] - obj.addProperty("App::PropertyLength", "StepOver", "Helix Drill", translate("PathHelix", "Radius increment (must be smaller than tool diameter)")) - obj.addProperty("App::PropertyLength", "StartRadius", "Helix Drill", translate("PathHelix", "Starting Radius")) + obj.addProperty( + "App::PropertyPercent", + "StepOver", + "Helix Drill", + QT_TRANSLATE_NOOP( + "App::Property", "Percent of cutter diameter to step over on each pass" + ), + ) + obj.addProperty( + "App::PropertyLength", + "StartRadius", + "Helix Drill", + QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), + ) + + ENUMS = self.helixOpPropertyEnumerations() + for n in ENUMS: + setattr(obj, n[0], n[1]) def opOnDocumentRestored(self, obj): - if not hasattr(obj, 'StartRadius'): - obj.addProperty("App::PropertyLength", "StartRadius", "Helix Drill", translate("PathHelix", "Starting Radius")) + if not hasattr(obj, "StartRadius"): + obj.addProperty( + "App::PropertyLength", + "StartRadius", + "Helix Drill", + QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), + ) def circularHoleExecute(self, obj, holes): - '''circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes''' + """circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes""" PathLog.track() - self.commandlist.append(Path.Command('(helix cut operation)')) + self.commandlist.append(Path.Command("(helix cut operation)")) - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + self.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) + ) - zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Base) + obj.ClearanceHeight.Value - output = '' + zsafe = ( + max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Base) + + obj.ClearanceHeight.Value + ) + output = "" output += "G0 Z" + fmt(zsafe) - holes = sort_locations(holes, ['x', 'y']) + holes = sort_locations(holes, ["x", "y"]) + + tool = obj.ToolController.Tool + tooldiamter = ( + tool.Diameter.Value if hasattr(tool.Diameter, "Value") else tool.Diameter + ) + + args = { + "edge": None, + "hole_radius": None, + "step_down": obj.StepDown.Value, + "step_over": obj.StepOver / 100, + "tool_diameter": tooldiamter, + "inner_radius": obj.StartRadius.Value, + "direction": obj.Direction, + "startAt": obj.StartSide, + } + for hole in holes: - output += self.helix_cut(obj, hole['x'], hole['y'], hole['r'] / 2, float(obj.StartRadius.Value), (float(obj.StepOver.Value) / 50.0) * self.radius) + args["hole_radius"] = hole["r"] / 2 + startPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.StartDepth.Value) + endPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value) + args["edge"] = Part.makeLine(startPoint, endPoint) + + results = helix_generator.generate(**args) + + for command in results: + self.commandlist.append(command) + + # output += self.helix_cut( + # obj, + # hole["x"], + # hole["y"], + # hole["r"] / 2, + # float(obj.StartRadius.Value), + # (float(obj.StepOver.Value) / 50.0) * self.radius, + # ) PathLog.debug(output) - def helix_cut(self, obj, x0, y0, r_out, r_in, dr): - '''helix_cut(obj, x0, y0, r_out, r_in, dr) ... generate helix commands for specified hole. - x0, y0: coordinates of center - r_out, r_in: outer and inner radius of the hole - dr: step over radius value''' - from numpy import ceil, linspace + # def helix_cut(self, obj, x0, y0, r_out, r_in, dr): + # '''helix_cut(obj, x0, y0, r_out, r_in, dr) ... generate helix commands for specified hole. + # x0, y0: coordinates of center + # r_out, r_in: outer and inner radius of the hole + # dr: step over radius value''' + # from numpy import ceil, linspace - if (obj.StartDepth.Value <= obj.FinalDepth.Value): - return "" + # if (obj.StartDepth.Value <= obj.FinalDepth.Value): + # return "" - out = "(helix_cut <{0}, {1}>, {2})".format( - x0, y0, ", ".join(map(str, (r_out, r_in, dr, obj.StartDepth.Value, - obj.FinalDepth.Value, obj.StepDown.Value, obj.SafeHeight.Value, - self.radius, self.vertFeed, self.horizFeed, obj.Direction, obj.StartSide)))) + # out = "(helix_cut <{0}, {1}>, {2})".format( + # x0, y0, ", ".join(map(str, (r_out, r_in, dr, obj.StartDepth.Value, + # obj.FinalDepth.Value, obj.StepDown.Value, obj.SafeHeight.Value, + # self.radius, self.vertFeed, self.horizFeed, obj.Direction, obj.StartSide)))) - nz = max(int(ceil((obj.StartDepth.Value - obj.FinalDepth.Value) / obj.StepDown.Value)), 2) - zi = linspace(obj.StartDepth.Value, obj.FinalDepth.Value, 2 * nz + 1) + # nz = max(int(ceil((obj.StartDepth.Value - obj.FinalDepth.Value) / obj.StepDown.Value)), 2) + # zi = linspace(obj.StartDepth.Value, obj.FinalDepth.Value, 2 * nz + 1) - def xyz(x=None, y=None, z=None): - out = "" - if x is not None: - out += " X" + fmt(x) - if y is not None: - out += " Y" + fmt(y) - if z is not None: - out += " Z" + fmt(z) - return out + # def xyz(x=None, y=None, z=None): + # out = "" + # if x is not None: + # out += " X" + fmt(x) + # if y is not None: + # out += " Y" + fmt(y) + # if z is not None: + # out += " Z" + fmt(z) + # return out - def rapid(x=None, y=None, z=None): - return "G0" + xyz(x, y, z) + "\n" + # def rapid(x=None, y=None, z=None): + # return "G0" + xyz(x, y, z) + "\n" - def F(f=None): - return (" F" + fmt(f) if f else "") + # def F(f=None): + # return (" F" + fmt(f) if f else "") - def feed(x=None, y=None, z=None, f=None): - return "G1" + xyz(x, y, z) + F(f) + "\n" + # def feed(x=None, y=None, z=None, f=None): + # return "G1" + xyz(x, y, z) + F(f) + "\n" - def arc(x, y, i, j, z, f): - if obj.Direction == "CW": - code = "G2" - elif obj.Direction == "CCW": - code = "G3" - return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n" + # def arc(x, y, i, j, z, f): + # if obj.Direction == "CW": + # code = "G2" + # elif obj.Direction == "CCW": + # code = "G3" + # return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n" - def helix_cut_r(r): - arc_cmd = 'G2' if obj.Direction == 'CW' else 'G3' - out = "" - out += rapid(x=x0 + r, y=y0) - self.commandlist.append(Path.Command('G0', {'X': x0 + r, 'Y': y0, 'F': self.horizRapid})) - out += rapid(z=obj.StartDepth.Value + 2 * self.radius) - self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - out += feed(z=obj.StartDepth.Value, f=self.vertFeed) - self.commandlist.append(Path.Command('G1', {'Z': obj.StartDepth.Value, 'F': self.vertFeed})) - # z = obj.FinalDepth.Value - for i in range(1, nz + 1): - out += arc(x0 - r, y0, i=-r, j=0.0, z=zi[2 * i - 1], f=self.horizFeed) - self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': zi[2 * i - 1], 'I': -r, 'J': 0.0, 'F': self.horizFeed})) - out += arc(x0 + r, y0, i=r, j=0.0, z=zi[2 * i], f=self.horizFeed) - self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': zi[2 * i], 'I': r, 'J': 0.0, 'F': self.horizFeed})) - out += arc(x0 - r, y0, i=-r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) - self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': -r, 'J': 0.0, 'F': self.horizFeed})) - out += arc(x0 + r, y0, i=r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) - self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': r, 'J': 0.0, 'F': self.horizFeed})) - out += feed(z=obj.StartDepth.Value + 2 * self.radius, f=self.vertFeed) - out += rapid(z=obj.SafeHeight.Value) - self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - return out + # def helix_cut_r(r): + # arc_cmd = 'G2' if obj.Direction == 'CW' else 'G3' + # out = "" + # out += rapid(x=x0 + r, y=y0) + # self.commandlist.append(Path.Command('G0', {'X': x0 + r, 'Y': y0, 'F': self.horizRapid})) + # out += rapid(z=obj.StartDepth.Value + 2 * self.radius) + # self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + # out += feed(z=obj.StartDepth.Value, f=self.vertFeed) + # self.commandlist.append(Path.Command('G1', {'Z': obj.StartDepth.Value, 'F': self.vertFeed})) + # # z = obj.FinalDepth.Value + # for i in range(1, nz + 1): + # out += arc(x0 - r, y0, i=-r, j=0.0, z=zi[2 * i - 1], f=self.horizFeed) + # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': zi[2 * i - 1], 'I': -r, 'J': 0.0, 'F': self.horizFeed})) + # out += arc(x0 + r, y0, i=r, j=0.0, z=zi[2 * i], f=self.horizFeed) + # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': zi[2 * i], 'I': r, 'J': 0.0, 'F': self.horizFeed})) + # out += arc(x0 - r, y0, i=-r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) + # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': -r, 'J': 0.0, 'F': self.horizFeed})) + # out += arc(x0 + r, y0, i=r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) + # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': r, 'J': 0.0, 'F': self.horizFeed})) + # out += feed(z=obj.StartDepth.Value + 2 * self.radius, f=self.vertFeed) + # out += rapid(z=obj.SafeHeight.Value) + # self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + # return out - msg = None - if r_out < 0.0: - msg = "r_out < 0" - elif r_in > 0 and r_out - r_in < 2 * self.radius: - msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, 2 * self.radius) - elif r_in == 0.0 and not r_out > self.radius / 2.: - msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - elif obj.StartSide not in ["Inside", "Outside"]: - msg = "Invalid value for parameter 'obj.StartSide'" - elif r_in > 0: - out += "(annulus mode)\n" - r_out = r_out - self.radius - r_in = r_in + self.radius - if abs((r_out - r_in) / dr) < 1e-5: - radii = [(r_out + r_in) / 2] - else: - nr = max(int(ceil((r_out - r_in) / dr)), 2) - radii = linspace(r_out, r_in, nr) - elif r_out <= 2 * dr: - out += "(single helix mode)\n" - radii = [r_out - self.radius] - if radii[0] <= 0: - msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - else: - out += "(full hole mode)\n" - r_out = r_out - self.radius - r_in = dr / 2 + # msg = None + # if r_out < 0.0: + # msg = "r_out < 0" + # elif r_in > 0 and r_out - r_in < 2 * self.radius: + # msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, 2 * self.radius) + # elif r_in == 0.0 and not r_out > self.radius / 2.: + # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) + # elif obj.StartSide not in ["Inside", "Outside"]: + # msg = "Invalid value for parameter 'obj.StartSide'" + # elif r_in > 0: + # out += "(annulus mode)\n" + # r_out = r_out - self.radius + # r_in = r_in + self.radius + # if abs((r_out - r_in) / dr) < 1e-5: + # radii = [(r_out + r_in) / 2] + # else: + # nr = max(int(ceil((r_out - r_in) / dr)), 2) + # radii = linspace(r_out, r_in, nr) + # elif r_out <= 2 * dr: + # out += "(single helix mode)\n" + # radii = [r_out - self.radius] + # if radii[0] <= 0: + # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) + # else: + # out += "(full hole mode)\n" + # r_out = r_out - self.radius + # r_in = dr / 2 - nr = max(1 + int(ceil((r_out - r_in) / dr)), 2) - radii = [r for r in linspace(r_out, r_in, nr) if r > 0] - if not radii: - msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) + # nr = max(1 + int(ceil((r_out - r_in) / dr)), 2) + # radii = [r for r in linspace(r_out, r_in, nr) if r > 0] + # if not radii: + # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - if msg: - out += "(ERROR: Hole at {0}: ".format((x0, y0, obj.StartDepth.Value)) + msg + ")\n" - PathLog.error("{0} - ".format((x0, y0, obj.StartDepth.Value)) + msg) - return out + # if msg: + # out += "(ERROR: Hole at {0}: ".format((x0, y0, obj.StartDepth.Value)) + msg + ")\n" + # PathLog.error("{0} - ".format((x0, y0, obj.StartDepth.Value)) + msg) + # return out - if obj.StartSide == "Inside": - radii = radii[::-1] + # if obj.StartSide == "Inside": + # radii = radii[::-1] - for r in radii: - out += "(radius {0})\n".format(r) - out += helix_cut_r(r) + # for r in radii: + # out += "(radius {0})\n".format(r) + # out += helix_cut_r(r) - return out + # return out - def opSetDefaultValues(self, obj, job): - obj.Direction = "CW" - obj.StartSide = "Inside" - obj.StepOver = 100 + # def opSetDefaultValues(self, obj, job): + # obj.Direction = "CW" + # obj.StartSide = "Inside" + # obj.StepOver = 100 def SetupProperties(): @@ -215,7 +334,7 @@ def SetupProperties(): def Create(name, obj=None, parentJob=None): - '''Create(name) ... Creates and returns a Helix operation.''' + """Create(name) ... Creates and returns a Helix operation.""" if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectHelix(obj, name, parentJob) diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index b65048ee9c..c4e0bdff0f 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -27,6 +27,9 @@ import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui +from PySide.QtCore import QT_TRANSLATE_NOOP + +translate = FreeCAD.Qt.translate from PySide import QtCore @@ -46,7 +49,29 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def getForm(self): '''getForm() ... return UI''' - return FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui") + + form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui") + comboToPropertyMap = [("startSide", "StartSide"), ("direction", "Direction")] + + enumTups = PathHelix.ObjectHelix.helixOpPropertyEnumerations(dataType="raw") + + self.populateCombobox(form, enumTups, comboToPropertyMap) + return form + + def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): + """fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations + ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. + Args: + form = UI form + enumTups = list of (translated_text, data_string) tuples + comboBoxesPropertyMap = list of (translated_text, data_string) tuples + """ + # Load appropriate enumerations in each combobox + for cb, prop in comboBoxesPropertyMap: + box = getattr(form, cb) # Get the combobox + box.clear() # clear the combobox + for text, data in enumTups[prop]: # load enumerations + box.addItem(text, data) def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' @@ -88,8 +113,8 @@ Command = PathOpGui.SetupOperation('Helix', PathHelix.Create, TaskPanelOpPage, 'Path_Helix', - QtCore.QT_TRANSLATE_NOOP("Path_Helix", "Helix"), - QtCore.QT_TRANSLATE_NOOP("Path_Helix", "Creates a Path Helix object from a features of a base object"), + QT_TRANSLATE_NOOP("Path_Helix", "Helix"), + QT_TRANSLATE_NOOP("Path_Helix", "Creates a Path Helix object from a features of a base object"), PathHelix.SetupProperties) FreeCAD.Console.PrintLog("Loading PathHelixGui... done\n") From a6c4dfa66d8ad35e00648b1a242ae7fedecf1dce Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 17:21:17 -0600 Subject: [PATCH 2/8] Helix refactor for generator --- src/Mod/Path/Generators/helix_generator.py | 30 ++++++++++------ .../Path/PathTests/TestPathHelixGenerator.py | 36 +++++++++---------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Mod/Path/Generators/helix_generator.py b/src/Mod/Path/Generators/helix_generator.py index 2d4f2cf29e..1d83095025 100644 --- a/src/Mod/Path/Generators/helix_generator.py +++ b/src/Mod/Path/Generators/helix_generator.py @@ -51,7 +51,7 @@ def generate( ): """generate(edge, hole_radius, inner_radius, step_over) ... generate helix commands. hole_radius, inner_radius: outer and inner radius of the hole - step_over: step over radius value""" + step_over: step over % of tool diameter""" startPoint = edge.Vertexes[0].Point endPoint = edge.Vertexes[1].Point @@ -73,16 +73,16 @@ def generate( ) if type(hole_radius) not in [float, int]: - raise ValueError("hole_radius must be a float") + raise TypeError("Invalid type for hole radius") if hole_radius < 0.0: raise ValueError("hole_radius < 0") if type(inner_radius) not in [float, int]: - raise ValueError("inner_radius must be a float") + raise TypeError("inner_radius must be a float") if type(tool_diameter) not in [float, int]: - raise ValueError("tool_diameter must be a float") + raise TypeError("tool_diameter must be a float") if inner_radius > 0 and hole_radius - inner_radius < tool_diameter: raise ValueError( @@ -104,6 +104,13 @@ def generate( elif direction not in ["CW", "CCW"]: raise ValueError("Invalid value for parameter 'direction'") + if type(step_over) not in [float, int]: + raise TypeError("Invalid value for parameter 'step_over'") + + if step_over <= 0 or step_over > 1: + raise ValueError("Invalid value for parameter 'step_over'") + step_over_distance = step_over * tool_diameter + if not ( isclose(startPoint.sub(endPoint).x, 0, rtol=1e-05, atol=1e-06) and (isclose(startPoint.sub(endPoint).y, 0, rtol=1e-05, atol=1e-06)) @@ -117,13 +124,13 @@ def generate( PathLog.debug("(annulus mode)\n") outer_radius = hole_radius - tool_diameter / 2 step_radius = inner_radius + tool_diameter / 2 - if abs((outer_radius - step_radius) / step_over) < 1e-5: - radii = [(outer_radius + step_radius) / 2] + if abs((outer_radius - step_radius) / step_over_distance) < 1e-5: + radii = [(outer_radius + inner_radius) / 2] else: - nr = max(int(ceil((outer_radius - step_radius) / step_over)), 2) + nr = max(int(ceil((outer_radius - inner_radius) / step_over_distance)), 2) radii = linspace(outer_radius, step_radius, nr) - elif hole_radius <= 2 * step_over: + elif hole_radius <= 2 * tool_diameter: PathLog.debug("(single helix mode)\n") radii = [hole_radius - tool_diameter / 2] if radii[0] <= 0: @@ -136,16 +143,17 @@ def generate( else: PathLog.debug("(full hole mode)\n") outer_radius = hole_radius - tool_diameter / 2 - step_radius = step_over / 2 - nr = max(1 + int(ceil((outer_radius - step_radius) / step_over)), 2) - radii = [r for r in linspace(outer_radius, step_radius, nr) if r > 0] + nr = max(1 + int(ceil((outer_radius - inner_radius) / step_over_distance)), 2) + PathLog.debug("nr: {}".format(nr)) + radii = [r for r in linspace(outer_radius, inner_radius, nr) if r > 0] if not radii: raise ValueError( "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format( 2 * hole_radius, tool_diameter ) ) + PathLog.debug("Radii: {}".format(radii)) # calculate the number of full and partial turns required # Each full turn is two 180 degree arcs. Zsteps is equally spaced step # down values diff --git a/src/Mod/Path/PathTests/TestPathHelixGenerator.py b/src/Mod/Path/PathTests/TestPathHelixGenerator.py index ea2b477823..d3707f9236 100644 --- a/src/Mod/Path/PathTests/TestPathHelixGenerator.py +++ b/src/Mod/Path/PathTests/TestPathHelixGenerator.py @@ -42,7 +42,7 @@ def _resetArgs(): "edge": edg, "hole_radius": 10.0, "step_down": 1.0, - "step_over": 5.0, + "step_over": 0.5, "tool_diameter": 5.0, "inner_radius": 0.0, "direction": "CW", @@ -62,7 +62,6 @@ G2 I7.500000 J0.000000 X12.500000 Y5.000000 Z18.000000\ G0 X5.000000 Y5.000000 Z18.000000\ G0 Z20.000000" - def test00(self): """Test Basic Helix Generator Return""" args = _resetArgs() @@ -71,34 +70,27 @@ G0 Z20.000000" self.assertTrue(type(result[0]) is Path.Command) gcode = "".join([r.toGCode() for r in result]) - print(gcode) self.assertTrue( gcode == self.expectedHelixGCode, "Incorrect helix g-code generated" ) def test01(self): - """Test Basic Helix Generator hole_radius is float > 0""" + """Test Value and Type checking""" args = _resetArgs() - args["hole_radius"] = '10' - self.assertRaises(ValueError, generator.generate, **args) + args["hole_radius"] = "10" + self.assertRaises(TypeError, generator.generate, **args) args["hole_radius"] = -10.0 self.assertRaises(ValueError, generator.generate, **args) - def test02(self): - """Test Basic Helix Generator inner_radius is float""" args = _resetArgs() - args["inner_radius"] = '2' - self.assertRaises(ValueError, generator.generate, **args) + args["inner_radius"] = "2" + self.assertRaises(TypeError, generator.generate, **args) - def test03(self): - """Test Basic Helix Generator tool_diameter is float""" args = _resetArgs() - args["tool_diameter"] = '5' - self.assertRaises(ValueError, generator.generate, **args) + args["tool_diameter"] = "5" + self.assertRaises(TypeError, generator.generate, **args) - def test04(self): - """Test Basic Helix Generator tool fit with radius difference less than tool diameter""" args = _resetArgs() # require tool fit 1: radius diff less than tool diam args["hole_radius"] = 10.0 @@ -112,14 +104,18 @@ G0 Z20.000000" args["tool_diameter"] = 5.0 self.assertRaises(ValueError, generator.generate, **args) - def test05(self): - """Test Basic Helix Generator validate the startAt enumeration value""" + # step_over is a percent value between 0 and 1 + args = _resetArgs() + args["step_over"] = 50 + self.assertRaises(ValueError, generator.generate, **args) + args["step_over"] = "50" + self.assertRaises(TypeError, generator.generate, **args) + + # Other argument testing args = _resetArgs() args["startAt"] = "Other" self.assertRaises(ValueError, generator.generate, **args) - def test06(self): - """Test Basic Helix Generator validate the direction enumeration value""" args = _resetArgs() args["direction"] = "clock" self.assertRaises(ValueError, generator.generate, **args) From 6bdb86fcef597ee4d347d985aaf392bb50829874 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 17:26:42 -0600 Subject: [PATCH 3/8] black reformat --- src/Mod/Path/PathScripts/PathHelixGui.py | 31 ++++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index c4e0bdff0f..a3973d37d2 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -22,7 +22,7 @@ import FreeCAD import FreeCADGui -import PathGui as PGui # ensure Path/Gui/Resources are loaded +import PathGui as PGui # ensure Path/Gui/Resources are loaded import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog @@ -45,10 +45,10 @@ else: class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): - '''Page controller class for Helix operations.''' + """Page controller class for Helix operations.""" def getForm(self): - '''getForm() ... return UI''' + """getForm() ... return UI""" form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui") comboToPropertyMap = [("startSide", "StartSide"), ("direction", "Direction")] @@ -74,7 +74,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): box.addItem(text, data) def getFields(self, obj): - '''getFields(obj) ... transfers values from UI to obj's proprties''' + """getFields(obj) ... transfers values from UI to obj's proprties""" PathLog.track() if obj.Direction != str(self.form.direction.currentText()): obj.Direction = str(self.form.direction.currentText()) @@ -87,7 +87,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.updateCoolant(obj, self.form.coolantController) def setFields(self, obj): - '''setFields(obj) ... transfers obj's property values to UI''' + """setFields(obj) ... transfers obj's property values to UI""" PathLog.track() self.form.stepOverPercent.setValue(obj.StepOver) @@ -98,7 +98,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupCoolant(obj, self.form.coolantController) def getSignalsForUpdate(self, obj): - '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] signals.append(self.form.stepOverPercent.editingFinished) @@ -109,12 +109,17 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): return signals -Command = PathOpGui.SetupOperation('Helix', - PathHelix.Create, - TaskPanelOpPage, - 'Path_Helix', - QT_TRANSLATE_NOOP("Path_Helix", "Helix"), - QT_TRANSLATE_NOOP("Path_Helix", "Creates a Path Helix object from a features of a base object"), - PathHelix.SetupProperties) + +Command = PathOpGui.SetupOperation( + "Helix", + PathHelix.Create, + TaskPanelOpPage, + "Path_Helix", + QT_TRANSLATE_NOOP("Path_Helix", "Helix"), + QT_TRANSLATE_NOOP( + "Path_Helix", "Creates a Path Helix object from a features of a base object" + ), + PathHelix.SetupProperties, +) FreeCAD.Console.PrintLog("Loading PathHelixGui... done\n") From 5be4570d1262595d04e43d7a33fed6f0b0e9fbca Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 17:38:52 -0600 Subject: [PATCH 4/8] add a material allowance property --- .../Gui/Resources/panels/PageOpHelixEdit.ui | 55 +++++++++++++------ src/Mod/Path/PathScripts/PathHelix.py | 23 +++++++- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpHelixEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpHelixEdit.ui index 5658602b78..52e5468f8f 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpHelixEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpHelixEdit.ui @@ -14,6 +14,19 @@ Form + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -81,14 +94,14 @@ - + Direction - + <html><head/><body><p>The direction for the helix, clockwise or counter clockwise.</p></body></html> @@ -105,14 +118,14 @@ - + Step over percent - + <html><head/><body><p>Specify the percent of the tool diameter each helix will be offset to the previous one.</p><p><br/></p><p>A step over of 100% means no overlap of the individual cuts.</p></body></html> @@ -131,24 +144,32 @@ + + + + Extra Offset + + + + + + + + + + - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 644570f8ad..c8efaaa6b2 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -134,6 +134,15 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): "Helix Drill", QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), ) + obj.addProperty( + "App::PropertyDistance", + "OffsetExtra", + "Helix Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Extra value to stay away from final profile- good for roughing toolpath", + ), + ) ENUMS = self.helixOpPropertyEnumerations() for n in ENUMS: @@ -148,6 +157,16 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): QT_TRANSLATE_NOOP("App::Property", "Starting Radius"), ) + if not hasattr(obj, "OffsetExtra"): + obj.addProperty( + "App::PropertyDistance", + "OffsetExtra", + "Helix Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Extra value to stay away from final profile- good for roughing toolpath", + ), + ) def circularHoleExecute(self, obj, holes): """circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes""" PathLog.track() @@ -177,13 +196,13 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): "step_down": obj.StepDown.Value, "step_over": obj.StepOver / 100, "tool_diameter": tooldiamter, - "inner_radius": obj.StartRadius.Value, + "inner_radius": obj.StartRadius.Value + obj.OffsetExtra.Value, "direction": obj.Direction, "startAt": obj.StartSide, } for hole in holes: - args["hole_radius"] = hole["r"] / 2 + args["hole_radius"] = (hole["r"] / 2) - (obj.OffsetExtra.Value) startPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.StartDepth.Value) endPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value) args["edge"] = Part.makeLine(startPoint, endPoint) From 7f79b8cd133c1a5e8a79f4e63609735547ae435a Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 17:41:15 -0600 Subject: [PATCH 5/8] remove deprecated code --- src/Mod/Path/PathScripts/PathHelix.py | 132 +------------------------- 1 file changed, 1 insertion(+), 131 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index c8efaaa6b2..b2d014be52 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -85,7 +85,6 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): PathLog.debug(enums) for k, v in enumerate(enums): - # data[k] = [tup[idx] for tup in v] data.append((v, [tup[idx] for tup in enums[v]])) PathLog.debug(data) @@ -108,7 +107,6 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): "The direction of the circular cuts, ClockWise (CW), or CounterClockWise (CCW)", ), ) - # obj.Direction = ["CW", "CCW"] obj.addProperty( "App::PropertyEnumeration", @@ -118,7 +116,6 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): "App::Property", "Start cutting from the inside or outside" ), ) - # obj.StartSide = ["Inside", "Outside"] obj.addProperty( "App::PropertyPercent", @@ -167,6 +164,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): "Extra value to stay away from final profile- good for roughing toolpath", ), ) + def circularHoleExecute(self, obj, holes): """circularHoleExecute(obj, holes) ... generate helix commands for each hole in holes""" PathLog.track() @@ -212,136 +210,8 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): for command in results: self.commandlist.append(command) - # output += self.helix_cut( - # obj, - # hole["x"], - # hole["y"], - # hole["r"] / 2, - # float(obj.StartRadius.Value), - # (float(obj.StepOver.Value) / 50.0) * self.radius, - # ) PathLog.debug(output) - # def helix_cut(self, obj, x0, y0, r_out, r_in, dr): - # '''helix_cut(obj, x0, y0, r_out, r_in, dr) ... generate helix commands for specified hole. - # x0, y0: coordinates of center - # r_out, r_in: outer and inner radius of the hole - # dr: step over radius value''' - # from numpy import ceil, linspace - - # if (obj.StartDepth.Value <= obj.FinalDepth.Value): - # return "" - - # out = "(helix_cut <{0}, {1}>, {2})".format( - # x0, y0, ", ".join(map(str, (r_out, r_in, dr, obj.StartDepth.Value, - # obj.FinalDepth.Value, obj.StepDown.Value, obj.SafeHeight.Value, - # self.radius, self.vertFeed, self.horizFeed, obj.Direction, obj.StartSide)))) - - # nz = max(int(ceil((obj.StartDepth.Value - obj.FinalDepth.Value) / obj.StepDown.Value)), 2) - # zi = linspace(obj.StartDepth.Value, obj.FinalDepth.Value, 2 * nz + 1) - - # def xyz(x=None, y=None, z=None): - # out = "" - # if x is not None: - # out += " X" + fmt(x) - # if y is not None: - # out += " Y" + fmt(y) - # if z is not None: - # out += " Z" + fmt(z) - # return out - - # def rapid(x=None, y=None, z=None): - # return "G0" + xyz(x, y, z) + "\n" - - # def F(f=None): - # return (" F" + fmt(f) if f else "") - - # def feed(x=None, y=None, z=None, f=None): - # return "G1" + xyz(x, y, z) + F(f) + "\n" - - # def arc(x, y, i, j, z, f): - # if obj.Direction == "CW": - # code = "G2" - # elif obj.Direction == "CCW": - # code = "G3" - # return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n" - - # def helix_cut_r(r): - # arc_cmd = 'G2' if obj.Direction == 'CW' else 'G3' - # out = "" - # out += rapid(x=x0 + r, y=y0) - # self.commandlist.append(Path.Command('G0', {'X': x0 + r, 'Y': y0, 'F': self.horizRapid})) - # out += rapid(z=obj.StartDepth.Value + 2 * self.radius) - # self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - # out += feed(z=obj.StartDepth.Value, f=self.vertFeed) - # self.commandlist.append(Path.Command('G1', {'Z': obj.StartDepth.Value, 'F': self.vertFeed})) - # # z = obj.FinalDepth.Value - # for i in range(1, nz + 1): - # out += arc(x0 - r, y0, i=-r, j=0.0, z=zi[2 * i - 1], f=self.horizFeed) - # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': zi[2 * i - 1], 'I': -r, 'J': 0.0, 'F': self.horizFeed})) - # out += arc(x0 + r, y0, i=r, j=0.0, z=zi[2 * i], f=self.horizFeed) - # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': zi[2 * i], 'I': r, 'J': 0.0, 'F': self.horizFeed})) - # out += arc(x0 - r, y0, i=-r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) - # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 - r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': -r, 'J': 0.0, 'F': self.horizFeed})) - # out += arc(x0 + r, y0, i=r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed) - # self.commandlist.append(Path.Command(arc_cmd, {'X': x0 + r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': r, 'J': 0.0, 'F': self.horizFeed})) - # out += feed(z=obj.StartDepth.Value + 2 * self.radius, f=self.vertFeed) - # out += rapid(z=obj.SafeHeight.Value) - # self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - # return out - - # msg = None - # if r_out < 0.0: - # msg = "r_out < 0" - # elif r_in > 0 and r_out - r_in < 2 * self.radius: - # msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, 2 * self.radius) - # elif r_in == 0.0 and not r_out > self.radius / 2.: - # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - # elif obj.StartSide not in ["Inside", "Outside"]: - # msg = "Invalid value for parameter 'obj.StartSide'" - # elif r_in > 0: - # out += "(annulus mode)\n" - # r_out = r_out - self.radius - # r_in = r_in + self.radius - # if abs((r_out - r_in) / dr) < 1e-5: - # radii = [(r_out + r_in) / 2] - # else: - # nr = max(int(ceil((r_out - r_in) / dr)), 2) - # radii = linspace(r_out, r_in, nr) - # elif r_out <= 2 * dr: - # out += "(single helix mode)\n" - # radii = [r_out - self.radius] - # if radii[0] <= 0: - # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - # else: - # out += "(full hole mode)\n" - # r_out = r_out - self.radius - # r_in = dr / 2 - - # nr = max(1 + int(ceil((r_out - r_in) / dr)), 2) - # radii = [r for r in linspace(r_out, r_in, nr) if r > 0] - # if not radii: - # msg = "Cannot helix a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2 * self.radius) - - # if msg: - # out += "(ERROR: Hole at {0}: ".format((x0, y0, obj.StartDepth.Value)) + msg + ")\n" - # PathLog.error("{0} - ".format((x0, y0, obj.StartDepth.Value)) + msg) - # return out - - # if obj.StartSide == "Inside": - # radii = radii[::-1] - - # for r in radii: - # out += "(radius {0})\n".format(r) - # out += helix_cut_r(r) - - # return out - - # def opSetDefaultValues(self, obj, job): - # obj.Direction = "CW" - # obj.StartSide = "Inside" - # obj.StepOver = 100 - def SetupProperties(): setup = [] From f99639f2fe7591abbc3b9b6a264ebe01bed636e4 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 20 Jan 2022 18:24:56 -0600 Subject: [PATCH 6/8] Tweaks --- src/Mod/Path/PathFeedRate.py | 2 +- src/Mod/Path/PathScripts/PathHelix.py | 35 ++++++++++++++++-------- src/Mod/Path/PathScripts/PathHelixGui.py | 12 +++++--- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Mod/Path/PathFeedRate.py b/src/Mod/Path/PathFeedRate.py index 4d99bb5fde..a64f084209 100644 --- a/src/Mod/Path/PathFeedRate.py +++ b/src/Mod/Path/PathFeedRate.py @@ -36,7 +36,7 @@ __doc__ = "Helper for adding Feed Rate to Path Commands" TODO: This needs to be able to handle feedrates for axes other than X,Y,Z """ -if True: +if False: PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) else: diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index b2d014be52..bda1fa1fb0 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -30,6 +30,8 @@ import Path import PathScripts.PathCircularHoleBase as PathCircularHoleBase import PathScripts.PathLog as PathLog import PathScripts.PathOp as PathOp +import PathFeedRate + __title__ = "Path Helix Drill Operation" __author__ = "Lorenz Hüdepohl" @@ -170,16 +172,7 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): PathLog.track() self.commandlist.append(Path.Command("(helix cut operation)")) - self.commandlist.append( - Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) - ) - - zsafe = ( - max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Base) - + obj.ClearanceHeight.Value - ) - output = "" - output += "G0 Z" + fmt(zsafe) + self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) holes = sort_locations(holes, ["x", "y"]) @@ -205,12 +198,32 @@ class ObjectHelix(PathCircularHoleBase.ObjectOp): endPoint = FreeCAD.Vector(hole["x"], hole["y"], obj.FinalDepth.Value) args["edge"] = Part.makeLine(startPoint, endPoint) + # move to starting postion + self.commandlist.append( + Path.Command("G0", {"Z": obj.ClearanceHeight.Value}) + ) + self.commandlist.append( + Path.Command( + "G0", + { + "X": startPoint.x, + "Y": startPoint.y, + "Z": obj.ClearanceHeight.Value, + }, + ) + ) + self.commandlist.append( + Path.Command( + "G0", {"X": startPoint.x, "Y": startPoint.y, "Z": startPoint.z} + ) + ) + results = helix_generator.generate(**args) for command in results: self.commandlist.append(command) - PathLog.debug(output) + PathFeedRate.setFeedRate(self.commandlist, obj.ToolController) def SetupProperties(): diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index a3973d37d2..fa1648ba9f 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -27,6 +27,7 @@ import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui import PathScripts.PathHelix as PathHelix import PathScripts.PathLog as PathLog import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathGui as PathGui from PySide.QtCore import QT_TRANSLATE_NOOP translate = FreeCAD.Qt.translate @@ -76,12 +77,13 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's proprties""" PathLog.track() - if obj.Direction != str(self.form.direction.currentText()): - obj.Direction = str(self.form.direction.currentText()) - if obj.StartSide != str(self.form.startSide.currentText()): - obj.StartSide = str(self.form.startSide.currentText()) + if obj.Direction != str(self.form.direction.currentData()): + obj.Direction = str(self.form.direction.currentData()) + if obj.StartSide != str(self.form.startSide.currentData()): + obj.StartSide = str(self.form.startSide.currentData()) if obj.StepOver != self.form.stepOverPercent.value(): obj.StepOver = self.form.stepOverPercent.value() + PathGui.updateInputField(obj, "OffsetExtra", self.form.extraOffset) self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) @@ -97,6 +99,8 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) + self.form.extraOffset.setText(FreeCAD.Units.Quantity(obj.OffsetExtra.Value, FreeCAD.Units.Length).UserString) + def getSignalsForUpdate(self, obj): """getSignalsForUpdate(obj) ... return list of signals for updating obj""" signals = [] From 73b3afe05dcd9a1fc67ec9f62231cc1cbe0d9fab Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 21 Jan 2022 09:20:57 -0600 Subject: [PATCH 7/8] translation cleanup --- src/Mod/Path/PathScripts/PathGui.py | 68 +++++++++++++++-------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 1d0d58f358..3cdb2fbf64 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -24,7 +24,6 @@ import FreeCAD import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil -import PySide __title__ = "Path UI helper and utility functions" @@ -32,32 +31,33 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecadweb.org" __doc__ = "A collection of helper and utility functions for the Path GUI." -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# PathLog.trackModule(PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) def updateInputField(obj, prop, widget, onBeforeChange=None): - '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. + """updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. The property's value is only assigned if the new value differs from the current value. This prevents onChanged notifications where the value didn't actually change. Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can be of type Quantity or Float. If onBeforeChange is specified it is called before a new value is assigned to the property. Returns True if a new value was assigned, False otherwise (new value is the same as the current). - ''' + """ PathLog.track() - value = widget.property('rawValue') + value = widget.property("rawValue") attr = PathUtil.getProperty(obj, prop) - attrValue = attr.Value if hasattr(attr, 'Value') else attr + attrValue = attr.Value if hasattr(attr, "Value") else attr isDiff = False if not PathGeom.isRoughly(attrValue, value): isDiff = True else: - if hasattr(obj, 'ExpressionEngine'): + if hasattr(obj, "ExpressionEngine"): noExpr = True for (prp, expr) in obj.ExpressionEngine: if prp == prop: @@ -76,7 +76,9 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): widget.update() if isDiff: - PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) + PathLog.debug( + "updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value) + ) if onBeforeChange: onBeforeChange(obj) PathUtil.setProperty(obj, prop, value) @@ -86,14 +88,14 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): class QuantitySpinBox: - '''Controller class to interface a Gui::QuantitySpinBox. + """Controller class to interface a Gui::QuantitySpinBox. The spin box gets bound to a given property and supports update in both directions. QuatitySpinBox(widget, obj, prop, onBeforeChange=None) widget ... expected to be reference to a Gui::QuantitySpinBox obj ... document object prop ... canonical name of the (sub-) property onBeforeChange ... an optional callback being executed before the value of the property is changed - ''' + """ def __init__(self, widget, obj, prop, onBeforeChange=None): PathLog.track(widget) @@ -103,59 +105,61 @@ class QuantitySpinBox: self.obj = obj self.attachTo(obj, prop) - def attachTo(self, obj, prop = None): - '''attachTo(obj, prop=None) ... use an existing editor for the given object and property''' + def attachTo(self, obj, prop=None): + """attachTo(obj, prop=None) ... use an existing editor for the given object and property""" PathLog.track(self.prop, prop) self.obj = obj self.prop = prop if obj and prop: attr = PathUtil.getProperty(obj, prop) if attr is not None: - if hasattr(attr, 'Value'): - self.widget.setProperty('unit', attr.getUserPreferred()[2]) - self.widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + if hasattr(attr, "Value"): + self.widget.setProperty("unit", attr.getUserPreferred()[2]) + self.widget.setProperty("binding", "%s.%s" % (obj.Name, prop)) self.valid = True else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + PathLog.warning("Cannot find property {} of {}".format(prop, obj.Label)) self.valid = False else: self.valid = False def expression(self): - '''expression() ... returns the expression if one is bound to the property''' + """expression() ... returns the expression if one is bound to the property""" PathLog.track(self.prop, self.valid) if self.valid: - return self.widget.property('expression') - return '' + return self.widget.property("expression") + return "" def setMinimum(self, quantity): - '''setMinimum(quantity) ... set the minimum''' + """setMinimum(quantity) ... set the minimum""" PathLog.track(self.prop, self.valid) if self.valid: - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('setMinimum', value) + value = quantity.Value if hasattr(quantity, "Value") else quantity + self.widget.setProperty("setMinimum", value) def updateSpinBox(self, quantity=None): - '''updateSpinBox(quantity=None) ... update the display value of the spin box. + """updateSpinBox(quantity=None) ... update the display value of the spin box. If no value is provided the value of the bound property is used. - quantity can be of type Quantity or Float.''' + quantity can be of type Quantity or Float.""" PathLog.track(self.prop, self.valid) if self.valid: - expr = self._hasExpression() + expr = self._hasExpression() if quantity is None: if expr: quantity = FreeCAD.Units.Quantity(self.obj.evalExpression(expr)) else: quantity = PathUtil.getProperty(self.obj, self.prop) - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('rawValue', value) + value = quantity.Value if hasattr(quantity, "Value") else quantity + self.widget.setProperty("rawValue", value) def updateProperty(self): - '''updateProperty() ... update the bound property with the value from the spin box''' + """updateProperty() ... update the bound property with the value from the spin box""" PathLog.track(self.prop, self.valid) if self.valid: - return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) + return updateInputField( + self.obj, self.prop, self.widget, self.onBeforeChange + ) return None def _hasExpression(self): From 8223aff5a2761f302a7d7a3af3470c7b28e054f4 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 21 Jan 2022 09:21:11 -0600 Subject: [PATCH 8/8] fix bug with extraoffset not stored --- src/Mod/Path/PathScripts/PathHelixGui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Path/PathScripts/PathHelixGui.py b/src/Mod/Path/PathScripts/PathHelixGui.py index fa1648ba9f..f96b839b2a 100644 --- a/src/Mod/Path/PathScripts/PathHelixGui.py +++ b/src/Mod/Path/PathScripts/PathHelixGui.py @@ -106,6 +106,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): signals = [] signals.append(self.form.stepOverPercent.editingFinished) + signals.append(self.form.extraOffset.editingFinished) signals.append(self.form.direction.currentIndexChanged) signals.append(self.form.startSide.currentIndexChanged) signals.append(self.form.toolController.currentIndexChanged)