Source code for linkforge.core.exceptions

"""Custom exceptions for the LinkForge ecosystem.

Defines the exception hierarchy used across models, parsers, and generators
to provide granular error handling and categorized validation failures.

Core Components:
    - ValidationErrorCode: Enum of standardized error categories.
    - LinkForgeError: Base exception for the entire ecosystem.
    - RobotModelError: Failures related to the IR and physical consistency.
    - RobotParserError: Failures during format import and resolution.
    - RobotGeneratorError: Failures during format export.
"""

from enum import StrEnum
from pathlib import Path
from typing import Any


[docs] class ValidationErrorCode(StrEnum): """Categorized error codes for robot validation failures.""" # Naming and Identity INVALID_NAME = "invalid_name" DUPLICATE_NAME = "duplicate_name" NAME_EMPTY = "name_empty" # Kinematic Structure NOT_FOUND = "not_found" HAS_CYCLE = "has_cycle" NO_ROOT = "no_root" MULTIPLE_ROOTS = "multiple_roots" # Physics and Values OUT_OF_RANGE = "out_of_range" VALUE_EMPTY = "value_empty" INVALID_VALUE = "invalid_value" PHYSICS_VIOLATION = "physics_violation" INERTIA_TRIANGLE_INEQUALITY = "inertia_triangle_inequality" # Mesh Topology MESH_UNWELDED = "mesh_unwelded" MESH_DUPLICATE_FACE = "mesh_duplicate_face" MESH_DEGENERATE = "mesh_degenerate" MESH_BOUNDARY_EDGE = "mesh_boundary_edge" MESH_NON_MANIFOLD = "mesh_non_manifold" MESH_INCONSISTENT_WINDING = "mesh_inconsistent_winding" MESH_SLIVER = "mesh_sliver" # Configuration and Misc GENERIC_FAILURE = "generic_failure"
[docs] class LinkForgeError(Exception): """Base category for all LinkForge-related exceptions.""" pass
[docs] class RobotModelError(LinkForgeError): """Exception raised for structural or logic errors in the Robot model.""" pass
[docs] class RobotGeneratorError(LinkForgeError): """Exception raised during robot generation or export.""" pass
[docs] class RobotParserError(LinkForgeError): """Exception raised during robot parsing or import.""" pass
[docs] class RobotParserIOError(RobotParserError): """Exception raised for file-level or I/O errors during parsing."""
[docs] def __init__(self, filepath: Path | str = "unknown", reason: str = "error"): super().__init__(f"Parser IO error: {reason} (file: {filepath})")
[docs] class RobotParserXMLRootError(RobotParserError): """Exception raised when the XML root element is invalid."""
[docs] def __init__(self, actual_tag: str = "unknown", expected_tag: str = "robot"): super().__init__(f"Invalid XML root: <{actual_tag}> (expected <{expected_tag}>)")
[docs] class RobotParserUnexpectedError(RobotParserError): """General wrapper for unexpected parsing failures."""
[docs] def __init__(self, source_area: str = "unknown", original_error: Any = None): msg = f"Unexpected error in {source_area}" if original_error: msg += f": {original_error}" super().__init__(msg)
[docs] class RobotPhysicsError(RobotModelError): """Exception raised for unphysical properties (e.g. negative mass or volume)."""
[docs] def __init__( self, code: ValidationErrorCode, message: str, target: str | None = None, value: Any = None, ): self.code = code self.target = target self.value = value full_msg = f"[PHYSICS_{code.name}] {message}" if target: full_msg += f" (target: {target})" if value is not None: full_msg += f" (value: {value})" super().__init__(full_msg)
[docs] class RobotValidationError(RobotModelError): """Exception raised for structural or logic validation failures. Now structured using ValidationErrorCode for robust error handling. """
[docs] def __init__( self, code: ValidationErrorCode, message: str, target: str | None = None, value: Any = None, ): self.code = code self.target = target self.value = value full_msg = f"[{code.name}] {message}" if target: full_msg += f" (target: {target})" if value is not None: full_msg += f" (value: {value})" super().__init__(full_msg)
[docs] class RobotSecurityError(RobotModelError): """Exception raised for security-related errors (e.g. sandbox escapes)."""
[docs] def __init__(self, path: str = "unknown", reason: str = "violation"): super().__init__(f"Security Violation: {reason} (path: {path})")
[docs] class RobotMathError(RobotModelError): """Exception raised for invalid numerical values (NaN, Inf, or Out of Range)."""
[docs] def __init__( self, code: ValidationErrorCode, message: str, target: str | None = None, value: Any = None, ): self.code = code self.target = target self.value = value full_msg = f"[MATH_{code.name}] {message}" if target: full_msg += f" (target: {target})" if value is not None: full_msg += f" (value: {value})" super().__init__(full_msg)
[docs] class RobotXacroError(RobotParserError): """General exception for XACRO resolution failures."""
[docs] def __init__(self, message: str = "failure", context: str | None = None): ctx = f" (at {context})" if context else "" super().__init__(f"XACRO error: {message}{ctx}")
[docs] class RobotXacroRecursionError(RobotXacroError): """Exception raised for circular dependencies or max depth in XACRO."""
[docs] def __init__(self, depth: int | str = "unknown", reason: str | None = None): msg = f"Recursion depth exceeded: {depth}" if reason: msg += f" ({reason})" super().__init__(msg)
[docs] class RobotXacroExpressionError(RobotXacroError): """Exception raised for failures in XACRO math or property evaluation."""
[docs] def __init__(self, expression: str = "unknown", reason: str = "error"): super().__init__(f"Expression evaluation failed: ${{{expression}}} -> {reason}")
[docs] class XacroDetectedError(RobotParserError): """Raised when XACRO content is detected in a URDF parser."""
[docs] def __init__(self, message: str = "XACRO detected"): super().__init__( f"XACRO file detected: {message}. Please convert to URDF or use XACROParser." )