diff --git a/src/Mod/CAM/Gui/Resources/Path.qrc b/src/Mod/CAM/Gui/Resources/Path.qrc index e681f05236..e665bd5e6c 100644 --- a/src/Mod/CAM/Gui/Resources/Path.qrc +++ b/src/Mod/CAM/Gui/Resources/Path.qrc @@ -39,6 +39,7 @@ icons/CAM_OperationB.svg icons/CAM_Pocket.svg icons/CAM_Post.svg + icons/CAM_PostSelected.svg icons/CAM_Probe.svg icons/CAM_Profile_Edges.svg icons/CAM_Profile_Face.svg diff --git a/src/Mod/CAM/Gui/Resources/icons/CAM_PostSelected.svg b/src/Mod/CAM/Gui/Resources/icons/CAM_PostSelected.svg new file mode 100644 index 0000000000..ee361fd15f --- /dev/null +++ b/src/Mod/CAM/Gui/Resources/icons/CAM_PostSelected.svg @@ -0,0 +1,1195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + CAM_Post + 2016-06-27 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/CAM/Gui/Resources/icons/CAM_Post.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/CAM/InitGui.py b/src/Mod/CAM/InitGui.py index 753d50e4fa..10dc9848f6 100644 --- a/src/Mod/CAM/InitGui.py +++ b/src/Mod/CAM/InitGui.py @@ -124,7 +124,8 @@ class CAMWorkbench(Workbench): Path.GuiInit.Startup() # build commands list - projcmdlist = ["CAM_Job", "CAM_Post", "CAM_Sanity"] + projcmdlist = ["CAM_Job", "CAM_Sanity"] + postcmdlist = ["CAM_Post", "CAM_PostSelected"] toolcmdlist = ["CAM_Inspect", "CAM_SelectLoop", "CAM_OpActiveToggle"] simcmdlist = ["CAM_SimulatorGL", "CAM_Simulator"] @@ -168,6 +169,14 @@ class CAMWorkbench(Workbench): toolcmdlist.extend(PathToolBitLibraryCmd.BarList) toolbitcmdlist = PathToolBitLibraryCmd.MenuList + postcmdgroup = ["CAM_PostTools"] + FreeCADGui.addCommand( + "CAM_PostTools", + PathCommandGroup( + postcmdlist, + QT_TRANSLATE_NOOP("CAM_PostTools", "Post process Operations"), + ), + ) simcmdgroup = ["CAM_SimTools"] FreeCADGui.addCommand( "CAM_SimTools", @@ -253,7 +262,10 @@ class CAMWorkbench(Workbench): if not Path.Preferences.suppressOpenCamLibWarning(): FreeCAD.Console.PrintError("OpenCamLib is not working!\n") - self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Project Setup"), projcmdlist) + self.appendToolbar( + QT_TRANSLATE_NOOP("Workbench", "Project Setup"), + projcmdlist + postcmdgroup, + ) self.appendToolbar( QT_TRANSLATE_NOOP("Workbench", "Tool Commands"), simcmdgroup + toolcmdlist, @@ -271,6 +283,7 @@ class CAMWorkbench(Workbench): self.appendMenu( [QT_TRANSLATE_NOOP("Workbench", "&CAM")], projcmdlist + + postcmdlist + ["CAM_ExportTemplate", "Separator"] + simcmdlist + toolcmdlist @@ -356,16 +369,17 @@ class CAMWorkbench(Workbench): import PathScripts menuAppended = False - if len(FreeCADGui.Selection.getSelection()) == 1: - obj = FreeCADGui.Selection.getSelection()[0] + selection = FreeCADGui.Selection.getSelection() + if len(selection) == 1: + obj = selection[0] + selectedName = obj.Name if obj.isDerivedFrom("Path::Feature"): self.appendContextMenu("", "Separator") self.appendContextMenu("", ["CAM_Inspect"]) - selectedName = obj.Name if "Remote" in selectedName: self.appendContextMenu("", ["Refresh_Path"]) if "Job" in selectedName: - self.appendContextMenu("", ["CAM_ExportTemplate"] + self.toolbitctxmenu) + self.appendContextMenu("", ["CAM_ExportTemplate"]) menuAppended = True if isinstance(obj.Proxy, Path.Op.Base.ObjectOp): self.appendContextMenu("", ["CAM_OperationCopy", "CAM_OpActiveToggle"]) @@ -388,6 +402,14 @@ class CAMWorkbench(Workbench): if isinstance(obj.Proxy, Path.Tool.ToolBit): self.appendContextMenu("", ["CAM_ToolBitSave", "CAM_ToolBitSaveAs"]) menuAppended = True + + if selection: + for obj in selection: + if not obj.isDerivedFrom("Path::Feature"): + break + else: + self.appendContextMenu("", ["CAM_Post", "CAM_PostSelected"]) + if menuAppended: self.appendContextMenu("", "Separator") diff --git a/src/Mod/CAM/Path/Main/Gui/PreferencesJob.py b/src/Mod/CAM/Path/Main/Gui/PreferencesJob.py index 8a3f0cda26..6900846bd6 100644 --- a/src/Mod/CAM/Path/Main/Gui/PreferencesJob.py +++ b/src/Mod/CAM/Path/Main/Gui/PreferencesJob.py @@ -277,7 +277,7 @@ class JobPreferencesPage: self.form.stockCreateCylinder.hide() def getPostProcessor(self, name): - if not name in self.processor: + if name not in self.processor: processor = PostProcessorFactory.get_post_processor(None, name) self.processor[name] = processor return processor diff --git a/src/Mod/CAM/Path/Post/Command.py b/src/Mod/CAM/Path/Post/Command.py index 196fd7b129..9021847ec7 100644 --- a/src/Mod/CAM/Path/Post/Command.py +++ b/src/Mod/CAM/Path/Post/Command.py @@ -61,8 +61,7 @@ def _resolve_post_processor_name(job): if valid_name and PostProcessor.exists(valid_name): return valid_name - else: - raise ValueError(f"Post processor not identified.") + raise ValueError("Post processor not identified.") class DlgSelectPostProcessor: @@ -109,16 +108,15 @@ class CommandPathPost: "Pixmap": "CAM_Post", "MenuText": QT_TRANSLATE_NOOP("CAM_Post", "Post Process"), "Accel": "P, P", - "ToolTip": QT_TRANSLATE_NOOP("CAM_Post", "Post Processes the selected job"), + "ToolTip": QT_TRANSLATE_NOOP("CAM_Post", "Post Processes the selected Job"), } def IsActive(self): - selected = FreeCADGui.Selection.getSelectionEx() - if len(selected) != 1: + selection = FreeCADGui.Selection.getSelection() + if not selection: return False - selected_object = selected[0].Object - self.candidate = PathUtils.findParentJob(selected_object) + self.candidate = PathUtils.findParentJob(selection[0]) return self.candidate is not None @@ -212,7 +210,10 @@ class CommandPathPost: return # get a postprocessor - postprocessor = PostProcessorFactory.get_post_processor(self.candidate, postprocessor_name) + postprocessor = PostProcessorFactory.get_post_processor( + self.candidate, + postprocessor_name, + ) post_data = postprocessor.export() # None is returned if there was an error during argument processing @@ -259,8 +260,105 @@ class CommandPathPost: FreeCAD.ActiveDocument.recompute() +class CommandPathPostSelected(CommandPathPost): + def GetResources(self): + return { + "Pixmap": "CAM_PostSelected", + "MenuText": QT_TRANSLATE_NOOP("CAM_Post", "Post Process Selected"), + "Accel": "P, O", + "ToolTip": QT_TRANSLATE_NOOP("CAM_Post", "Post Processes the selected operations"), + } + + def IsActive(self): + selection = FreeCADGui.Selection.getSelection() + if not selection: + return False + + return all(hasattr(op, "Path") and not op.Name.startswith("Job") for op in selection) + + def Activated(self): + """ + Handles the activation of post processing, initiating the process based + on user selection and document context. + """ + FreeCAD.ActiveDocument.openTransaction("Post Process the Selected operations") + + selection = FreeCADGui.Selection.getSelection() + job = PathUtils.findParentJob(selection[0]) + if ( + not job + and hasattr(selection[0], "Base") + and isinstance(selection[0].Base, list) + and selection[0].Base + ): + # find 'job' for operation inside 'Array' with multi tool controller + baseOp = FreeCAD.ActiveDocument.getObject(selection[0].Base[0]) + job = PathUtils.findParentJob(baseOp) + + opCandidates = [op for op in selection if hasattr(op, "Path") and "Job" not in op.Name] + operations = [] + if opCandidates and job.Operations.Group != opCandidates: + msgBox = QtGui.QMessageBox() + msgBox.setWindowTitle("Post Process") + msgBox.setText("

What needs to be exported?

") + msgBox.setInformativeText( + "

Check to make sure that you won't break anything by leaving out operations

" + ) + msgBox.findChild(QtGui.QGridLayout).setColumnMinimumWidth(1, 250) + btn1 = msgBox.addButton("Only selected", QtGui.QMessageBox.ButtonRole.YesRole) + btn2 = msgBox.addButton("All", QtGui.QMessageBox.ButtonRole.NoRole) + msgBox.setDefaultButton(btn2) + msgBox.exec() + + if msgBox.clickedButton() == btn1: + print( + f"Post process only selected operations: {', '.join([op.Name for op in opCandidates])}" + ) + operations = opCandidates + + postprocessor_name = _resolve_post_processor_name(job) + Path.Log.debug(f"Post Processor: {postprocessor_name}") + + if not postprocessor_name: + FreeCAD.ActiveDocument.abortTransaction() + return + + # get a postprocessor + postprocessor = PostProcessorFactory.get_post_processor( + {"job": job, "operations": operations}, postprocessor_name + ) + + post_data = postprocessor.export() + # None is returned if there was an error during argument processing + # otherwise the "usual" post_data data structure is returned. + if not post_data: + FreeCAD.ActiveDocument.abortTransaction() + return + + policy = Path.Preferences.defaultOutputPolicy() + generator = FilenameGenerator(job=job) + generated_filename = generator.generate_filenames() + + for item in post_data: + subpart, gcode = item + + # get a name for the file + subpart = "" if subpart == "allitems" else subpart + Path.Log.debug(subpart) + generator.set_subpartname(subpart) + fname = next(generated_filename) + + if gcode is not None: + # write the results to the file + self._write_file(fname, gcode, policy) + + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand("CAM_Post", CommandPathPost()) + FreeCADGui.addCommand("CAM_PostSelected", CommandPathPostSelected()) FreeCAD.Console.PrintLog("Loading PathPost… done\n") diff --git a/src/Mod/CAM/Path/Post/Processor.py b/src/Mod/CAM/Path/Post/Processor.py index 252585a2b4..4518f1d55a 100644 --- a/src/Mod/CAM/Path/Post/Processor.py +++ b/src/Mod/CAM/Path/Post/Processor.py @@ -116,6 +116,8 @@ class PostProcessorFactory: Path.Log.debug(f"Post processor {postname} is a script") return WrapperPost(job, module_path, module_name) + return None + def needsTcOp(oldTc, newTc): return ( @@ -133,11 +135,19 @@ class PostProcessor: self._tooltip = tooltip self._tooltipargs = tooltipargs self._units = units - self._job = job self._args = args self._kwargs = kwargs self.reinitialize() + if isinstance(job, dict): + # process only selected operations + self._job = job["job"] + self._operations = job["operations"] + else: + # get all operations from 'Operations' group + self._job = job + self._operations = getattr(job.Operations, "Group", []) + @classmethod def exists(cls, processor): return processor in Path.Preferences.allAvailablePostProcessors() @@ -203,7 +213,7 @@ class PostProcessor: sublist = [__fixtureSetup(index, f, self._job)] # Now generate the gcode - for obj in self._job.Operations.Group: + for obj in self._operations: tc = PathUtil.toolControllerForOp(obj) if tc is not None and PathUtil.activeForOp(obj): if needsTcOp(currTc, tc): @@ -240,7 +250,7 @@ class PostProcessor: postlist.append((toolstring, sublist)) Path.Log.track(self._job.PostProcessorOutputFile) - for idx, obj in enumerate(self._job.Operations.Group): + for idx, obj in enumerate(self._operations): Path.Log.track(obj.Label) # check if the operation is active @@ -286,7 +296,7 @@ class PostProcessor: currTc = None # Now generate the gcode - for obj in self._job.Operations.Group: + for obj in self._operations: # check if the operation is active if not PathUtil.activeForOp(obj):