freecad-cam/Mod/cam-dev/ref-fusion/CAM360/ManufacturingAdvisor/ModelInput.py

305 lines
10 KiB
Python

#! python
"""
Classes for handling model inputs to Manufacturing Advisor function calling
tools.
- Construct a ModelInput instance by calling extractModelInput() with a 'model'
dictionary argument.
- The ModelInput mode is NONE if no models are to be added to the setup.
- Call attachModelToSetupInput() to attach the model input configuration to
a SetupInput instance. This returns True if models were successfully added
(or nothing added with CAMFunctionContext warnings, allowing setup creation
to continue).
"""
from adsk.cam import CAM, SetupInput
from adsk.core import Selections, UserInterface
from adsk.fusion import BRepBody, Component, Design, Occurrence
from CAMFunctionContext import CAMFunctionContext
from CAMFunctionUtils import (
ModelInputMode,
areMatchingCaseInsensitiveNames,
getManufacturingModelByName,
getValidOccurrences,
)
class ModelInput:
"""
Represents a model input configuration.
"""
def __init__(
self,
mode: ModelInputMode,
occurrenceName: str | None = None,
manufacturingModelName: str | None = None,
):
"""
Initializes a ModelInput instance.
"""
self.mode: ModelInputMode = mode
self.occurrenceName: str | None = occurrenceName
self.manufacturingModelName: str | None = manufacturingModelName
def attachModelToSetupInput(
self,
ui: UserInterface,
cam: CAM,
errorMessage: str,
warningMessage: str,
setupInput: SetupInput,
) -> bool:
"""
Attaches the model input configuration to the given setup input.
- Returns True if models were successfully added (or nothing added with
CAMFunctionContext warnings, allowing setup creation to continue).
- Returns False otherwise (with errors added to CAMFunctionContext).
"""
match self.mode:
case ModelInputMode.NONE:
return True # No models to add
case ModelInputMode.ALL:
return self._addAllOccurrences(
cam=cam,
setupInput=setupInput,
)
case ModelInputMode.NAMED:
return self._addNamedOccurrence(
cam=cam,
errorMessage=errorMessage,
warningMessage=warningMessage,
setupInput=setupInput,
)
case ModelInputMode.MANUFACTURING_MODEL:
return self._addManufacturingModelOccurrences(
cam=cam,
errorMessage=errorMessage,
setupInput=setupInput,
)
case ModelInputMode.ACTIVE_SELECTION:
return self._addActiveSelectionOccurrences(
ui=ui,
errorMessage=errorMessage,
setupInput=setupInput,
)
return False
@classmethod
def _addAllOccurrences(
cls,
cam: CAM,
setupInput: SetupInput,
) -> bool:
"""
Adds all models to the setup input.
This is done by getting access to the the root component of the CAM
product. We can get this by getting the assembly context of the design
root occurrence - but fall back to the design root occurrence for
safety.
"""
rootOccurrence: Occurrence = cam.designRootOccurrence
camRootOccurrence: Occurrence = rootOccurrence.assemblyContext
setupInput.models = (
[camRootOccurrence] if camRootOccurrence else [rootOccurrence]
)
return True
def _addNamedOccurrence(
self,
cam: CAM,
errorMessage: str,
warningMessage: str,
setupInput: SetupInput,
) -> bool:
"""
Adds a named occurrence to the setup input.
- Returns True if the occurrence was found and added.
- Returns True with a warning if the occurrence was not found.
- Returns False with an error if the occurrence name was not provided.
"""
if not self.occurrenceName:
CAMFunctionContext.fail(
message=errorMessage,
error="Name is not provided",
)
return False
target: BRepBody | Occurrence | None = self._findComponentOccurrenceOrBodyByName(
occurrence=cam.designRootOccurrence,
name=self.occurrenceName,
)
if not target:
CAMFunctionContext.warn(
message=warningMessage,
warning=f"Occurrence with name '{self.occurrenceName}' does not exist.",
)
return True # No bodies added, but not a failure
setupInput.models = [target]
return True
def _addManufacturingModelOccurrences(
self,
cam: CAM,
errorMessage: str,
setupInput: SetupInput,
) -> bool:
"""
Adds occurrences from a manufacturing model to the setup input.
- Returns True if occurrences were found and added.
- Returns False with an error if the manufacturing model name was not
provided.
- Returns False with an error if the manufacturing model was not found.
- Returns False with an error if the manufacturing model has no valid
occurrences.
"""
if not self.manufacturingModelName:
CAMFunctionContext.fail(
message=errorMessage,
error="Manufacturing model name is not provided.",
)
return False
manufacturingModel = getManufacturingModelByName(
cam=cam,
name=self.manufacturingModelName,
)
if not manufacturingModel:
CAMFunctionContext.fail(
message=errorMessage,
error=f"Manufacturing model '{self.manufacturingModelName}' not found.",
)
return False
occs = getValidOccurrences(manufacturingModel.occurrence)
if len(occs) == 0:
CAMFunctionContext.fail(
message=errorMessage,
error=f"Manufacturing model '{manufacturingModel.name}' has no valid occurrences.",
)
return False
setupInput.models = occs
return True
@classmethod
def _addActiveSelectionOccurrences(
cls,
ui: UserInterface,
errorMessage: str,
setupInput: SetupInput,
) -> bool:
"""
Adds occurrences from the active selection to the setup input.
- Returns True if models were successfully added.
- Returns False if no active selections were found (with errors added
to CAMFunctionContext).
- Returns True if no valid bodies or components were found in the
active selection.
"""
selections: Selections = ui.activeSelections
if selections.count == 0:
CAMFunctionContext.fail(
message=errorMessage,
error="No active selections found.",
)
return False
selectedObjects = []
for selection in selections:
entity = selection.entity
if isinstance(entity, (BRepBody, Occurrence)):
selectedObjects.append(entity)
if len(selectedObjects) == 0:
CAMFunctionContext.warn(
message=errorMessage,
warning="No valid bodies or components found in active selections.",
)
return True # No occurrences/bodies added from selection, but not a failure
setupInput.models = selectedObjects
return True
@classmethod
def _findBodyByName(cls, component: Component, name: str) -> BRepBody | None:
"""
Finds a body by (case-insensitive) name within a component.
"""
for body in component.bRepBodies:
if areMatchingCaseInsensitiveNames(body.name, name):
return body
return None
@classmethod
def _findComponentOccurrenceOrBodyByName(
cls,
occurrence: Occurrence,
name: str,
) -> BRepBody | Occurrence | None:
"""
Find the occurrence of a component or a body by (case-insensitive) name
within the given occurrence recursively.
"""
component: Component = occurrence.component
if areMatchingCaseInsensitiveNames(component.name, name):
return occurrence
body: BRepBody | None = cls._findBodyByName(component, name)
if body:
return body
for childOccurrence in occurrence.childOccurrences:
result = cls._findComponentOccurrenceOrBodyByName(
occurrence=childOccurrence,
name=name,
)
if result:
return result
return None
def extractModelInput(modelInputDict: dict | None) -> ModelInput:
"""
Extracts a ModelInput instance from the given dictionary.
Returns a ModelInput instance with mode NONE if the dictionary is invalid,
or if the mode is invalid.
"""
mode: ModelInputMode = ModelInputMode.NONE
occurrenceName: str | None = None
manufacturingModelName: str | None = None
if isinstance(modelInputDict, dict):
modeStr: str = modelInputDict.get("mode", "none")
try:
mode = ModelInputMode(modeStr)
except ValueError:
mode = ModelInputMode.NONE
if mode != ModelInputMode.NONE:
occurrenceName = modelInputDict.get("occurrence_name")
manufacturingModelName = modelInputDict.get("manufacturing_model_name")
return ModelInput(
mode=mode,
occurrenceName=occurrenceName,
manufacturingModelName=manufacturingModelName,
)