| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """ |
| | The base classes for post processors in the CAM workbench. |
| | """ |
| | import argparse |
| | import importlib.util |
| | import os |
| | from PySide import QtCore, QtGui |
| | import re |
| | import sys |
| | from typing import Any, Dict, List, Optional, Tuple, Union |
| |
|
| | import Path.Base.Util as PathUtil |
| | import Path.Post.UtilsArguments as PostUtilsArguments |
| | import Path.Post.UtilsExport as PostUtilsExport |
| | import Path.Post.PostList as PostList |
| | import Path.Post.Utils as PostUtils |
| |
|
| | import FreeCAD |
| | import Path |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| | debug = False |
| | if debug: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | |
| | |
| | |
| | Defaults = Dict[str, bool] |
| | FormatHelp = str |
| | GCodeOrNone = Optional[str] |
| | GCodeSections = List[Tuple[str, GCodeOrNone]] |
| | Parser = argparse.ArgumentParser |
| | ParserArgs = Union[None, str, argparse.Namespace] |
| | Postables = Union[List, List[Tuple[str, List]]] |
| | Section = Tuple[str, List] |
| | Sublist = List |
| | Units = str |
| | Values = Dict[str, Any] |
| | Visible = Dict[str, bool] |
| |
|
| |
|
| | class PostProcessorFactory: |
| | """Factory class for creating post processors.""" |
| |
|
| | @staticmethod |
| | def get_post_processor(job, postname): |
| | |
| | Path.Log.debug("PostProcessorFactory.get_post_processor()") |
| |
|
| | |
| | paths = Path.Preferences.searchPathsPost() |
| | paths.extend(sys.path) |
| |
|
| | module_name = f"{postname}_post" |
| | class_name = postname.title() |
| | Path.Log.debug(f"PostProcessorFactory.get_post_processor() - postname: {postname}") |
| | Path.Log.debug(f"PostProcessorFactory.get_post_processor() - module_name: {module_name}") |
| | Path.Log.debug(f"PostProcessorFactory.get_post_processor() - class_name: {class_name}") |
| |
|
| | |
| | for path in paths: |
| | module_path = os.path.join(path, f"{module_name}.py") |
| | spec = importlib.util.spec_from_file_location(module_name, module_path) |
| |
|
| | if spec and spec.loader: |
| | module = importlib.util.module_from_spec(spec) |
| | try: |
| | spec.loader.exec_module(module) |
| | Path.Log.debug(f"found module {module_name} at {module_path}") |
| |
|
| | except (FileNotFoundError, ImportError, ModuleNotFoundError): |
| | continue |
| |
|
| | try: |
| | PostClass = getattr(module, class_name) |
| | return PostClass(job) |
| | except AttributeError: |
| | |
| | Path.Log.debug(f"Post processor {postname} is a script") |
| | return WrapperPost(job, module_path, module_name) |
| |
|
| | return None |
| |
|
| |
|
| | def needsTcOp(oldTc, newTc): |
| | return PostList.needsTcOp(oldTc, newTc) |
| |
|
| |
|
| | class PostProcessor: |
| | """Base Class. All non-legacy postprocessors should inherit from this class.""" |
| |
|
| | def __init__(self, job, tooltip, tooltipargs, units, *args, **kwargs): |
| | self._tooltip = tooltip |
| | self._tooltipargs = tooltipargs |
| | self._units = units |
| | self._args = args |
| | self._kwargs = kwargs |
| | self.reinitialize() |
| |
|
| | if isinstance(job, dict): |
| | |
| | self._job = job["job"] |
| | self._operations = job["operations"] |
| | else: |
| | |
| | self._job = job |
| | self._operations = getattr(job.Operations, "Group", []) if job is not None else [] |
| |
|
| | @classmethod |
| | def exists(cls, processor): |
| | return processor in Path.Preferences.allAvailablePostProcessors() |
| |
|
| | @property |
| | def tooltip(self): |
| | """Get the tooltip text for the post processor.""" |
| | raise NotImplementedError("Subclass must implement abstract method") |
| | |
| |
|
| | @property |
| | def tooltipArgs(self) -> FormatHelp: |
| | return self.parser.format_help() |
| |
|
| | @property |
| | def units(self): |
| | """Get the units used by the post processor.""" |
| | return self._units |
| |
|
| | def _buildPostList(self, early_tool_prep=False): |
| | """Determine the specific objects and order to postprocess. |
| | |
| | Returns a list of objects which can be passed to exportObjectsWith() |
| | for final posting. The ordering strategy is determined by the job's |
| | OrderOutputBy setting. |
| | |
| | Args: |
| | early_tool_prep: If True, split tool changes into separate prep (Tn) |
| | and change (M6) commands for better machine efficiency |
| | |
| | Returns: |
| | List of (name, operations) tuples |
| | """ |
| | return PostList.buildPostList(self, early_tool_prep) |
| |
|
| | def export(self) -> Union[None, GCodeSections]: |
| | """Process the parser arguments, then postprocess the 'postables'.""" |
| | args: ParserArgs |
| | flag: bool |
| |
|
| | Path.Log.debug("Exporting the job") |
| |
|
| | (flag, args) = self.process_arguments() |
| | |
| | |
| | |
| | if flag: |
| | return self.process_postables() |
| | |
| | |
| | |
| | |
| | |
| | if args is None: |
| | return None |
| | |
| | |
| | |
| | |
| | return [("allitems", args)] |
| |
|
| | def init_arguments( |
| | self, |
| | values: Values, |
| | argument_defaults: Defaults, |
| | arguments_visible: Visible, |
| | ) -> Parser: |
| | """Initialize the shared argument definitions.""" |
| | _parser: Parser = PostUtilsArguments.init_shared_arguments( |
| | values, argument_defaults, arguments_visible |
| | ) |
| | |
| | |
| | |
| | return _parser |
| |
|
| | def init_argument_defaults(self, argument_defaults: Defaults) -> None: |
| | """Initialize which arguments (in a pair) are shown as the default argument.""" |
| | PostUtilsArguments.init_argument_defaults(argument_defaults) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | def init_arguments_visible(self, arguments_visible: Visible) -> None: |
| | """Initialize which argument pairs are visible in TOOLTIP_ARGS.""" |
| | PostUtilsArguments.init_arguments_visible(arguments_visible) |
| | |
| | |
| | |
| |
|
| | def init_values(self, values: Values) -> None: |
| | """Initialize values that are used throughout the postprocessor.""" |
| | |
| | PostUtilsArguments.init_shared_values(values) |
| | |
| | |
| | |
| | |
| | values["UNITS"] = self._units |
| |
|
| | def process_arguments(self) -> Tuple[bool, ParserArgs]: |
| | """Process any arguments to the postprocessor.""" |
| | |
| | |
| | |
| | args: ParserArgs |
| | flag: bool |
| |
|
| | (flag, args) = PostUtilsArguments.process_shared_arguments( |
| | self.values, self.parser, self._job.PostProcessorArgs, self.all_visible, "-" |
| | ) |
| | |
| | |
| | |
| | if flag: |
| | |
| | |
| | |
| | |
| | |
| | |
| | self._units = self.values["UNITS"] |
| | |
| | |
| | |
| | |
| | |
| | return (flag, args) |
| |
|
| | def process_postables(self) -> GCodeSections: |
| | """Postprocess the 'postables' in the job to g code sections.""" |
| | |
| | |
| | |
| | gcode: GCodeOrNone |
| | g_code_sections: GCodeSections |
| | partname: str |
| | postables: Postables |
| | section: Section |
| | sublist: Sublist |
| |
|
| | postables = self._buildPostList() |
| |
|
| | |
| | for _, section in enumerate(postables): |
| | _, sublist = section |
| | for obj in sublist: |
| | if hasattr(obj, "Path"): |
| | obj.Path = PostUtils.cannedCycleTerminator(obj.Path) |
| |
|
| | Path.Log.debug(f"postables count: {len(postables)}") |
| |
|
| | g_code_sections = [] |
| | for _, section in enumerate(postables): |
| | partname, sublist = section |
| | gcode = PostUtilsExport.export_common(self.values, sublist, "-") |
| | g_code_sections.append((partname, gcode)) |
| |
|
| | return g_code_sections |
| |
|
| | def reinitialize(self) -> None: |
| | """Initialize or reinitialize the 'core' data structures for the postprocessor.""" |
| | |
| | |
| | |
| | self.values: Values = {} |
| | self.init_values(self.values) |
| | self.argument_defaults: Defaults = {} |
| | self.init_argument_defaults(self.argument_defaults) |
| | self.arguments_visible: Visible = {} |
| | self.init_arguments_visible(self.arguments_visible) |
| | self.parser: Parser = self.init_arguments( |
| | self.values, self.argument_defaults, self.arguments_visible |
| | ) |
| | |
| | |
| | |
| | |
| | self.all_arguments_visible: Visible = {} |
| | for k in iter(self.arguments_visible): |
| | self.all_arguments_visible[k] = True |
| | self.all_visible: Parser = self.init_arguments( |
| | self.values, self.argument_defaults, self.all_arguments_visible |
| | ) |
| |
|
| |
|
| | class WrapperPost(PostProcessor): |
| | """Wrapper class for old post processors that are scripts.""" |
| |
|
| | def __init__(self, job, script_path, module_name, *args, **kwargs): |
| | super().__init__(job, tooltip=None, tooltipargs=None, units=None, *args, **kwargs) |
| | self.script_path = script_path |
| | self.module_name = module_name |
| | Path.Log.debug(f"WrapperPost.__init__({script_path})") |
| | self.load_script() |
| |
|
| | def load_script(self): |
| | """Dynamically load the script as a module.""" |
| | try: |
| | spec = importlib.util.spec_from_file_location(self.module_name, self.script_path) |
| | self.script_module = importlib.util.module_from_spec(spec) |
| | spec.loader.exec_module(self.script_module) |
| | except Exception as e: |
| | raise ImportError(f"Failed to load script: {e}") |
| |
|
| | if not hasattr(self.script_module, "export"): |
| | raise AttributeError("The script does not have an 'export' function.") |
| |
|
| | |
| | self._units = "Metric" if getattr(self.script_module, "UNITS", "G21") == "G21" else "Inch" |
| | self._tooltip = getattr(self.script_module, "TOOLTIP", "No tooltip provided") |
| | self._tooltipargs = getattr(self.script_module, "TOOLTIP_ARGS", []) |
| |
|
| | def export(self): |
| | """Dynamically reload the module for the export to ensure up-to-date usage.""" |
| |
|
| | postables = self._buildPostList() |
| | Path.Log.debug(f"postables count: {len(postables)}") |
| |
|
| | g_code_sections = [] |
| | for idx, section in enumerate(postables): |
| | partname, sublist = section |
| |
|
| | gcode = self.script_module.export(sublist, "-", self._job.PostProcessorArgs) |
| | Path.Log.debug(f"Exported {partname}") |
| | g_code_sections.append((partname, gcode)) |
| | return g_code_sections |
| |
|
| | @property |
| | def tooltip(self): |
| | return self._tooltip |
| |
|
| | @property |
| | def tooltipArgs(self): |
| | return self._tooltipargs |
| |
|
| | @property |
| | def units(self): |
| | return self._units |
| |
|