work
update amznchartools
This commit is contained in:
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
|
||||
id = "amzncharactertools"
|
||||
name = "AMZNCharacterTools"
|
||||
tagline = "AMZNCharacterTools"
|
||||
version = "0.10.2"
|
||||
version = "0.11.0"
|
||||
type = "add-on"
|
||||
|
||||
maintainer = "Nathan Lindsay"
|
||||
|
||||
@@ -5,7 +5,7 @@ def add_body_masks():
|
||||
body_obj = bpy.data.objects.get('CC_Base_Body')
|
||||
if not body_obj:
|
||||
print("Error: CC_Base_Body object not found")
|
||||
return
|
||||
return {"success": False, "reason": "NO_BODY"}
|
||||
|
||||
print(f"Found body object: {body_obj.name}")
|
||||
|
||||
@@ -124,6 +124,8 @@ def add_body_masks():
|
||||
print("\nBody masking completed!")
|
||||
print("Main_Mask: Shows head, arms, and chest")
|
||||
print("Hand_Mask: Shows head, arms, chest, and hands")
|
||||
return {"success": True}
|
||||
|
||||
# Execute the operation
|
||||
add_body_masks()
|
||||
if __name__ == "__main__":
|
||||
# When run as a script, execute for side-effects and keep existing prints.
|
||||
add_body_masks()
|
||||
@@ -0,0 +1,10 @@
|
||||
import bpy
|
||||
|
||||
# Simple operator script that removes the 'Devices' custom property from the active pose bone.
|
||||
# Usage: enter Pose Mode, select the Settings pose bone, then click the button in the Devices panel.
|
||||
try:
|
||||
bpy.ops.wm.properties_remove(data_path="active_pose_bone", property_name="Devices")
|
||||
except Exception as e:
|
||||
# This will fail if the context isn't correct; keep it minimal and print for debugging.
|
||||
print(f"Failed to remove 'Devices' property via wm.properties_remove: {e}")
|
||||
|
||||
@@ -8,14 +8,17 @@ print(f"Auto-execution enabled: {bpy.context.preferences.filepaths.use_scripts_a
|
||||
if not bpy.context.preferences.filepaths.use_scripts_auto_execute:
|
||||
print("WARNING: Auto-execution is disabled - drivers may not work properly")
|
||||
|
||||
# Get the active object
|
||||
# Get the active object and selected objects
|
||||
active_obj = bpy.context.active_object
|
||||
selected_objects = bpy.context.selected_objects
|
||||
|
||||
if not active_obj:
|
||||
print("✗ ERROR: No active object selected")
|
||||
print("Please select an object and run the script again")
|
||||
raise Exception("No active object - script aborted")
|
||||
|
||||
print(f"Found active object: {active_obj.name}")
|
||||
print(f"Found {len(selected_objects)} selected object(s): {[obj.name for obj in selected_objects]}")
|
||||
|
||||
# Find the armature object in the scene
|
||||
armature_obj = None
|
||||
@@ -92,61 +95,65 @@ print(f"✓ Created '{property_name}' custom property with library override supp
|
||||
print(f"Setting up drivers for {active_obj.name} object...")
|
||||
|
||||
try:
|
||||
# Clear any existing drivers
|
||||
try:
|
||||
active_obj.driver_remove('hide_render')
|
||||
print(" Removed existing hide_render driver")
|
||||
except:
|
||||
print(" No existing hide_render driver to remove")
|
||||
|
||||
try:
|
||||
active_obj.driver_remove('hide_viewport')
|
||||
print(" Removed existing hide_viewport driver")
|
||||
except:
|
||||
print(" No existing hide_viewport driver to remove")
|
||||
|
||||
# Create hide_render driver (hide when property = False, show when property = True)
|
||||
driver_fcurve = active_obj.driver_add('hide_render')
|
||||
driver = driver_fcurve.driver
|
||||
driver.type = 'SUM'
|
||||
|
||||
var = driver.variables.new()
|
||||
var.name = 'vis_val'
|
||||
var.type = 'SINGLE_PROP'
|
||||
var.targets[0].id_type = 'OBJECT'
|
||||
var.targets[0].id = armature_obj
|
||||
var.targets[0].data_path = f'pose.bones["Settings"]["{property_name}"]'
|
||||
|
||||
# Use polynomial modifier to invert: hide_render = 1 - vis_val
|
||||
mod = driver_fcurve.modifiers.new('GENERATOR')
|
||||
mod.mode = 'POLYNOMIAL'
|
||||
mod.poly_order = 1
|
||||
mod.coefficients = (1.0, -1.0) # 1 - x
|
||||
|
||||
print(f" ✓ Created hide_render driver for {active_obj.name}")
|
||||
|
||||
# Create hide_viewport driver (same logic)
|
||||
driver_fcurve = active_obj.driver_add('hide_viewport')
|
||||
driver = driver_fcurve.driver
|
||||
driver.type = 'SUM'
|
||||
|
||||
var = driver.variables.new()
|
||||
var.name = 'vis_val'
|
||||
var.type = 'SINGLE_PROP'
|
||||
var.targets[0].id_type = 'OBJECT'
|
||||
var.targets[0].id = armature_obj
|
||||
var.targets[0].data_path = f'pose.bones["Settings"]["{property_name}"]'
|
||||
|
||||
# Use polynomial modifier to invert: hide_viewport = 1 - vis_val
|
||||
mod = driver_fcurve.modifiers.new('GENERATOR')
|
||||
mod.mode = 'POLYNOMIAL'
|
||||
mod.poly_order = 1
|
||||
mod.coefficients = (1.0, -1.0) # 1 - x
|
||||
|
||||
print(f" ✓ Created hide_viewport driver for {active_obj.name}")
|
||||
|
||||
# Set up drivers for all selected objects
|
||||
for obj in selected_objects:
|
||||
print(f" Setting up drivers for {obj.name}...")
|
||||
|
||||
# Clear any existing drivers
|
||||
try:
|
||||
obj.driver_remove('hide_render')
|
||||
print(f" Removed existing hide_render driver")
|
||||
except:
|
||||
print(" No existing hide_render driver to remove")
|
||||
|
||||
try:
|
||||
obj.driver_remove('hide_viewport')
|
||||
print(" Removed existing hide_viewport driver")
|
||||
except:
|
||||
print(" No existing hide_viewport driver to remove")
|
||||
|
||||
# Create hide_render driver (hide when property = False, show when property = True)
|
||||
driver_fcurve = obj.driver_add('hide_render')
|
||||
driver = driver_fcurve.driver
|
||||
driver.type = 'SUM'
|
||||
|
||||
var = driver.variables.new()
|
||||
var.name = 'vis_val'
|
||||
var.type = 'SINGLE_PROP'
|
||||
var.targets[0].id_type = 'OBJECT'
|
||||
var.targets[0].id = armature_obj
|
||||
var.targets[0].data_path = f'pose.bones["Settings"]["{property_name}"]'
|
||||
|
||||
# Use polynomial modifier to invert: hide_render = 1 - vis_val
|
||||
mod = driver_fcurve.modifiers.new('GENERATOR')
|
||||
mod.mode = 'POLYNOMIAL'
|
||||
mod.poly_order = 1
|
||||
mod.coefficients = (1.0, -1.0) # 1 - x
|
||||
|
||||
print(f" ✓ Created hide_render driver for {obj.name}")
|
||||
|
||||
# Create hide_viewport driver (same logic)
|
||||
driver_fcurve = obj.driver_add('hide_viewport')
|
||||
driver = driver_fcurve.driver
|
||||
driver.type = 'SUM'
|
||||
|
||||
var = driver.variables.new()
|
||||
var.name = 'vis_val'
|
||||
var.type = 'SINGLE_PROP'
|
||||
var.targets[0].id_type = 'OBJECT'
|
||||
var.targets[0].id = armature_obj
|
||||
var.targets[0].data_path = f'pose.bones["Settings"]["{property_name}"]'
|
||||
|
||||
# Use polynomial modifier to invert: hide_viewport = 1 - vis_val
|
||||
mod = driver_fcurve.modifiers.new('GENERATOR')
|
||||
mod.mode = 'POLYNOMIAL'
|
||||
mod.poly_order = 1
|
||||
mod.coefficients = (1.0, -1.0) # 1 - x
|
||||
|
||||
print(f" ✓ Created hide_viewport driver for {obj.name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error creating {active_obj.name} drivers: {e}")
|
||||
print(f" Error creating drivers: {e}")
|
||||
|
||||
print("✓ Driver setup complete")
|
||||
|
||||
@@ -159,28 +166,31 @@ bpy.context.evaluated_depsgraph_get().update()
|
||||
|
||||
# Initial state (should be visible)
|
||||
print(f"Current state: {property_name} = {pose_bone[property_name]} (True=visible, False=hidden)")
|
||||
print(f" {active_obj.name}: hide_render={active_obj.hide_render}, hide_viewport={active_obj.hide_viewport}")
|
||||
for obj in selected_objects:
|
||||
print(f" {obj.name}: hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
|
||||
|
||||
# Test toggle to False (object hidden)
|
||||
print(f"\nToggling to False ({active_obj.name} hidden)...")
|
||||
# Test toggle to False (objects hidden)
|
||||
print(f"\nToggling to False (objects hidden)...")
|
||||
pose_bone[property_name] = False
|
||||
bpy.context.view_layer.update()
|
||||
bpy.context.evaluated_depsgraph_get().update()
|
||||
|
||||
print(f"New state: {property_name} = {pose_bone[property_name]}")
|
||||
print(f" {active_obj.name}: hide_render={active_obj.hide_render}, hide_viewport={active_obj.hide_viewport}")
|
||||
for obj in selected_objects:
|
||||
print(f" {obj.name}: hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
|
||||
|
||||
# Test toggle back to True (object visible)
|
||||
print(f"\nToggling to True ({active_obj.name} visible)...")
|
||||
# Test toggle back to True (objects visible)
|
||||
print(f"\nToggling to True (objects visible)...")
|
||||
pose_bone[property_name] = True
|
||||
bpy.context.view_layer.update()
|
||||
bpy.context.evaluated_depsgraph_get().update()
|
||||
|
||||
print(f"Final state: {property_name} = {pose_bone[property_name]}")
|
||||
print(f" {active_obj.name}: hide_render={active_obj.hide_render}, hide_viewport={active_obj.hide_viewport}")
|
||||
for obj in selected_objects:
|
||||
print(f" {obj.name}: hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
|
||||
|
||||
print("\n✓ Custom Visibility script completed successfully!")
|
||||
print(f"The '{property_name}' property is now available on the Settings bone as a checkbox")
|
||||
print(f"Use checkbox to toggle {active_obj.name} visibility")
|
||||
print(f" - {property_name} ON (checked): Shows {active_obj.name}")
|
||||
print(f" - {property_name} OFF (unchecked): Hides {active_obj.name}")
|
||||
print(f"Use checkbox to toggle visibility of {len(selected_objects)} selected object(s): {[obj.name for obj in selected_objects]}")
|
||||
print(f" - {property_name} ON (checked): Shows all selected objects")
|
||||
print(f" - {property_name} OFF (unchecked): Hides all selected objects")
|
||||
@@ -53,9 +53,16 @@ def link_bsdf_materials():
|
||||
materials_before = set(bpy.data.materials.keys())
|
||||
|
||||
# Link all materials from the library file
|
||||
with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):
|
||||
# Link all materials
|
||||
data_to.materials = data_from.materials
|
||||
try:
|
||||
with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):
|
||||
# Link all materials
|
||||
data_to.materials = data_from.materials
|
||||
print(f" Library loaded successfully, materials in file: {len(data_from.materials)}")
|
||||
except Exception as e:
|
||||
print(f"Error loading library: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return []
|
||||
|
||||
# Get list of newly linked materials
|
||||
materials_after = set(bpy.data.materials.keys())
|
||||
@@ -368,6 +375,8 @@ def replace_cel_materials():
|
||||
"wood": "Pallet_Wood",
|
||||
"Package_Cardboard": "Package_Cardboard",
|
||||
"Pallet_Wood": "Pallet_Wood",
|
||||
"Shuttle_Cardboard_1": "Shuttle_Cardboard_1",
|
||||
"Shuttle_Cardboard_2": "Shuttle_Cardboard_2",
|
||||
"blue (triton)": "BSDF_blue-2_TRITON",
|
||||
"gray (snow)": "BSDF_gray-6_SNOW",
|
||||
"gray (storm)": "BSDF_gray-2_STORM",
|
||||
@@ -608,6 +617,13 @@ def replace_cel_materials():
|
||||
print(f"Total user remappings: {replacements_made}")
|
||||
print(f"Source materials removed: {removed_materials}")
|
||||
|
||||
# Print a highly visible summary message
|
||||
print(f"\n{'='*60}")
|
||||
print(f" CEL REPLACEMENT COMPLETE")
|
||||
print(f" {len(material_mapping)} materials replaced")
|
||||
print(f" {replacements_made} total users remapped")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
if missing_targets:
|
||||
print(f"\nMissing target materials for:")
|
||||
for mapping in missing_targets:
|
||||
@@ -618,19 +634,19 @@ def replace_cel_materials():
|
||||
# Run the replacement
|
||||
if __name__ == "__main__":
|
||||
replace_cel_materials()
|
||||
|
||||
print("\nRemaining CEL materials in file:")
|
||||
cel_count = 0
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("CEL_"):
|
||||
print(f" {mat.name} ({mat.users} users)")
|
||||
cel_count += 1
|
||||
|
||||
if cel_count == 0:
|
||||
print(" None - all CEL materials have been replaced!")
|
||||
|
||||
print("\nBSDF materials in file:")
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("BSDF_"):
|
||||
print(f" {mat.name} ({mat.users} users)")
|
||||
|
||||
print("\nRemaining CEL materials in file:")
|
||||
cel_count = 0
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("CEL_"):
|
||||
print(f" {mat.name} ({mat.users} users)")
|
||||
cel_count += 1
|
||||
|
||||
if cel_count == 0:
|
||||
print(" None - all CEL materials have been replaced!")
|
||||
|
||||
print("\nBSDF materials in file:")
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("BSDF_"):
|
||||
print(f" {mat.name} ({mat.users} users)")
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
"""Add Ambassador Vest Color to the active object's vest color node group."""
|
||||
import bpy
|
||||
import os
|
||||
|
||||
try:
|
||||
from ..utils import get_addon_preferences
|
||||
except (ImportError, ValueError):
|
||||
# Fallback if import fails
|
||||
def get_addon_preferences():
|
||||
test_names = [
|
||||
"bl_ext.vscode_development.AmazonCharacterTools",
|
||||
"amzncharactertools",
|
||||
"AmazonCharacterTools",
|
||||
]
|
||||
for addon_name in test_names:
|
||||
addon_prefs = bpy.context.preferences.addons.get(addon_name)
|
||||
if addon_prefs and hasattr(addon_prefs, 'preferences'):
|
||||
if hasattr(addon_prefs.preferences, 'amzn_bsdf_materials_path'):
|
||||
return addon_prefs.preferences
|
||||
return None
|
||||
|
||||
|
||||
def add_vest_ambassador_color():
|
||||
"""Add blue (ambassador) vest color option to the active object's vest color node group."""
|
||||
|
||||
# Get active object
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
print("Error: No active object selected")
|
||||
return {"success": False, "reason": "NO_ACTIVE_OBJECT"}
|
||||
|
||||
print(f"Working on object: {obj.name}")
|
||||
|
||||
# Find the amazon-vest-color node group on the object
|
||||
vest_node_group = None
|
||||
for mod in obj.modifiers:
|
||||
if mod.type == 'NODES' and mod.node_group:
|
||||
if 'amazon-vest-color' in mod.node_group.name.lower():
|
||||
vest_node_group = mod.node_group
|
||||
print(f"Found vest color node group: {vest_node_group.name}")
|
||||
break
|
||||
|
||||
if not vest_node_group:
|
||||
print("Error: No amazon-vest-color node group found on active object")
|
||||
print("Available geometry node groups:")
|
||||
for mod in obj.modifiers:
|
||||
if mod.type == 'NODES' and mod.node_group:
|
||||
print(f" - {mod.node_group.name}")
|
||||
return {"success": False, "reason": "NO_NODE_GROUP"}
|
||||
|
||||
# Link the Vest-blue material from MAT_Char.blend
|
||||
library_path = r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Mat\MAT_Char.blend"
|
||||
|
||||
if not os.path.exists(library_path):
|
||||
print(f"Error: Library file not found at {library_path}")
|
||||
return {"success": False, "reason": "LIBRARY_NOT_FOUND"}
|
||||
|
||||
print(f"Linking Vest-blue material from: {library_path}")
|
||||
|
||||
# Check if material already exists
|
||||
materials_before = set(bpy.data.materials.keys())
|
||||
|
||||
try:
|
||||
with bpy.data.libraries.load(library_path, link=True) as (data_from, data_to):
|
||||
if 'Vest-blue' in data_from.materials:
|
||||
data_to.materials = ['Vest-blue']
|
||||
print(" Vest-blue material linked successfully")
|
||||
else:
|
||||
print(" Warning: Vest-blue not found in library, trying alternate names...")
|
||||
# Try to find any vest-related material
|
||||
for mat_name in data_from.materials:
|
||||
if 'vest' in mat_name.lower() and 'blue' in mat_name.lower():
|
||||
data_to.materials = [mat_name]
|
||||
print(f" Found and linked alternate: {mat_name}")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error loading library: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {"success": False, "reason": "LIBRARY_LOAD_FAILED"}
|
||||
|
||||
# Get the linked material
|
||||
materials_after = set(bpy.data.materials.keys())
|
||||
newly_linked = materials_after - materials_before
|
||||
|
||||
if not newly_linked:
|
||||
# Check if Vest-blue already exists
|
||||
if 'Vest-blue' in bpy.data.materials:
|
||||
vest_material = bpy.data.materials['Vest-blue']
|
||||
print(f"Vest-blue already exists: {vest_material.name}")
|
||||
else:
|
||||
print("Error: Could not link Vest-blue material")
|
||||
return {"success": False, "reason": "MATERIAL_NOT_FOUND"}
|
||||
else:
|
||||
vest_material = bpy.data.materials[list(newly_linked)[0]]
|
||||
print(f"Linked material: {vest_material.name}")
|
||||
|
||||
# Find the menu switch node in the node group
|
||||
# This is typically a node with an enum property or index-based switch
|
||||
menu_switch_node = None
|
||||
for node in vest_node_group.nodes:
|
||||
# Look for common menu/switch node types
|
||||
if node.type in ('MENU_SWITCH', 'MENU_SWITCH_ITEM', 'SWITCH', 'INDEX_SWITCH'):
|
||||
menu_switch_node = node
|
||||
print(f"Found menu switch node: {node.name} (type: {node.type})")
|
||||
break
|
||||
# Also check for nodes with "color" or "menu" in the name
|
||||
if 'color' in node.name.lower() or 'menu' in node.name.lower():
|
||||
print(f"Potential menu node: {node.name} (type: {node.type})")
|
||||
if not menu_switch_node:
|
||||
menu_switch_node = node
|
||||
|
||||
if not menu_switch_node:
|
||||
print("Error: Could not find menu/switch node in vest color node group")
|
||||
print("Available nodes:")
|
||||
for node in vest_node_group.nodes:
|
||||
print(f" - {node.name} (type: {node.type})")
|
||||
return {"success": False, "reason": "NO_MENU_SWITCH"}
|
||||
|
||||
node_type = menu_switch_node.type
|
||||
print(f"Adding 'blue (ambassador)' option to {menu_switch_node.name} (type: {node_type})...")
|
||||
added = False
|
||||
item_name = "blue (ambassador)"
|
||||
|
||||
if node_type == "MENU_SWITCH" and hasattr(menu_switch_node, "enum_items"):
|
||||
existing_names = [item.name for item in menu_switch_node.enum_items]
|
||||
if item_name in existing_names:
|
||||
print(" Option 'blue (ambassador)' already exists")
|
||||
added = True
|
||||
else:
|
||||
try:
|
||||
# Blender API for GeometryNodeMenuSwitch: enum_items.new(name)
|
||||
menu_switch_node.enum_items.new(item_name)
|
||||
print(" Added enum item to menu switch")
|
||||
added = True
|
||||
except Exception as e:
|
||||
print(f" Failed to add enum item: {e}")
|
||||
elif node_type in {"SWITCH", "INDEX_SWITCH"}:
|
||||
print(f" Switch node type '{node_type}' is not a geometry menu switch; cannot add named menu item")
|
||||
else:
|
||||
print(f" Node type '{node_type}' does not support enum menu items")
|
||||
|
||||
# For MATERIAL menu switches, assign the linked material to the corresponding input socket default.
|
||||
if added:
|
||||
for socket in menu_switch_node.inputs:
|
||||
if socket.name == item_name and hasattr(socket, "default_value"):
|
||||
try:
|
||||
socket.default_value = vest_material
|
||||
print(f" Assigned material '{vest_material.name}' to input '{socket.name}'")
|
||||
except Exception as e:
|
||||
print(f" Warning: could not assign material to '{socket.name}': {e}")
|
||||
break
|
||||
|
||||
if added:
|
||||
print("\n" + "="*60)
|
||||
print(" VEST AMBASSADOR COLOR ADDED")
|
||||
print(f" Material: {vest_material.name}")
|
||||
print(f" Target: {obj.name}")
|
||||
print("="*60 + "\n")
|
||||
else:
|
||||
print("\n" + "="*60)
|
||||
print(" WARNING: Could not add menu item")
|
||||
print(f" Material: {vest_material.name} was linked but not assigned")
|
||||
print(" The menu API may need manual configuration")
|
||||
print("="*60 + "\n")
|
||||
|
||||
if added:
|
||||
return {"success": True, "reason": "OK"}
|
||||
return {"success": False, "reason": "MENU_ITEM_ADD_FAILED"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
add_vest_ambassador_color()
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Operator definitions for AMZN Character Tools."""
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
import runpy
|
||||
import traceback
|
||||
@@ -8,6 +9,7 @@ from bpy.types import Operator
|
||||
|
||||
|
||||
OPS_DIR = Path(__file__).parent.parent / "ops"
|
||||
BASE_PACKAGE = (__package__ or "").rsplit(".", 1)[0]
|
||||
|
||||
|
||||
def run_script(script_name: str) -> None:
|
||||
@@ -25,6 +27,15 @@ def run_script(script_name: str) -> None:
|
||||
runpy.run_path(str(script_path), run_name="__main__")
|
||||
|
||||
|
||||
def _import_ops_module(module_name: str):
|
||||
"""Import and reload an ops module from this addon package."""
|
||||
if not BASE_PACKAGE:
|
||||
raise RuntimeError("Cannot resolve addon base package for module imports")
|
||||
full_name = f"{BASE_PACKAGE}.ops.{module_name}"
|
||||
module = importlib.import_module(full_name)
|
||||
return importlib.reload(module)
|
||||
|
||||
|
||||
# Icon mapping from old indices to icon names
|
||||
ICON_MAP = {
|
||||
144: "PREFERENCES", # Settings/configuration operations
|
||||
@@ -86,6 +97,15 @@ OP_SPECS = [
|
||||
"icon": "FILE_REFRESH",
|
||||
"panel": "devices",
|
||||
},
|
||||
{
|
||||
"name": "RemoveDevicesSettings",
|
||||
"id": "remove_devices_settings",
|
||||
"desc": "Removes the 'Devices' custom property from SettingsBone",
|
||||
"script": "RemoveDevicesSettings.py",
|
||||
"button": "Remove Devices Settings",
|
||||
"icon": "CANCEL",
|
||||
"panel": "devices",
|
||||
},
|
||||
{
|
||||
"name": "GeoSeparator",
|
||||
"id": "geo_separator",
|
||||
@@ -122,6 +142,15 @@ OP_SPECS = [
|
||||
"icon": "PREFERENCES",
|
||||
"panel": "geo",
|
||||
},
|
||||
{
|
||||
"name": "AddVestAmbassadorColor",
|
||||
"id": "add_vest_ambassador_color",
|
||||
"desc": "Adds blue (ambassador) vest color option to active object's vest color node group",
|
||||
"script": "vest_ambassador_color.py",
|
||||
"button": "Add Vest Ambassador Color",
|
||||
"icon": "MATERIAL",
|
||||
"panel": "vest",
|
||||
},
|
||||
{
|
||||
"name": "HHSpawn",
|
||||
"id": "hh_spawn",
|
||||
@@ -192,7 +221,65 @@ def _make_operator(spec: dict) -> type[Operator]:
|
||||
"""Create an operator class from a specification dictionary."""
|
||||
def _execute(self, context):
|
||||
try:
|
||||
run_script(spec["script"])
|
||||
# Special handling for operators that need result capture
|
||||
if spec["script"] == "replace_cel_with_bsdf.py":
|
||||
script_path = OPS_DIR / spec["script"]
|
||||
if script_path.exists():
|
||||
module = _import_ops_module("replace_cel_with_bsdf")
|
||||
materials_mapped, users_remapped = module.replace_cel_materials()
|
||||
self.report({"INFO"}, f"Replaced CEL: {materials_mapped} materials, {users_remapped} users remapped")
|
||||
else:
|
||||
run_script(spec["script"])
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
elif spec["script"] == "vest_ambassador_color.py":
|
||||
script_path = OPS_DIR / spec["script"]
|
||||
if script_path.exists():
|
||||
module = _import_ops_module("vest_ambassador_color")
|
||||
result = module.add_vest_ambassador_color()
|
||||
success = bool(result.get("success")) if isinstance(result, dict) else bool(result)
|
||||
reason = result.get("reason") if isinstance(result, dict) else ""
|
||||
if success:
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
else:
|
||||
if reason == "NO_NODE_GROUP":
|
||||
self.report({"INFO"}, "No vest color node group found on active object")
|
||||
elif reason == "NO_ACTIVE_OBJECT":
|
||||
self.report({"INFO"}, "No active object selected")
|
||||
elif reason == "NO_MENU_SWITCH":
|
||||
self.report({"INFO"}, "No vest menu switch node found in vest color node group")
|
||||
else:
|
||||
self.report({"WARNING"}, "Vest-blue material linked but menu item was not added")
|
||||
else:
|
||||
run_script(spec["script"])
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
elif spec["script"] == "BodyMasker.py":
|
||||
script_path = OPS_DIR / spec["script"]
|
||||
if script_path.exists():
|
||||
module = _import_ops_module("BodyMasker")
|
||||
result = module.add_body_masks()
|
||||
if isinstance(result, dict):
|
||||
if result.get("success"):
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
else:
|
||||
reason = result.get("reason")
|
||||
if reason == "NO_BODY":
|
||||
self.report({"ERROR"}, "CC_Base_Body not found in scene")
|
||||
else:
|
||||
self.report({"WARNING"}, f"{spec['button']} incomplete: {reason}")
|
||||
else:
|
||||
# Unexpected return type; fall back to running the script for side-effects
|
||||
run_script(spec["script"])
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
elif spec.get("id") == "remove_devices_settings":
|
||||
# Pre-check: ensure CC_Base_Body exists in the scene. If not, inform the user.
|
||||
if "CC_Base_Body" not in bpy.data.objects:
|
||||
self.report({"ERROR"}, "CC_Base_Body not found in scene")
|
||||
return {"CANCELLED"}
|
||||
run_script(spec["script"])
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
else:
|
||||
run_script(spec["script"])
|
||||
self.report({"INFO"}, f"{spec['button']} complete")
|
||||
except Exception as exc: # pragma: no cover - best effort logging
|
||||
traceback.print_exc()
|
||||
self.report({"ERROR"}, f"{spec['button']} failed: {exc}")
|
||||
|
||||
@@ -5,7 +5,7 @@ from bpy.types import Panel
|
||||
from .operators import OP_SPECS
|
||||
|
||||
|
||||
PANEL_KEYS = ("scene", "general", "core", "devices", "geo", "helmet")
|
||||
PANEL_KEYS = ("scene", "general", "core", "devices", "geo", "helmet", "vest")
|
||||
PANEL_BUTTONS = {key: [spec for spec in OP_SPECS if spec["panel"] == key] for key in PANEL_KEYS}
|
||||
|
||||
|
||||
@@ -87,5 +87,13 @@ class AMZN_PT_Helmet(_AMZN_BasePanel):
|
||||
panel_key = "helmet"
|
||||
|
||||
|
||||
PANEL_CLASSES = (AMZN_PT_Main, AMZN_PT_Scene, AMZN_PT_General, AMZN_PT_Devices, AMZN_PT_Geo, AMZN_PT_Helmet)
|
||||
class AMZN_PT_Vest(_AMZN_BasePanel):
|
||||
"""Vest panel."""
|
||||
bl_idname = "AMZN_PT_VEST"
|
||||
bl_label = "Vest"
|
||||
bl_parent_id = "AMZN_PT_MAIN"
|
||||
panel_key = "vest"
|
||||
|
||||
|
||||
PANEL_CLASSES = (AMZN_PT_Main, AMZN_PT_Scene, AMZN_PT_General, AMZN_PT_Devices, AMZN_PT_Geo, AMZN_PT_Helmet, AMZN_PT_Vest)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class AMZN_AddonPreferences(AddonPreferences):
|
||||
name="BSDF Materials Library",
|
||||
description="Path to MATERIALS_BSDF_pallette_v1.0.blend",
|
||||
subtype='FILE_PATH',
|
||||
default=r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\MATERIALS_BSDF_pallette_v1.0.blend",
|
||||
default=r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Mat\MATERIALS_BSDF_pallette_v1.0.blend",
|
||||
)
|
||||
|
||||
amzn_device_path: StringProperty(
|
||||
|
||||
Reference in New Issue
Block a user