2026-01-01

This commit is contained in:
2026-03-17 15:16:34 -06:00
parent ec4cf523fb
commit b80274187b
263 changed files with 95164 additions and 3848 deletions
@@ -0,0 +1,6 @@
"""UI module for AMZN Character Tools."""
from .operators import OPERATOR_CLASSES
from .panels import PANEL_CLASSES
__all__ = ["OPERATOR_CLASSES", "PANEL_CLASSES"]
@@ -0,0 +1,225 @@
"""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": "WhiteWorld",
"id": "white_world",
"desc": "Removes Dual Node Background world and replaces with pure white world",
"script": "white_world.py",
"button": "White World",
"icon": "WORLD",
"panel": "scene",
},
{
"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]
@@ -0,0 +1,91 @@
"""Panel definitions for AMZN Character Tools."""
import bpy
from bpy.types import Panel
from .operators import OP_SPECS
PANEL_KEYS = ("scene", "general", "core", "devices", "geo", "helmet")
PANEL_BUTTONS = {key: [spec for spec in OP_SPECS if spec["panel"] == key] for key in PANEL_KEYS}
class _AMZN_BasePanel(Panel):
"""Base panel class for AMZN Character Tools."""
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Rigging"
panel_key = ""
@classmethod
def poll(cls, context):
return True
def draw(self, context):
layout = self.layout
for spec in PANEL_BUTTONS.get(self.panel_key, ()):
# Make buttons marked as "large" bigger
if spec.get("large", False):
row = layout.row()
row.scale_y = 2.0
row.operator(
spec["full_idname"],
text=spec["button"],
icon=spec["icon"],
)
else:
layout.operator(
spec["full_idname"],
text=spec["button"],
icon=spec["icon"],
)
class AMZN_PT_Main(_AMZN_BasePanel):
"""Main panel for AMZN Character Tools."""
bl_idname = "AMZN_PT_MAIN"
bl_label = "AMZN Character Tools"
panel_key = "core"
class AMZN_PT_Scene(_AMZN_BasePanel):
"""Scene panel."""
bl_idname = "AMZN_PT_SCENE"
bl_label = "Scene"
bl_parent_id = "AMZN_PT_MAIN"
panel_key = "scene"
class AMZN_PT_General(_AMZN_BasePanel):
"""General panel."""
bl_idname = "AMZN_PT_GENERAL"
bl_label = "General"
bl_parent_id = "AMZN_PT_MAIN"
panel_key = "general"
class AMZN_PT_Devices(_AMZN_BasePanel):
"""Devices panel."""
bl_idname = "AMZN_PT_DEVICES"
bl_label = "Devices"
bl_parent_id = "AMZN_PT_MAIN"
panel_key = "devices"
class AMZN_PT_Geo(_AMZN_BasePanel):
"""GEO panel."""
bl_idname = "AMZN_PT_GEO"
bl_label = "GEO"
bl_parent_id = "AMZN_PT_MAIN"
panel_key = "geo"
class AMZN_PT_Helmet(_AMZN_BasePanel):
"""Helmet panel."""
bl_idname = "AMZN_PT_HELMET"
bl_label = "Helmet"
bl_parent_id = "AMZN_PT_MAIN"
panel_key = "helmet"
PANEL_CLASSES = (AMZN_PT_Main, AMZN_PT_Scene, AMZN_PT_General, AMZN_PT_Devices, AMZN_PT_Geo, AMZN_PT_Helmet)