"""Operator definitions for AMZN Character Tools.""" from pathlib import Path import runpy import traceback import bpy from bpy.types import Operator OPS_DIR = Path(__file__).parent.parent / "ops" def run_script(script_name: str) -> None: """Execute a script from the ops directory. Args: script_name: Name of the script file to execute (e.g., "SettingsBone.py") Raises: FileNotFoundError: If the script file doesn't exist """ script_path = OPS_DIR / script_name if not script_path.exists(): raise FileNotFoundError(f"Missing script: {script_path}") runpy.run_path(str(script_path), run_name="__main__") # Icon mapping from old indices to icon names ICON_MAP = { 144: "PREFERENCES", # Settings/configuration operations 125: "WORLD", # World/environment operations 475: "MODIFIER_DATA", # Modifier operations 415: "CON_OBJECTSOLVER", # Spawn/add operations 630: "FILE_REFRESH", # Replace/refresh operations 785: "OUTLINER_COLLECTION", # Collection/separator operations 453: "MODIFIER_DATA", # Mask operations 66: "CON_OBJECTSOLVER", # Target selection operations 186: "SHAPEKEY_DATA", # Shapekey operations } OP_SPECS = [ { "name": "SpawnSettingsBone", "id": "spawn_settings_bone", "desc": "Spawns SettingsBone within active armature", "script": "SettingsBone.py", "button": "Spawn Settings Bone", "icon": "PREFERENCES", "panel": "general", "large": True, }, { "name": "ApplySubdivWgt", "id": "apply_subdiv_wgt", "desc": "Apply all subdivision modifiers to WGT objects, so blender can draw them properly on the rig.", "script": "apply_subdiv_wgt.py", "button": "Apply Subdiv to WGTs", "icon": "MOD_SUBSURF", "panel": "general", }, { "name": "FreshDevices", "id": "fresh_devices", "desc": "Spawns, places, and parents new Device and Finger Scanner to active armature", "script": "Devices_FreshPlacement.py", "button": "Spawn/Parent Devices", "icon": "CON_OBJECTSOLVER", "panel": "devices", }, { "name": "DevicesSettings", "id": "devices_settings", "desc": "Applies devices function to SettingsBone", "script": "DevicesSettings.py", "button": "DevicesSettings", "icon": "PREFERENCES", "panel": "devices", }, { "name": "DeviceReplace", "id": "device_replace", "desc": "Replaces old device with the new version", "script": "Device_Replacement.py", "button": "ReplaceDevice", "icon": "FILE_REFRESH", "panel": "devices", }, { "name": "GeoSeparator", "id": "geo_separator", "desc": "All child geometry of active armature to GEO collection", "script": "GeoSeparator.py", "button": "GEO Separator", "icon": "COLLECTION_COLOR_02", "panel": "geo", }, { "name": "BodyMasker", "id": "body_masker", "desc": "Separates key body parts", "script": "BodyMasker.py", "button": "Body Masker", "icon": "MOD_MASK", "panel": "geo", }, { "name": "MaskSettings", "id": "mask_settings", "desc": "Creates custom properties for masking the gloves", "script": "MaskSettings.py", "button": "Glove Mask Settings", "icon": "PREFERENCES", "panel": "geo", }, { "name": "CustomVis", "id": "custom_vis", "desc": "Creates a visibility property toggle for the active object", "script": "custom_vis.py", "button": "Custom Visibility Setting", "icon": "PREFERENCES", "panel": "geo", }, { "name": "HHSpawn", "id": "hh_spawn", "desc": "HardHat Spawn/Parent", "script": "hh_spawn.py", "button": "Spawn/Parent HardHat", "icon": "CON_OBJECTSOLVER", "panel": "helmet", }, { "name": "HHSetTargets", "id": "hh_set_targets", "desc": "Set HardHat Hair Targets", "script": "hh_set_targets.py", "button": "Set HH Hair Targets", "icon": "EYEDROPPER", "panel": "helmet", }, { "name": "HHMask", "id": "hh_mask", "desc": "HardHat Mask", "script": "hh_mask.py", "button": "HardHat Mask", "icon": "MODIFIER_DATA", "panel": "helmet", }, { "name": "HHShapekey", "id": "hh_shapekey", "desc": "HardHat Shapekey", "script": "hh_shapekey.py", "button": "HardHat Shapekey", "icon": "SHAPEKEY_DATA", "panel": "helmet", }, { "name": "HHSettings", "id": "hh_settings", "desc": "HardHat Settings", "script": "hh_settings.py", "button": "HardHat Settings", "icon": "PREFERENCES", "panel": "helmet", }, { "name": "ReplaceCelWithBsdf", "id": "replace_cel_with_bsdf", "desc": "Replace all CEL materials with their BSDF counterparts", "script": "replace_cel_with_bsdf.py", "button": "Replace CEL with BSDF", "icon": "MATERIAL", "panel": "scene", }, { "name": "RemapVectorFonts", "id": "remap_vector_fonts", "desc": "Remap all Vector Fonts in the blendfile to Amazon Ember Heavy", "script": "remap_vector_fonts.py", "button": "Remap Vector Fonts", "icon": "FILE_FONT", "panel": "scene", }, ] def _make_operator(spec: dict) -> type[Operator]: """Create an operator class from a specification dictionary.""" def _execute(self, context): try: run_script(spec["script"]) except Exception as exc: # pragma: no cover - best effort logging traceback.print_exc() self.report({"ERROR"}, f"{spec['button']} failed: {exc}") return {"CANCELLED"} return {"FINISHED"} attrs = { "__module__": __name__, "bl_idname": f"amzn.{spec['id']}", "bl_label": f"AMZN_{spec['name']}", "bl_description": spec["desc"], "bl_options": {"REGISTER", "UNDO"}, "execute": _execute, } cls = type(f"AMZN_OT_{spec['name']}", (Operator,), attrs) spec["full_idname"] = cls.bl_idname return cls OPERATOR_CLASSES = [_make_operator(spec) for spec in OP_SPECS]