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.svgicons/CAM_Pocket.svgicons/CAM_Post.svg
+ icons/CAM_PostSelected.svgicons/CAM_Probe.svgicons/CAM_Profile_Edges.svgicons/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 @@
+
+
+
+
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):