From 383ab6ed9593ddd4a971a3585344c0ae65303d8b Mon Sep 17 00:00:00 2001 From: "Morgan 'ARR\\!' Allen" Date: Wed, 15 Mar 2023 00:58:42 -0700 Subject: [PATCH 1/4] WIP add File source to CustomOp original functionality is the default behavior under Text Source --- src/Mod/Path/Path/Op/Custom.py | 60 +++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/Path/Op/Custom.py b/src/Mod/Path/Path/Op/Custom.py index 2d24957329..0881eb3d46 100644 --- a/src/Mod/Path/Path/Op/Custom.py +++ b/src/Mod/Path/Path/Op/Custom.py @@ -21,6 +21,8 @@ # *************************************************************************** import FreeCAD +import FreeCADGui +import os import Path import Path.Op.Base as PathOp @@ -47,6 +49,20 @@ class ObjectCustom(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureCoolant def initOperation(self, obj): + obj.addProperty( + "App::PropertyEnumeration", + "Source", + "Path", + "Source of gcode (text, file, ...)" + ) + + obj.addProperty( + "App::PropertyFile", + "GcodeFile", + "Path", + "File containing gcode to be inserted", + ) + obj.addProperty( "App::PropertyStringList", "Gcode", @@ -54,14 +70,56 @@ class ObjectCustom(PathOp.ObjectOp): QT_TRANSLATE_NOOP("App::Property", "The gcode to be inserted"), ) + obj.Source = [ "Text", "File" ] obj.Proxy = self + self.setEditorModes(obj) + + def onChanged(self, obj, prop): + if prop == "Source": + self.setEditorModes(obj) + + def onDocumentRestore(self, obj): + self.setEditorModes(self, obj) + + def setEditorModes(self, obj, features=None): + if not hasattr(obj, "Source"): + return + + if obj.Source == "Text": + obj.setEditorMode("GcodeFile", 2) + obj.setEditorMode("Gcode", 0) + elif obj.Source == "File": + obj.setEditorMode("GcodeFile", 0) + obj.setEditorMode("Gcode", 2) + + def findGcodeFile(self, filename): + if os.path.exists(filename): + # probably absolute, just return + return filename + + doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) + prospective_path = os.path.join(doc_path, filename) + + if os.path.exists(prospective_path): + return prospective_path def opExecute(self, obj): self.commandlist.append(Path.Command("(Begin Custom)")) - if obj.Gcode: + + if obj.Source == "Text" and obj.Gcode: for l in obj.Gcode: newcommand = Path.Command(str(l)) self.commandlist.append(newcommand) + elif obj.Source == "File" and len(obj.GcodeFile) > 0: + gcode_file = self.findGcodeFile(obj.GcodeFile) + + # could not determine the path + if not gcode_file: return + + with open(gcode_file) as fd: + for l in fd.readlines(): + newcommand = Path.Command(str(l)) + self.commandlist.append(newcommand) self.commandlist.append(Path.Command("(End Custom)")) From 2d43df19a347f295ce1b79293c633942eb45e335 Mon Sep 17 00:00:00 2001 From: "Morgan 'ARR\\!' Allen" Date: Wed, 29 Mar 2023 21:22:47 -0700 Subject: [PATCH 2/4] return useful error message if file is not found --- src/Mod/Path/Path/Op/Custom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/Path/Op/Custom.py b/src/Mod/Path/Path/Op/Custom.py index 0881eb3d46..0e8813dd52 100644 --- a/src/Mod/Path/Path/Op/Custom.py +++ b/src/Mod/Path/Path/Op/Custom.py @@ -114,7 +114,8 @@ class ObjectCustom(PathOp.ObjectOp): gcode_file = self.findGcodeFile(obj.GcodeFile) # could not determine the path - if not gcode_file: return + if not gcode_file: + Path.Log.error(translate("PathCustom", "Custom file %s could not be found.") % obj.GcodeFile) with open(gcode_file) as fd: for l in fd.readlines(): From c330c31b6a832c3f6ce3484d5206893725f355dd Mon Sep 17 00:00:00 2001 From: "Morgan 'ARR\\!' Allen" Date: Thu, 29 Jun 2023 23:01:57 -0700 Subject: [PATCH 3/4] Migrate to using opPropertyEnumerations to set defaults This feels a bit heavy but I still feel like it's helpful to inform the path forward to consolidate these default properties. --- src/Mod/Path/Path/Op/Custom.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/Path/Op/Custom.py b/src/Mod/Path/Path/Op/Custom.py index 3382e64268..beb4a5c9ac 100644 --- a/src/Mod/Path/Path/Op/Custom.py +++ b/src/Mod/Path/Path/Op/Custom.py @@ -70,10 +70,25 @@ class ObjectCustom(PathOp.ObjectOp): QT_TRANSLATE_NOOP("App::Property", "The G-code to be inserted"), ) - obj.Source = [ "Text", "File" ] obj.Proxy = self self.setEditorModes(obj) + def opPropertyEnumerations(self, dateType="data"): + enum = super().opPropertyEnumerations(dateType) + + props = [ + (translate("PathCustom", "Text"), "Text"), + (translate("PathCustom", "File"), "File") + ] + + if dateType == "raw": + enum["Source"] = props + return enum + + enum.append(("Source", [ p[1][1] for p in enumerate(props) ])) + + return enum + def onChanged(self, obj, prop): if prop == "Source": self.setEditorModes(obj) From 5f18095667429cf7eedd703173da7234fa75ff8f Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 30 Jun 2023 10:06:18 -0500 Subject: [PATCH 4/4] fix bugs handles malformed gcode commands Adds properties to existing custom ops --- src/Mod/Path/Path/Op/Custom.py | 100 +++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/Mod/Path/Path/Op/Custom.py b/src/Mod/Path/Path/Op/Custom.py index beb4a5c9ac..6bd4f3d85f 100644 --- a/src/Mod/Path/Path/Op/Custom.py +++ b/src/Mod/Path/Path/Op/Custom.py @@ -45,6 +45,40 @@ translate = FreeCAD.Qt.translate class ObjectCustom(PathOp.ObjectOp): + @classmethod + def propertyEnumerations(self, dataType="data"): + """customOpPropertyEnumerations(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 = { + "Source": [ + (translate("PathCustom", "Text"), "Text"), + (translate("PathCustom", "File"), "File"), + ], + } + + if dataType == "raw": + return enums + + data = list() + idx = 0 if dataType == "translated" else 1 + + Path.Log.debug(enums) + + for k, v in enumerate(enums): + data.append((v, [tup[idx] for tup in enums[v]])) + Path.Log.debug(data) + + return data + def opFeatures(self, obj): return PathOp.FeatureTool | PathOp.FeatureCoolant @@ -53,7 +87,7 @@ class ObjectCustom(PathOp.ObjectOp): "App::PropertyEnumeration", "Source", "Path", - "Source of gcode (text, file, ...)" + "Source of gcode (text, file, ...)", ) obj.addProperty( @@ -70,29 +104,38 @@ class ObjectCustom(PathOp.ObjectOp): QT_TRANSLATE_NOOP("App::Property", "The G-code to be inserted"), ) + # populate the property enumerations + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) + obj.Proxy = self self.setEditorModes(obj) - def opPropertyEnumerations(self, dateType="data"): - enum = super().opPropertyEnumerations(dateType) - - props = [ - (translate("PathCustom", "Text"), "Text"), - (translate("PathCustom", "File"), "File") - ] - - if dateType == "raw": - enum["Source"] = props - return enum - - enum.append(("Source", [ p[1][1] for p in enumerate(props) ])) - - return enum - def onChanged(self, obj, prop): if prop == "Source": self.setEditorModes(obj) + def opOnDocumentRestored(self, obj): + if not hasattr(obj, "Source"): + obj.addProperty( + "App::PropertyEnumeration", + "Source", + "Path", + "Source of gcode (text, file, ...)", + ) + + if not hasattr(obj, "GcodeFile"): + obj.addProperty( + "App::PropertyFile", + "GcodeFile", + "Path", + "File containing gcode to be inserted", + ) + + # populate the property enumerations + for n in self.propertyEnumerations(): + setattr(obj, n[0], n[1]) + def onDocumentRestore(self, obj): self.setEditorModes(self, obj) @@ -101,11 +144,11 @@ class ObjectCustom(PathOp.ObjectOp): return if obj.Source == "Text": - obj.setEditorMode("GcodeFile", 2) - obj.setEditorMode("Gcode", 0) + obj.setEditorMode("GcodeFile", 2) + obj.setEditorMode("Gcode", 0) elif obj.Source == "File": - obj.setEditorMode("GcodeFile", 0) - obj.setEditorMode("Gcode", 2) + obj.setEditorMode("GcodeFile", 0) + obj.setEditorMode("Gcode", 2) def findGcodeFile(self, filename): if os.path.exists(filename): @@ -130,12 +173,21 @@ class ObjectCustom(PathOp.ObjectOp): # could not determine the path if not gcode_file: - Path.Log.error(translate("PathCustom", "Custom file %s could not be found.") % obj.GcodeFile) + Path.Log.error( + translate("PathCustom", "Custom file %s could not be found.") + % obj.GcodeFile + ) with open(gcode_file) as fd: for l in fd.readlines(): - newcommand = Path.Command(str(l)) - self.commandlist.append(newcommand) + try: + newcommand = Path.Command(str(l)) + self.commandlist.append(newcommand) + except ValueError: + Path.Log.warning( + translate("PathCustom", "Invalid Gcode line: %s") % l + ) + continue self.commandlist.append(Path.Command("(End Custom)"))