From 5fe73e2393984be7b4ea6c8ac06535ef1f572c7b Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Tue, 27 May 2025 09:30:35 +0200 Subject: [PATCH] CAM: Refactor ToolBit.from_dict() for clarity and to generate more relevant warnings --- src/Mod/CAM/Path/Tool/shape/doc.py | 17 ++-- src/Mod/CAM/Path/Tool/shape/models/base.py | 2 +- src/Mod/CAM/Path/Tool/toolbit/models/base.py | 92 ++++++++++++-------- 3 files changed, 65 insertions(+), 46 deletions(-) diff --git a/src/Mod/CAM/Path/Tool/shape/doc.py b/src/Mod/CAM/Path/Tool/shape/doc.py index 1e2d306a02..4b9d01b585 100644 --- a/src/Mod/CAM/Path/Tool/shape/doc.py +++ b/src/Mod/CAM/Path/Tool/shape/doc.py @@ -21,6 +21,7 @@ # *************************************************************************** import FreeCAD +import Path import Path.Base.Util as PathUtil from typing import Dict, List, Any, Optional import tempfile @@ -76,9 +77,9 @@ def get_object_properties( properties[name] = getattr(obj, name) else: # Log a warning if a parameter expected by the shape class is missing - FreeCAD.Console.PrintWarning( + Path.Log.debug( f"Parameter '{name}' not found on object '{obj.Label}' " - f"({obj.Name}). Default value will be used by the shape class.\n" + f"({obj.Name}). Default value will be used by the shape class." ) properties[name] = None # Indicate missing value return properties @@ -99,13 +100,13 @@ def update_shape_object_properties( try: PathUtil.setProperty(obj, name, value) except Exception as e: - FreeCAD.Console.PrintWarning( + Path.Log.warning( f"Failed to set property '{name}' on object '{obj.Label}'" - f" ({obj.Name}) with value '{value}': {e}\n" + f" ({obj.Name}) with value '{value}': {e}" ) else: - FreeCAD.Console.PrintWarning( - f"Property '{name}' not found on object '{obj.Label}'" f" ({obj.Name}). Skipping.\n" + Path.Log.warning( + f"Property '{name}' not found on object '{obj.Label}' ({obj.Name}). Skipping." ) @@ -184,6 +185,4 @@ class ShapeDocFromBytes: try: os.remove(self._temp_file) except Exception as e: - FreeCAD.Console.PrintWarning( - f"Failed to remove temporary file {self._temp_file}: {e}\n" - ) + Path.Log.warning(f"Failed to remove temporary file {self._temp_file}: {e}") diff --git a/src/Mod/CAM/Path/Tool/shape/models/base.py b/src/Mod/CAM/Path/Tool/shape/models/base.py index 87ea1a925a..47219d40c4 100644 --- a/src/Mod/CAM/Path/Tool/shape/models/base.py +++ b/src/Mod/CAM/Path/Tool/shape/models/base.py @@ -287,7 +287,7 @@ class ToolBitShape(Asset): try: shape_class = ToolBitShape.get_shape_class_from_bytes(data) except Exception as e: - Path.Log.warning(f"{id}: Failed to determine shape class from bytes: {e}") + Path.Log.debug(f"{id}: Failed to determine shape class from bytes: {e}") shape_types = [c.name for c in ToolBitShape.__subclasses__()] shape_class = ToolBitShape.guess_subclass_from_name(id) if shape_class: diff --git a/src/Mod/CAM/Path/Tool/toolbit/models/base.py b/src/Mod/CAM/Path/Tool/toolbit/models/base.py index 9537ab945b..ca0cf7bfd6 100644 --- a/src/Mod/CAM/Path/Tool/toolbit/models/base.py +++ b/src/Mod/CAM/Path/Tool/toolbit/models/base.py @@ -89,6 +89,49 @@ class ToolBit(Asset, ABC): return subclass raise ValueError(f"No ToolBit subclass found for shape {type(shape).__name__}") + @classmethod + def _get_shape_type(cls, shape_id: str, shape_type: str | None) -> Type[ToolBitShape]: + """ + Extracts the shape class from the attributes dictionary. + """ + # Best method: if the shape-type is specified, use that. + if shape_type: + return ToolBitShape.get_subclass_by_name(shape_type) + + # If no shape type is specified, try to find the shape class from the ID. + shape_class = ToolBitShape.get_subclass_by_name(shape_id) + if shape_class: + return shape_class + + # If that also fails, try to load the shape to get the class. + Path.Log.debug( + f'Failed to infer shape type from "{shape_id}", trying to load' + f' the shape "{shape_id}" to determine the class. This may' + " negatively impact performance." + ) + shape_asset_uri = ToolBitShape.resolve_name(shape_id) + shape = cam_assets.get(shape_asset_uri, depth=0) + if shape: + return shape.__class__ + + # If all else fails, try to guess the shape class from the ID. + shape_types = [c.name for c in ToolBitShape.__subclasses__()] + shape_class = ToolBitShape.guess_subclass_from_name(shape_id) + if shape_class: + Path.Log.warning( + f'Failed to infer shape type from "{shape_id}",' + f' guessing "{shape_class.name}".' + f" To fix, name the body in the shape file to one of: {shape_types}" + ) + return shape_class + + # Default to endmill if nothing else works + Path.Log.warning( + f'Failed to infer shape type from {shape_id}, using "endmill".' + f" To fix, name the body in the shape file to one of: {shape_types}" + ) + return ToolBitShapeEndmill + @classmethod def from_dict(cls, attrs: Mapping, shallow: bool = False) -> "ToolBit": """ @@ -101,51 +144,28 @@ class ToolBit(Asset, ABC): if not shape_id: raise ValueError("ToolBit dictionary is missing 'shape' key") - # Find the shape type. - shape_types = [c.name for c in ToolBitShape.__subclasses__()] - shape_type = attrs.get("shape-type") - shape_class = None - if shape_type is None: - shape_class = ToolBitShape.get_subclass_by_name(shape_id) - if not shape_class: - shape_class = ToolBitShape.guess_subclass_from_name(shape_id) - if shape_class: - Path.Log.warning( - f"failed to infer shape type from {shape_id}," - f' guessing "{shape_class.name}". To fix, name' - f" the body in the shape file to one of: {shape_types}" - ) - else: - Path.Log.warning( - f"failed to infer shape type from {shape_id}," - f' using "endmill". To fix, name' - f" the body in the shape file to one of: {shape_types}" - ) - shape_class = ToolBitShapeEndmill - shape_type = shape_class.name + shape_class = cls._get_shape_type(shape_id, attrs.get("shape-type")) - # Try to load the shape, if the asset exists. - tool_bit_shape = None + # Create a ToolBitShape instance. if not shallow: # Shallow means: skip loading of child assets shape_asset_uri = ToolBitShape.resolve_name(shape_id) try: tool_bit_shape = cast(ToolBitShape, cam_assets.get(shape_asset_uri)) except FileNotFoundError: - pass # Rely on the fallback below Path.Log.debug(f"ToolBit.from_dict: Shape asset {shape_asset_uri} not found.") + # Rely on the fallback below + else: + return cls.from_shape(tool_bit_shape, attrs, id=attrs.get("id")) - # If it does not exist, create a new instance from scratch. + # Ending up here means we either could not load the shape asset, + # or we are in shallow mode and do not want to load it. + # Create a shape instance from scratch as a "placeholder". params = attrs.get("parameter", {}) - if tool_bit_shape is None: - if not shape_class: - shape_class = ToolBitShape.get_subclass_by_name(shape_type) - if not shape_class: - raise ValueError(f"failed to get shape class from {shape_id}") - tool_bit_shape = shape_class(shape_id, **params) - Path.Log.debug( - f"ToolBit.from_dict: created shape instance {tool_bit_shape.name}" - f" from {shape_id}. Uri: {tool_bit_shape.get_uri()}" - ) + tool_bit_shape = shape_class(shape_id, **params) + Path.Log.debug( + f"ToolBit.from_dict: created shape instance {tool_bit_shape.name}" + f" from {shape_id}. Uri: {tool_bit_shape.get_uri()}" + ) # Now that we have a shape, create the toolbit instance. return cls.from_shape(tool_bit_shape, attrs, id=attrs.get("id"))