Merge pull request #22764 from tarman3/postprocess

CAM: Post Process only selected Operations
This commit is contained in:
sliptonic
2025-11-14 11:35:27 -06:00
committed by GitHub
6 changed files with 1345 additions and 19 deletions

View File

@@ -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

View File

@@ -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("<p align='center'>What needs to be exported?</p>")
msgBox.setInformativeText(
"<p align='center'>Check to make sure that you won't break anything by leaving out operations</p>"
)
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")

View File

@@ -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):