update amznchartools
This commit is contained in:
2026-03-18 18:03:21 -06:00
parent eea7cc1969
commit 9fcddeca02
26 changed files with 461 additions and 110 deletions
+8 -8
View File
@@ -10,13 +10,13 @@ D:\Work\9 iClone\Amazon\
D:\Amazon\00_external-files\
N:\1. CHARACTERS\remapping\
[Recent]
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Scenes\textures\AMZ-warehouse_BSDF\SLAM_0\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\
C:\Users\Nathan\AppData\Local\Temp\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\
T:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\blendcache_Visual_new_final\drinkfluid\
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\stills\Waterspider A\
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Deliverable\WSA\
A:\1 Amazon_Active_Projects\0 AssetArchive\
A:\1 Amazon_Active_Projects\1 BlenderAssets\
E:\SteamLibrary\steamapps\common\Blender\5.0\scripts\startup\
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Deliverable\
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\stills\
C:\Users\Nathan\Desktop\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Mat\
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\
P:\260217_Jarvis-Defense\Assets\Blends\Char\
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\
+40 -5
View File
@@ -1,4 +1,44 @@
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 1a_part1.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 1a_part2.blend
T:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final.blend
P:\250827_FestivalTurf\Blends\animations\07 Final Touches And Maintenance\Visual_new_final.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\animations\Waterspider A\WS_A_move go-cart from trailer A.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\stills\Waterspider A\WS_A_go-cart loading_e_red.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\stills\Waterspider A\WS_A_go-cart loading_f_red.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\stills\Waterspider A\WS_A_go-cart loading_f.blend
A:\1 Amazon_Active_Projects\260206_Dock_Unified\Blends\stills\Waterspider A\WS_A_go-cart loading_e.blend
C:\Users\Nathan\Desktop\Untitled.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Regina_v4.3.blend
T:\1 BlenderAssets\Amazon\Char\Cartoon2\AS\Paul_v3.4.blend
T:\1 BlenderAssets\Amazon\Char\Cartoon2\AS\Kennedy_v3.3.blend
T:\1 BlenderAssets\Amazon\Char\Cartoon1\Regina_v4.3.blend
T:\1 BlenderAssets\Amazon\Char\Cartoon1\Hailey_v4.3.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 10a.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Chan_v4.3.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Manny_v4.3.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Dennis_v4.3.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Joe_v4.3.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Char\Cartoon1\Kirk_v4.4.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Mat\MAT_Char.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 8c_part2.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 4B.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 3C.blend
A:\1 Amazon_Active_Projects\260206_PAE_2026\Blends\animations\PAE_Animation 3B.blend
A:\1 Amazon_Active_Projects\260225_Problem-Solve_2026\Blends\animations\PS_2026_animation 5G.blend
A:\1 Amazon_Active_Projects\260225_Problem-Solve_2026\Blends\animations\PS_2026_animation 5E.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 2f.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Mat\MATERIALS_BSDF_pallette_v1.0.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Scenes\AMZ-warehouse_v6.1_small.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Scenes\AMZ-warehouse_v5.1.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Scenes\AMZ-warehouse_BSDF_v4.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\3075 Decade Dr.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v3.0.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 2e.blend
C:\Users\Nathan\Downloads\noncon_OOG_short_animation 2d.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 2d.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 9a.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 2a.blend
T:\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 9a.blend
A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\Props\Dog-Food-Bag.blend
A:\1 Amazon_Active_Projects\260317_NONCON_2026\Blends\animations\noncon_OOG_short_animation 3d.blend
@@ -30,7 +70,6 @@ C:\Users\Nathan\Downloads\PnS_BD2_animation 6d\UNC_NEXUS_amazon\1 Amazon_Active_
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 6a.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 6c.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 6b.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 8c_part2.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 11a.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 9b.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 7c.blend
@@ -56,7 +95,6 @@ A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_an
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 11b.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 10b_part3.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 10b_part2.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 10a.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 10b_part1.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_4.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_3_retimed.blend
@@ -71,8 +109,6 @@ P:\260217_Jarvis-Defense\Blends\animations\closeup.blend
F:\jobs\Shot5c\Shot_Q.flamenco.blend
F:\jobs\Shot5b\Shot_Q.flamenco.blend
F:\jobs\Shot5a\Shot_Q.flamenco.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_Q.blend
P:\260217_Jarvis-Defense\Assets\Blends\3075 Decade Dr.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\stills\iP&S_BD2_11b.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 3a_part1.blend
A:\1 Amazon_Active_Projects\260311_PnS-Beyond-Day-2\Blends\animations\P&S_BD2_animation 3a.blend
@@ -94,7 +130,6 @@ P:\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v3.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Russell_v4.0.blend
P:\260217_Jarvis-Defense\Blends\animations\Shot_3.blend
T:\260217_Jarvis-Defense\Assets\Blends\Char\Chase_v2.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v3.0.blend
P:\260217_Jarvis-Defense\Assets\Blends\Char\Priest_v2.0.blend
D:\Work\9 iClone\Jarvis Defense\Blender\Dec_v5.blend
C:\Users\Nathan\Desktop\Shot5c.blend
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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(
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long