CAM: Refactor ToolBit.from_dict() for clarity and to generate more relevant warnings

This commit is contained in:
Samuel Abels
2025-05-27 09:30:35 +02:00
parent 383e2f599a
commit 7e635bed46
3 changed files with 65 additions and 46 deletions

View File

@@ -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}")

View File

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

View File

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