Files
blender-portable-repo/extensions/user_default/amzncharactertools/__init__.py
T
2026-03-17 14:58:51 -06:00

2042 lines
94 KiB
Python

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
bl_info = {
"name" : "AMZNCharacterTools",
"author" : "Nathan Lindsay",
"description" : "",
"blender" : (4, 5, 0),
"version" : (0, 6, 3),
"location" : "View3D > Rigging",
"warning" : "",
"doc_url": "",
"tracker_url": "",
"category" : "3D View"
}
import bpy
import bpy.utils.previews
addon_keymaps = {}
_icons = None
class SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D(bpy.types.Panel):
bl_label = 'AMZN Character Tools'
bl_idname = 'SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ''
bl_category = 'Rigging'
bl_order = 0
bl_ui_units_x=0
@classmethod
def poll(cls, context):
return not (False)
def draw_header(self, context):
layout = self.layout
def draw(self, context):
layout = self.layout
op = layout.operator('sna.amzsetttings_bone_f0618', text='Spawn Settings Bone', icon_value=144, emboss=True, depress=False)
op = layout.operator('sna.amzwhite_world_b90b2', text='White World', icon_value=125, emboss=True, depress=False)
op = layout.operator('sna.amzapply_subdiv_wgt_9df87', text='Apply Subdiv to WGTs', icon_value=475, emboss=True, depress=False)
class SNA_OT_Amzfresh_Devices_36Cdc(bpy.types.Operator):
bl_idname = "sna.amzfresh_devices_36cdc"
bl_label = "amz.fresh_devices"
bl_description = "Spawns, places, and parents new Device and Finger Scanner to active armature"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
import math
def append_and_parent_device():
# Append the Device asset
device_blend_path = r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\device_v2.blend"
# Append the Device object
with bpy.data.libraries.load(device_blend_path, link=False) as (data_from, data_to):
if 'Device' in data_from.objects:
data_to.objects = ['Device']
# Link the Device to the current scene
if 'Device' in bpy.data.objects:
device_obj = bpy.data.objects['Device']
bpy.context.collection.objects.link(device_obj)
# Make it no longer an asset
device_obj.asset_clear()
print("Successfully appended Device")
else:
print("Error: Device not found in blend file")
return
# Set transforms before parenting
device_obj.location = (-0.030083, 0.002195, -0.000632)
device_obj.rotation_euler = (
math.radians(-89.493),
math.radians(0.63873),
math.radians(85.309)
)
# Get the active armature
active_armature = bpy.context.active_object
if not active_armature or active_armature.type != 'ARMATURE':
print("Error: No active armature selected")
return
# Find the DEF-forearm.L bone
forearm_bone = active_armature.data.bones.get('DEF-forearm.L')
if not forearm_bone:
print("Error: Bone 'DEF-forearm.L' not found in active armature")
return
# Parent the device to the armature and the specific bone
device_obj.parent = active_armature
device_obj.parent_type = 'BONE'
device_obj.parent_bone = 'DEF-forearm.L'
print(f"Successfully parented 'Device' to {active_armature.name} bone 'DEF-forearm.L'")
def append_and_parent_finger_scanner():
# Append the Finger-Scanner asset
scanner_blend_path = r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\amazon-3Dworld-assets_v4.0.blend"
# Append the Finger-Scanner object
with bpy.data.libraries.load(scanner_blend_path, link=False) as (data_from, data_to):
if 'Finger-Scanner' in data_from.objects:
data_to.objects = ['Finger-Scanner']
# Link the Finger-Scanner to the current scene
if 'Finger-Scanner' in bpy.data.objects:
scanner_obj = bpy.data.objects['Finger-Scanner']
bpy.context.collection.objects.link(scanner_obj)
# Make it no longer an asset
scanner_obj.asset_clear()
print("Successfully appended Finger-Scanner")
else:
print("Error: Finger-Scanner not found in blend file")
return
# Set transforms before parenting (from latest screenshot)
scanner_obj.location = (0.000367, -0.012914, 0.002702)
scanner_obj.rotation_euler = (
math.radians(0),
math.radians(-185),
math.radians(-180)
)
scanner_obj.scale = (0.493, 0.493, 0.493)
# Get the active armature
active_armature = bpy.context.active_object
if not active_armature or active_armature.type != 'ARMATURE':
print("Error: No active armature selected")
return
# Find the DEF-f_index.01.R bone
finger_bone = active_armature.data.bones.get('DEF-f_index.01.R')
if not finger_bone:
print("Error: Bone 'DEF-f_index.01.R' not found in active armature")
return
# Parent the finger scanner to the armature and the specific bone
scanner_obj.parent = active_armature
scanner_obj.parent_type = 'BONE'
scanner_obj.parent_bone = 'DEF-f_index.01.R'
print(f"Successfully parented 'Finger-Scanner' to {active_armature.name} bone 'DEF-f_index.01.R'")
def rename_device_band():
# Find and rename arm-band or armband to device-band
band_variants = ['arm-band', 'armband', 'Arm-band', 'Armband', 'ARM-BAND', 'ARMBAND']
for variant in band_variants:
obj = bpy.data.objects.get(variant)
if obj:
print(f"Found {variant}, renaming to device-band")
obj.name = 'device-band'
return True
print("No arm-band or armband object found to rename")
return False
def rename_geometry_data():
# Select all geometry objects (meshes, curves, etc.)
bpy.ops.object.select_all(action='DESELECT')
renamed_count = 0
skipped_count = 0
not_in_view_layer_count = 0
for obj in bpy.data.objects:
if obj.type in ['MESH', 'CURVE', 'SURFACE', 'META']:
# Check if object is in current view layer before selecting
if obj.name in bpy.context.view_layer.objects:
obj.select_set(True)
else:
not_in_view_layer_count += 1
skipped_count += 1
# Try to rename the data directly
try:
if obj.data and obj.data.name != obj.name:
# Check if data is shared with other objects
data_users = [o for o in bpy.data.objects if o.data == obj.data]
if len(data_users) == 1:
# Only one user, safe to rename
obj.data.name = obj.name
renamed_count += 1
else:
skipped_count += 1
except AttributeError:
skipped_count += 1
# Set the first selected object as active (for any remaining operations)
selected_objects = [obj for obj in bpy.data.objects if obj.select_get()]
if selected_objects:
bpy.context.view_layer.objects.active = selected_objects[0]
# Now run the operator on the selection
bpy.ops.renaming.data_name_from_obj()
print(f"Renamed {renamed_count} objects, skipped {skipped_count} objects (not in view layer: {not_in_view_layer_count})")
# Execute all operations
append_and_parent_device()
append_and_parent_finger_scanner()
rename_device_band()
rename_geometry_data()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzsetttings_Bone_F0618(bpy.types.Operator):
bl_idname = "sna.amzsetttings_bone_f0618"
bl_label = "amz.setttings_bone"
bl_description = "Spawns SettingsBone within active armature"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
from mathutils import Vector
def create_settings_bone():
"""
Creates a new bone under the armature, parents it to the 'root' bone,
and sets the bone colors to Theme Color Set 11.
"""
# Get the active object (should be an armature)
armature_obj = bpy.context.active_object
if not armature_obj or armature_obj.type != 'ARMATURE':
print("Error: Please select an armature object")
return
# Enter Edit Mode
bpy.context.view_layer.objects.active = armature_obj
bpy.ops.object.mode_set(mode='EDIT')
armature = armature_obj.data
# Find the 'root' bone
root_bone = None
for bone in armature.edit_bones:
if bone.name.lower() == 'root':
root_bone = bone
break
if not root_bone:
print("Error: 'root' bone not found in armature")
bpy.ops.object.mode_set(mode='OBJECT')
return
# Find the root bone collection
root_collection = None
for collection in armature.collections:
if collection.name.lower() == 'root':
root_collection = collection
break
# Create a new bone named 'Settings'
new_bone = armature.edit_bones.new('Settings')
# Position the new bone at y = 0.5
new_bone.head = Vector((0, 0.5, 0))
new_bone.tail = Vector((0, 0.5, 0.5))
# Parent the new bone to the root bone
new_bone.parent = root_bone
# Switch to Object Mode to add bone to collection
bpy.ops.object.mode_set(mode='OBJECT')
# Add the new bone to the root collection
if root_collection:
# Get the bone data
bone_data = armature.bones['Settings']
# Add to root collection
root_collection.assign(bone_data)
print("Settings added to Root bone collection")
# Switch to Pose Mode to set bone colors
bpy.ops.object.mode_set(mode='POSE')
# Get the pose bone
pose_bone = armature_obj.pose.bones['Settings']
# Set bone color to Theme Color Set 11
pose_bone.color.palette = 'THEME11'
# Set bone color custom (for viewport display)
pose_bone.bone.color.palette = 'THEME11'
# Return to Object Mode
bpy.ops.object.mode_set(mode='OBJECT')
# Go into Pose Mode and select Settings bone
bpy.ops.object.mode_set(mode='POSE')
# Deselect all bones first
bpy.ops.pose.select_all(action='DESELECT')
# Select the Settings bone
pose_bone = armature_obj.pose.bones['Settings']
pose_bone.bone.select = True
armature_obj.data.bones.active = pose_bone.bone
# Create widget for the Settings bone
bpy.ops.bonewidget.create_widget()
# Set the custom shape wire width to 1
armature_obj.pose.bones['Settings'].custom_shape_wire_width = 1
# Return to Object Mode
bpy.ops.object.mode_set(mode='OBJECT')
print("Settings created successfully and parented to root bone")
print("Bone colors set to Theme Color Set 11")
print("Widget created for Settings bone")
create_settings_bone()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzdevices_Settings_Ac2Bd(bpy.types.Operator):
bl_idname = "sna.amzdevices_settings_ac2bd"
bl_label = "amz.devices_settings"
bl_description = "Applies devices function to SettingsBone"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
print("=== Devices Settings Script Starting ===")
# Check if auto-execution is enabled for drivers
print(f"Auto-execution enabled: {bpy.context.preferences.filepaths.use_scripts_auto_execute}")
if not bpy.context.preferences.filepaths.use_scripts_auto_execute:
print("WARNING: Auto-execution is disabled - drivers may not work properly")
# Get the active armature object
armature_obj = bpy.context.active_object
if not armature_obj or armature_obj.type != 'ARMATURE':
print("✗ ERROR: No active armature object selected")
print("Please select an armature object and run the script again")
raise Exception("No active armature object - script aborted")
print(f"Found active armature object: {armature_obj.name}")
# Get the armature data
armature = armature_obj.data
print(f"Using armature data: {armature.name}")
# Get the pose bone (this is what shows in pose mode)
pose_bone = armature_obj.pose.bones.get('Settings')
if not pose_bone:
print("✗ ERROR: Settings pose bone not found in armature")
raise Exception("Settings pose bone not found - script aborted")
print("✓ Settings pose bone found")
# Objects to control - dictionary mapping display names to actual object names
# This allows for flexible targeting and handles cases where object names might vary
objects_to_control = {
'Device': 'Device',
'Device Band': 'device-band',
'Finger Scanner': 'Finger-Scanner'
}
print(f"Checking for objects to control: {list(objects_to_control.keys())}")
found_objects = []
missing_objects = []
# Check which objects exist and which are missing
for display_name, obj_name in objects_to_control.items():
obj = bpy.data.objects.get(obj_name)
if obj:
found_objects.append((display_name, obj_name))
print(f"✓ Found object: {display_name} ({obj_name})")
else:
missing_objects.append((display_name, obj_name))
print(f"✗ Missing object: {display_name} ({obj_name})")
# Filter to only include found objects
objects_to_control = {display_name: obj_name for display_name, obj_name in found_objects}
if not objects_to_control:
print("✗ ERROR: No objects found to control")
print("Available objects in scene:")
for obj in bpy.data.objects:
if obj.type == 'MESH':
print(f" - {obj.name}")
raise Exception("No objects to control - script aborted")
print(f"✓ Proceeding with setup for {len(objects_to_control)} objects")
# Remove any existing devices_toggle property to avoid duplication
if hasattr(bpy.types.PoseBone, 'devices_toggle'):
delattr(bpy.types.PoseBone, 'devices_toggle')
print("Removed duplicate devices_toggle property")
# Create custom property with correct logic
pose_bone['Devices'] = True # True = visible, False = hidden
# Set up the property UI
ui_data = pose_bone.id_properties_ui('Devices')
ui_data.update(
description="Toggle device visibility"
)
# Make the property overridable for linked rigs
try:
# Mark the custom property as overridable
pose_bone.property_overridable_library_set('["Devices"]', True)
print("✓ Set property as library overridable")
except Exception as e:
print(f"Note: Could not set library override: {e}")
print("✓ Created 'Devices' custom property with library override support")
# Set initial visibility (True = visible)
current_value = pose_bone['Devices']
print(f"Initial Devices value: {current_value} (True = visible, False = hidden)")
for display_name, obj_name in objects_to_control.items():
obj = bpy.data.objects.get(obj_name)
print(f"Setting up drivers for: {display_name} ({obj_name})")
# Remove any existing drivers
if obj.animation_data and obj.animation_data.drivers:
drivers_to_remove = []
for driver in obj.animation_data.drivers:
if driver.data_path in ['hide_render', 'hide_viewport']:
drivers_to_remove.append((driver.data_path, driver.array_index))
for data_path, array_index in drivers_to_remove:
obj.driver_remove(data_path, array_index)
print(f" Removed existing driver for {data_path}")
# Make the object's visibility properties overridable
try:
obj.property_overridable_library_set('hide_render', True)
obj.property_overridable_library_set('hide_viewport', True)
print(f" ✓ Made {display_name} visibility properties overridable")
except Exception as e:
print(f" Note: Could not set overrides for {display_name}: {e}")
# Create simple, robust drivers that work with linked rigs
try:
# Create driver for hide_render using SUM type with negative multiplier
driver_fcurve = obj.driver_add('hide_render')
driver = driver_fcurve.driver
driver.type = 'SUM' # SUM type works reliably across file boundaries
var = driver.variables.new()
var.name = 'devices_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Devices"]'
# Use modifiers to invert the value: 1 - devices_val
mod = driver_fcurve.modifiers.new('GENERATOR')
mod.mode = 'POLYNOMIAL'
mod.poly_order = 1
mod.coefficients = (1.0, -1.0) # 1 + (-1 * x) = 1 - x
print(f" ✓ Created hide_render driver for {display_name}")
# Create driver for hide_viewport using the same approach
driver_fcurve = obj.driver_add('hide_viewport')
driver = driver_fcurve.driver
driver.type = 'SUM'
var = driver.variables.new()
var.name = 'devices_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Devices"]'
# Use modifiers to invert the value
mod = driver_fcurve.modifiers.new('GENERATOR')
mod.mode = 'POLYNOMIAL'
mod.poly_order = 1
mod.coefficients = (1.0, -1.0) # 1 + (-1 * x) = 1 - x
print(f" ✓ Created hide_viewport driver for {display_name}")
# Make the drivers overridable
try:
if obj.animation_data:
obj.animation_data.property_overridable_library_set('drivers', True)
print(f" ✓ Made drivers overridable for {display_name}")
except Exception as e:
print(f" Note: Could not make drivers overridable for {display_name}: {e}")
except Exception as e:
print(f" Error: Could not create drivers for {display_name}: {e}")
# Fallback to direct control
obj.hide_render = not current_value
obj.hide_viewport = not current_value
print(f" Fallback: Set initial visibility: hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
print("✓ Driver setup complete")
# Test the toggle functionality
print("\n=== Testing toggle functionality ===")
# Force update to make sure drivers are working
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
# Initial state
print(f"Current state: Devices = {pose_bone['Devices']} (True = visible)")
for display_name, obj_name in objects_to_control.items():
obj = bpy.data.objects.get(obj_name)
if obj:
print(f" {display_name} ({obj_name}): hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
# Test toggle to False (hidden)
print("\nToggling to False (should hide objects)...")
pose_bone['Devices'] = False
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
print(f"New state: Devices = {pose_bone['Devices']} (False = hidden)")
for display_name, obj_name in objects_to_control.items():
obj = bpy.data.objects.get(obj_name)
if obj:
print(f" {display_name} ({obj_name}): hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
# Test toggle back to True (visible)
print("\nToggling to True (should show objects)...")
pose_bone['Devices'] = True
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
print(f"Final state: Devices = {pose_bone['Devices']} (True = visible)")
for display_name, obj_name in objects_to_control.items():
obj = bpy.data.objects.get(obj_name)
if obj:
print(f" {display_name} ({obj_name}): hide_render={obj.hide_render}, hide_viewport={obj.hide_viewport}")
print("\n=== Setup Complete ===")
print("The 'Devices' property is now available and supports linked rigs")
print("Toggle it to show/hide the device objects")
print("- True = Objects visible (hide_render/hide_viewport = False)")
print("- False = Objects hidden (hide_render/hide_viewport = True)")
print("- Property is marked as library overridable")
print("- Uses SUM drivers with modifiers for reliable cross-file functionality")
print("- Includes backup handler for linked rig scenarios")
print("=== Devices Settings Script Complete ===")
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzwhite_World_B90B2(bpy.types.Operator):
bl_idname = "sna.amzwhite_world_b90b2"
bl_label = "amz.white_world"
bl_description = "Removes Dual Node Background world and replaces with pure white world"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
# Delete the 'Dual Node Background' world if it exists
if 'Dual Node Background' in bpy.data.worlds:
world_to_delete = bpy.data.worlds['Dual Node Background']
bpy.data.worlds.remove(world_to_delete, do_unlink=True)
# Create new world
new_world = bpy.data.worlds.new(name="World")
# Set world color to pure white (#FFFFFFFF)
new_world.use_nodes = True
nodes = new_world.node_tree.nodes
links = new_world.node_tree.links
# Clear existing nodes
nodes.clear()
# Create background node
background_node = nodes.new(type='ShaderNodeBackground')
background_node.inputs[0].default_value = (1.0, 1.0, 1.0, 1.0) # Pure white RGBA
# Create output node
output_node = nodes.new(type='ShaderNodeOutputWorld')
# Link background to output
links.new(background_node.outputs[0], output_node.inputs[0])
# Set as active world
bpy.context.scene.world = new_world
# Purge orphaned data
bpy.ops.outliner.orphans_purge()
# Enable transparent film rendering
bpy.context.scene.render.film_transparent = True
print("World 'Dual Node Background' deleted and new white world created successfully!")
print("Orphaned data purged and transparent film rendering enabled.")
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzdevice_Replace_644D5(bpy.types.Operator):
bl_idname = "sna.amzdevice_replace_644d5"
bl_label = "amz.device_replace"
bl_description = "Replaces old device with the new version"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
import math
def append_and_parent_device():
# First, find and rename the existing Device to Device-Old
old_device = bpy.data.objects.get('Device')
if old_device:
print("Found existing Device, renaming to Device-Old")
old_device.name = 'Device-Old'
# Store the transforms of the old device
old_location = old_device.location.copy()
old_rotation = old_device.rotation_euler.copy()
old_scale = old_device.scale.copy()
old_parent = old_device.parent
old_parent_type = old_device.parent_type
old_parent_bone = old_device.parent_bone
else:
print("No existing Device found, using default transforms")
# Default transforms if no old device exists
old_location = (-0.030083, 0.002195, -0.000632)
old_rotation = (math.radians(-89.493), math.radians(0.63873), math.radians(85.309))
old_scale = (1.0, 1.0, 1.0)
# Get the active armature for default parenting
active_armature = bpy.context.active_object
if active_armature and active_armature.type == 'ARMATURE':
old_parent = active_armature
old_parent_type = 'BONE'
old_parent_bone = 'DEF-forearm.L'
else:
old_parent = None
old_parent_type = 'OBJECT'
old_parent_bone = ''
# Append the new Device asset
device_blend_path = r"A:\1 Amazon_Active_Projects\1 BlenderAssets\Amazon\device_v2.blend"
# Append the Device object
with bpy.data.libraries.load(device_blend_path, link=False) as (data_from, data_to):
if 'Device' in data_from.objects:
data_to.objects = ['Device']
# Link the new Device to the current scene
if 'Device' in bpy.data.objects:
device_obj = bpy.data.objects['Device']
bpy.context.collection.objects.link(device_obj)
# Make it no longer an asset
device_obj.asset_clear()
# Apply the transforms from the old device
device_obj.location = old_location
device_obj.rotation_euler = old_rotation
device_obj.scale = old_scale
# Apply the parenting from the old device
device_obj.parent = old_parent
device_obj.parent_type = old_parent_type
if old_parent_bone:
device_obj.parent_bone = old_parent_bone
print("Successfully appended new Device with old device transforms")
else:
print("Error: Device not found in blend file")
return
def rename_device_band():
# Find and rename arm-band or armband to device-band
band_variants = ['arm-band', 'armband', 'Arm-band', 'Armband', 'ARM-BAND', 'ARMBAND']
for variant in band_variants:
obj = bpy.data.objects.get(variant)
if obj:
print(f"Found {variant}, renaming to device-band")
obj.name = 'device-band'
return True
print("No arm-band or armband object found to rename")
return False
def rename_geometry_data():
# Select all geometry objects (meshes, curves, etc.)
bpy.ops.object.select_all(action='DESELECT')
renamed_count = 0
skipped_count = 0
not_in_view_layer_count = 0
for obj in bpy.data.objects:
if obj.type in ['MESH', 'CURVE', 'SURFACE', 'META']:
# Check if object is in current view layer before selecting
if obj.name in bpy.context.view_layer.objects:
obj.select_set(True)
else:
not_in_view_layer_count += 1
skipped_count += 1
# Try to rename the data directly
try:
if obj.data and obj.data.name != obj.name:
# Check if data is shared with other objects
data_users = [o for o in bpy.data.objects if o.data == obj.data]
if len(data_users) == 1:
# Only one user, safe to rename
obj.data.name = obj.name
renamed_count += 1
else:
skipped_count += 1
except AttributeError:
skipped_count += 1
# Set the first selected object as active (for any remaining operations)
selected_objects = [obj for obj in bpy.data.objects if obj.select_get()]
if selected_objects:
bpy.context.view_layer.objects.active = selected_objects[0]
# Now run the operator on the selection
bpy.ops.renaming.data_name_from_obj()
print(f"Renamed {renamed_count} objects, skipped {skipped_count} objects (not in view layer: {not_in_view_layer_count})")
# Execute all operations
append_and_parent_device()
rename_device_band()
rename_geometry_data()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_PT_GEO_46A0E(bpy.types.Panel):
bl_label = 'GEO'
bl_idname = 'SNA_PT_GEO_46A0E'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ''
bl_order = 2
bl_parent_id = 'SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D'
bl_ui_units_x=0
@classmethod
def poll(cls, context):
return not (False)
def draw_header(self, context):
layout = self.layout
def draw(self, context):
layout = self.layout
op = layout.operator('sna.amzgeo_separator_7ee0b', text='GEO Separator', icon_value=785, emboss=True, depress=False)
op = layout.operator('sna.amzbody_masker_b38e1', text='Body Masker', icon_value=453, emboss=True, depress=False)
op = layout.operator('sna.amzmask_settings_12aea', text='Glove Mask Settings', icon_value=144, emboss=True, depress=False)
op = layout.operator('sna.amzcustom_vis_b9242', text='Custom Visibility Setting', icon_value=144, emboss=True, depress=False)
class SNA_PT_DEVICES_7E7ED(bpy.types.Panel):
bl_label = 'Devices'
bl_idname = 'SNA_PT_DEVICES_7E7ED'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ''
bl_order = 1
bl_parent_id = 'SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D'
bl_ui_units_x=0
@classmethod
def poll(cls, context):
return not (False)
def draw_header(self, context):
layout = self.layout
def draw(self, context):
layout = self.layout
op = layout.operator('sna.amzfresh_devices_36cdc', text='Spawn/Parent Devices', icon_value=415, emboss=True, depress=False)
op = layout.operator('sna.amzdevices_settings_ac2bd', text='DevicesSettings', icon_value=144, emboss=True, depress=False)
op = layout.operator('sna.amzdevice_replace_644d5', text='ReplaceDevice', icon_value=630, emboss=True, depress=False)
class SNA_OT_Amzgeo_Separator_7Ee0B(bpy.types.Operator):
bl_idname = "sna.amzgeo_separator_7ee0b"
bl_label = "amz.geo_separator"
bl_description = "All child geometry of active armature to GEO collection"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def separate_geometry_objects():
# Get the active armature
active_armature = bpy.context.active_object
if not active_armature or active_armature.type != 'ARMATURE':
print("Error: No active armature selected")
return
print(f"Working with armature: {active_armature.name}")
# Find the collection that contains the armature
armature_collection = None
for collection in bpy.data.collections:
if active_armature.name in collection.objects:
armature_collection = collection
break
# If armature is in scene collection, use scene name
if not armature_collection:
if active_armature.name in bpy.context.scene.collection.objects:
armature_collection_name = bpy.context.scene.name
else:
armature_collection_name = "Scene"
else:
armature_collection_name = armature_collection.name
print(f"Armature is in collection: {armature_collection_name}")
# Create new collection name
geo_collection_name = f"GEO-{armature_collection_name}"
# Check if the collection already exists
geo_collection = bpy.data.collections.get(geo_collection_name)
if not geo_collection:
# Create new collection
geo_collection = bpy.data.collections.new(geo_collection_name)
# Link to the same collection as the armature
if armature_collection:
armature_collection.children.link(geo_collection)
print(f"Created new collection: {geo_collection_name} inside {armature_collection.name}")
else:
bpy.context.scene.collection.children.link(geo_collection)
print(f"Created new collection: {geo_collection_name} under scene collection")
else:
print(f"Using existing collection: {geo_collection_name}")
# Set collection color to orange
geo_collection.color_tag = 'COLOR_02' # Orange color tag
print(f"Set {geo_collection_name} color to orange")
# Create subcollections inside GEO collection
accessories_collection_name = "Accessories"
clothing_collection_name = "Clothing"
body_collection_name = "Body"
# Create Accessories subcollection
accessories_collection = bpy.data.collections.get(accessories_collection_name)
if not accessories_collection:
accessories_collection = bpy.data.collections.new(accessories_collection_name)
# Ensure it's linked to GEO collection
if accessories_collection_name not in [child.name for child in geo_collection.children]:
geo_collection.children.link(accessories_collection)
print(f"Created Accessories subcollection inside {geo_collection_name}")
# Create Clothing subcollection
clothing_collection = bpy.data.collections.get(clothing_collection_name)
if not clothing_collection:
clothing_collection = bpy.data.collections.new(clothing_collection_name)
if clothing_collection_name not in [child.name for child in geo_collection.children]:
geo_collection.children.link(clothing_collection)
print(f"Created Clothing subcollection inside {geo_collection_name}")
# Create Body subcollection
body_collection = bpy.data.collections.get(body_collection_name)
if not body_collection:
body_collection = bpy.data.collections.new(body_collection_name)
if body_collection_name not in [child.name for child in geo_collection.children]:
geo_collection.children.link(body_collection)
print(f"Created Body subcollection inside {geo_collection_name}")
# Find all objects parented to the armature
parented_objects = []
for obj in bpy.data.objects:
if obj.parent == active_armature:
parented_objects.append(obj)
print(f"Found {len(parented_objects)} objects parented to armature")
# Organize objects by category
body_moved_count = 0
accessories_moved_count = 0
fallback_moved_count = 0
for obj in parented_objects:
# Remove object from all collections it's currently in
for collection in obj.users_collection:
collection.objects.unlink(obj)
# Check if object has CC_ prefix (goes to Body)
if obj.name.startswith('CC_'):
body_collection.objects.link(obj)
body_moved_count += 1
print(f"Moved {obj.name} to Body")
# Check if object should go to Accessories by name (only specific items)
elif obj.name in ['Device', 'device-band', 'Finger-Scanner', 'Lanyard', 'Vest']:
accessories_collection.objects.link(obj)
accessories_moved_count += 1
print(f"Moved {obj.name} to Accessories")
# Everything else goes to main GEO collection
else:
geo_collection.objects.link(obj)
fallback_moved_count += 1
print(f"Moved {obj.name} to {geo_collection_name}")
# Also find any standalone CC_ objects that aren't parented to armature
standalone_cc_objects = []
for obj in bpy.data.objects:
if obj.name.startswith('CC_') and obj.parent != active_armature:
standalone_cc_objects.append(obj)
if standalone_cc_objects:
print(f"Found {len(standalone_cc_objects)} standalone CC_ objects")
for obj in standalone_cc_objects:
# Remove object from all collections it's currently in
for collection in obj.users_collection:
collection.objects.unlink(obj)
# Add to Body collection
body_collection.objects.link(obj)
body_moved_count += 1
print(f"Moved standalone {obj.name} to Body")
print(f"Successfully organized objects:")
print(f" - {accessories_moved_count} objects moved to Accessories")
print(f" - {body_moved_count} objects moved to Body")
print(f" - {fallback_moved_count} objects moved to {geo_collection_name} (fallback)")
print(f" - Clothing subcollection created (objects to be moved manually)")
# Execute the operation
separate_geometry_objects()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzbody_Masker_B38E1(bpy.types.Operator):
bl_idname = "sna.amzbody_masker_b38e1"
bl_label = "amz.body_masker"
bl_description = "Separates key body parts"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def add_body_masks():
# Find the CC_Base_Body object
body_obj = bpy.data.objects.get('CC_Base_Body')
if not body_obj:
print("Error: CC_Base_Body object not found")
return
print(f"Found body object: {body_obj.name}")
# Define vertex groups for each mask
main_mask_groups = [
# Arms
'DEF-shoulder.L', 'DEF-shoulder.R',
'DEF-upper_arm.L', 'DEF-upper_arm.L.001', 'DEF-upper_arm.R', 'DEF-upper_arm.R.001',
'DEF-elbow_share.L', 'DEF-elbow_share.R',
'DEF-forearm.L', 'DEF-forearm.L.001', 'DEF-forearm.R', 'DEF-forearm.R.001',
# Head and chest (upper torso)
'DEF-spine.003', 'DEF-spine.004', 'DEF-spine.005', 'DEF-spine.006',
'DEF-jaw', 'DEF-jaw.L', 'DEF-jaw.R', 'DEF-jaw.L.001', 'DEF-jaw.R.001',
'DEF-forehead.L', 'DEF-forehead.R', 'DEF-forehead.L.001', 'DEF-forehead.R.001', 'DEF-forehead.L.002', 'DEF-forehead.R.002',
'DEF-temple.L', 'DEF-temple.R',
'DEF-brow.B.L', 'DEF-brow.B.L.001', 'DEF-brow.B.L.002', 'DEF-brow.B.L.003',
'DEF-brow.B.R', 'DEF-brow.B.R.001', 'DEF-brow.B.R.002', 'DEF-brow.B.R.003',
'DEF-brow.T.L', 'DEF-brow.T.L.001', 'DEF-brow.T.L.002', 'DEF-brow.T.L.003',
'DEF-brow.T.R', 'DEF-brow.T.R.001', 'DEF-brow.T.R.002', 'DEF-brow.T.R.003',
'DEF-lid.B.L', 'DEF-lid.B.L.001', 'DEF-lid.B.L.002', 'DEF-lid.B.L.003',
'DEF-lid.B.R', 'DEF-lid.B.R.001', 'DEF-lid.B.R.002', 'DEF-lid.B.R.003',
'DEF-lid.T.L', 'DEF-lid.T.L.001', 'DEF-lid.T.L.002', 'DEF-lid.T.L.003',
'DEF-lid.T.R', 'DEF-lid.T.R.001', 'DEF-lid.T.R.002', 'DEF-lid.T.R.003',
'DEF-ear.L', 'DEF-ear.L.001', 'DEF-ear.L.002', 'DEF-ear.L.003', 'DEF-ear.L.004',
'DEF-ear.R', 'DEF-ear.R.001', 'DEF-ear.R.002', 'DEF-ear.R.003', 'DEF-ear.R.004',
'DEF-chin', 'DEF-chin.001', 'DEF-chin.L', 'DEF-chin.R',
'DEF-cheek.T.L', 'DEF-cheek.T.R', 'DEF-cheek.T.L.001', 'DEF-cheek.T.R.001',
'DEF-cheek.B.L', 'DEF-cheek.B.R', 'DEF-cheek.B.L.001', 'DEF-cheek.B.R.001',
'DEF-nose', 'DEF-nose.L', 'DEF-nose.R', 'DEF-nose.001', 'DEF-nose.002', 'DEF-nose.003', 'DEF-nose.004',
'DEF-nose.L.001', 'DEF-nose.R.001',
'DEF-lip.B.L', 'DEF-lip.B.R', 'DEF-lip.B.L.001', 'DEF-lip.B.R.001',
'DEF-lip.T.L', 'DEF-lip.T.R', 'DEF-lip.T.L.001', 'DEF-lip.T.R.001',
'DEF-tongue', 'DEF-tongue.001', 'DEF-tongue.002',
'DEF-breast.L', 'DEF-breast.R', 'DEF-breast_twist.L', 'DEF-breast_twist.R'
]
hand_groups = [
# Hands
'DEF-hand.L', 'DEF-hand.R',
'DEF-f_pinky.01.L', 'DEF-f_pinky.02.L', 'DEF-f_pinky.03.L',
'DEF-f_ring.01.L', 'DEF-f_ring.02.L', 'DEF-f_ring.03.L',
'DEF-f_middle.01.L', 'DEF-f_middle.02.L', 'DEF-f_middle.03.L',
'DEF-f_index.01.L', 'DEF-f_index.02.L', 'DEF-f_index.03.L',
'DEF-thumb.01.L', 'DEF-thumb.02.L', 'DEF-thumb.03.L',
'DEF-f_pinky.01.R', 'DEF-f_pinky.02.R', 'DEF-f_pinky.03.R',
'DEF-f_ring.01.R', 'DEF-f_ring.02.R', 'DEF-f_ring.03.R',
'DEF-f_middle.01.R', 'DEF-f_middle.02.R', 'DEF-f_middle.03.R',
'DEF-f_index.01.R', 'DEF-f_index.02.R', 'DEF-f_index.03.R',
'DEF-thumb.01.R', 'DEF-thumb.02.R', 'DEF-thumb.03.R'
]
# Create vertex groups for masks
def create_mask_vertex_group(obj, group_name, vertex_group_names):
# Remove existing group if it exists
existing_group = obj.vertex_groups.get(group_name)
if existing_group:
obj.vertex_groups.remove(existing_group)
# Create new vertex group
mask_group = obj.vertex_groups.new(name=group_name)
# Get indices of source vertex groups
source_group_indices = []
for vg_name in vertex_group_names:
vg = obj.vertex_groups.get(vg_name)
if vg:
source_group_indices.append(vg.index)
if not source_group_indices:
print(f"Warning: No source vertex groups found for {group_name}")
return None
# Add vertices that have weights in any of the source groups
vertices_to_add = []
for vert_idx, vert in enumerate(obj.data.vertices):
for group in vert.groups:
if group.group in source_group_indices and group.weight > 0.0:
vertices_to_add.append(vert_idx)
break
if vertices_to_add:
mask_group.add(vertices_to_add, 1.0, 'REPLACE')
print(f"Created {group_name} vertex group with {len(vertices_to_add)} vertices")
return mask_group
# Create Main mask vertex group (head, arms, chest)
main_mask_vg = create_mask_vertex_group(body_obj, "Main_Mask", main_mask_groups)
# Create Hand mask vertex group (main + hands)
hand_mask_vg = create_mask_vertex_group(body_obj, "Hand_Mask", main_mask_groups + hand_groups)
# Add mask modifiers
# Remove existing mask modifiers if they exist
modifiers_to_remove = []
for modifier in body_obj.modifiers:
if modifier.type == 'MASK' and modifier.name in ['Main_Mask', 'Hand_Mask']:
modifiers_to_remove.append(modifier)
for modifier in modifiers_to_remove:
body_obj.modifiers.remove(modifier)
print(f"Removed existing {modifier.name} modifier")
# Add Main mask modifier
if main_mask_vg:
main_mask_modifier = body_obj.modifiers.new(name="Main_Mask", type='MASK')
main_mask_modifier.vertex_group = "Main_Mask"
print("Added Main_Mask modifier (head, arms, chest)")
# Add Hand mask modifier
if hand_mask_vg:
hand_mask_modifier = body_obj.modifiers.new(name="Hand_Mask", type='MASK')
hand_mask_modifier.vertex_group = "Hand_Mask"
print("Added Hand_Mask modifier (head, arms, chest + hands)")
print("\nBody masking completed!")
print("Main_Mask: Shows head, arms, and chest")
print("Hand_Mask: Shows head, arms, chest, and hands")
# Execute the operation
add_body_masks()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzmask_Settings_12Aea(bpy.types.Operator):
bl_idname = "sna.amzmask_settings_12aea"
bl_label = "amz.mask_settings"
bl_description = "Creates custom properties for masking the gloves"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
print("=== Mask Settings Script Starting ===")
# Check if auto-execution is enabled for drivers
print(f"Auto-execution enabled: {bpy.context.preferences.filepaths.use_scripts_auto_execute}")
if not bpy.context.preferences.filepaths.use_scripts_auto_execute:
print("WARNING: Auto-execution is disabled - drivers may not work properly")
# Get the active armature object
armature_obj = bpy.context.active_object
if not armature_obj or armature_obj.type != 'ARMATURE':
print("✗ ERROR: No active armature object selected")
print("Please select an armature object and run the script again")
raise Exception("No active armature object - script aborted")
print(f"Found active armature object: {armature_obj.name}")
# Get the armature data
armature = armature_obj.data
print(f"Using armature data: {armature.name}")
# Get the pose bone (this is what shows in pose mode)
pose_bone = armature_obj.pose.bones.get('Settings')
if not pose_bone:
print("✗ ERROR: Settings pose bone not found in armature")
raise Exception("Settings pose bone not found - script aborted")
print("✓ Settings pose bone found")
# Find the Work_gloves object
work_gloves_obj = bpy.data.objects.get('Work_gloves')
if not work_gloves_obj:
print("✗ ERROR: Work_gloves object not found")
raise Exception("Work_gloves object not found - script aborted")
print(f"✓ Found Work_gloves object: {work_gloves_obj.name}")
# Find the CC_Base_Body object
base_body_obj = bpy.data.objects.get('CC_Base_Body')
if not base_body_obj:
print("✗ ERROR: CC_Base_Body object not found")
raise Exception("CC_Base_Body object not found - script aborted")
print(f"✓ Found CC_Base_Body object: {base_body_obj.name}")
# Check for Main_Mask modifier
main_mask_modifier = None
for modifier in base_body_obj.modifiers:
if modifier.type == 'MASK' and modifier.name == 'Main_Mask':
main_mask_modifier = modifier
break
if not main_mask_modifier:
print("✗ ERROR: Main_Mask modifier not found on CC_Base_Body")
print("Available modifiers:")
for mod in base_body_obj.modifiers:
print(f" - {mod.name} ({mod.type})")
raise Exception("Main_Mask modifier not found - script aborted")
print(f"✓ Found Main_Mask modifier on CC_Base_Body")
# Remove any existing Gloves property to avoid duplication
if 'Gloves' in pose_bone:
del pose_bone['Gloves']
print("Removed existing Gloves property")
# Create custom property as boolean
pose_bone['Gloves'] = True # Default to gloves on
# Set up the property UI as boolean checkbox
ui_data = pose_bone.id_properties_ui('Gloves')
ui_data.update(
description="Toggle gloves visibility",
default=True
)
# Make the property overridable for linked rigs
try:
# Mark the custom property as overridable
pose_bone.property_overridable_library_set('["Gloves"]', True)
print("✓ Set Gloves property as library overridable")
except Exception as e:
print(f"Note: Could not set library override: {e}")
print("✓ Created 'Gloves' custom property with library override support")
# Set up drivers for Work_gloves object visibility
print("Setting up drivers for Work_gloves object...")
try:
# Clear any existing drivers
try:
work_gloves_obj.driver_remove('hide_render')
work_gloves_obj.driver_remove('hide_viewport')
except:
pass
# Create hide_render driver (hide when Gloves = 0, show when Gloves = 1)
driver_fcurve = work_gloves_obj.driver_add('hide_render')
driver = driver_fcurve.driver
driver.type = 'SUM'
var = driver.variables.new()
var.name = 'gloves_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Gloves"]'
# Use polynomial modifier to invert: hide_render = 1 - gloves_val
mod = driver_fcurve.modifiers.new('GENERATOR')
mod.mode = 'POLYNOMIAL'
mod.poly_order = 1
mod.coefficients = (1.0, -1.0) # 1 - x
print(" ✓ Created hide_render driver for Work_gloves")
# Create hide_viewport driver (same logic)
driver_fcurve = work_gloves_obj.driver_add('hide_viewport')
driver = driver_fcurve.driver
driver.type = 'SUM'
var = driver.variables.new()
var.name = 'gloves_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Gloves"]'
# Use polynomial modifier to invert: hide_viewport = 1 - gloves_val
mod = driver_fcurve.modifiers.new('GENERATOR')
mod.mode = 'POLYNOMIAL'
mod.poly_order = 1
mod.coefficients = (1.0, -1.0) # 1 - x
print(" ✓ Created hide_viewport driver for Work_gloves")
except Exception as e:
print(f" Error creating Work_gloves drivers: {e}")
# Set up drivers for Main_Mask modifier visibility
print("Setting up drivers for Main_Mask modifier...")
try:
# Clear any existing drivers
try:
main_mask_modifier.driver_remove('show_render')
main_mask_modifier.driver_remove('show_viewport')
except:
pass
# Create show_render driver (show when Gloves = 1, hide when Gloves = 0)
driver_fcurve = main_mask_modifier.driver_add('show_render')
driver = driver_fcurve.driver
driver.type = 'SUM'
var = driver.variables.new()
var.name = 'gloves_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Gloves"]'
print(" ✓ Created show_render driver for Main_Mask")
# Create show_viewport driver (same logic)
driver_fcurve = main_mask_modifier.driver_add('show_viewport')
driver = driver_fcurve.driver
driver.type = 'SUM'
var = driver.variables.new()
var.name = 'gloves_val'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = armature_obj
var.targets[0].data_path = 'pose.bones["Settings"]["Gloves"]'
print(" ✓ Created show_viewport driver for Main_Mask")
except Exception as e:
print(f" Error creating Main_Mask drivers: {e}")
print("✓ Driver setup complete")
# Test the toggle functionality
print("\n=== Testing gloves functionality ===")
# Force update to make sure drivers are working
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
# Initial state (should be gloves on)
print(f"Current state: Gloves = {pose_bone['Gloves']} (True=on, False=off)")
print(f" Work_gloves: hide_render={work_gloves_obj.hide_render}, hide_viewport={work_gloves_obj.hide_viewport}")
print(f" Main_Mask: show_render={main_mask_modifier.show_render}, show_viewport={main_mask_modifier.show_viewport}")
# Test toggle to False (gloves off)
print("\nToggling to False (gloves off - should hide gloves and Main_Mask, exposing hands)...")
pose_bone['Gloves'] = False
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
print(f"New state: Gloves = {pose_bone['Gloves']}")
print(f" Work_gloves: hide_render={work_gloves_obj.hide_render}, hide_viewport={work_gloves_obj.hide_viewport}")
print(f" Main_Mask: show_render={main_mask_modifier.show_render}, show_viewport={main_mask_modifier.show_viewport}")
# Test toggle back to True (gloves on)
print("\nToggling to True (gloves on - should show gloves and Main_Mask, hiding hands)...")
pose_bone['Gloves'] = True
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
print(f"Final state: Gloves = {pose_bone['Gloves']}")
print(f" Work_gloves: hide_render={work_gloves_obj.hide_render}, hide_viewport={work_gloves_obj.hide_viewport}")
print(f" Main_Mask: show_render={main_mask_modifier.show_render}, show_viewport={main_mask_modifier.show_viewport}")
print("\n✓ Mask Settings script completed successfully!")
print("The 'Gloves' property is now available on the Settings bone as a checkbox")
print("Use checkbox to toggle gloves and hand visibility")
print(" - Gloves ON (checked): Shows Work_gloves object + Main_Mask (hides hands from body)")
print(" - Gloves OFF (unchecked): Hides Work_gloves object + Main_Mask (shows hands via Hand_Mask)")
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzcustom_Vis_B9242(bpy.types.Operator):
bl_idname = "sna.amzcustom_vis_b9242"
bl_label = "amz.custom_vis"
bl_description = "Creates a visibility property toggle for the active object"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
print("=== Custom Visibility Script Starting ===")
print(f"Blender version: {bpy.app.version_string}")
# Check if auto-execution is enabled for drivers
print(f"Auto-execution enabled: {bpy.context.preferences.filepaths.use_scripts_auto_execute}")
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
active_obj = bpy.context.active_object
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}")
# Find the armature object in the scene
armature_obj = None
# First, check if the active object is an armature
if active_obj and active_obj.type == 'ARMATURE':
armature_obj = active_obj
print(f"Using active armature: {armature_obj.name}")
else:
# Look for an armature that has a Settings bone
print("Active object is not an armature, searching for armature with Settings bone...")
for obj in bpy.data.objects:
if obj.type == 'ARMATURE' and 'Settings' in obj.pose.bones:
armature_obj = obj
print(f"Found armature with Settings bone: {armature_obj.name}")
break
# If still no armature found, just use the first armature
if not armature_obj:
for obj in bpy.data.objects:
if obj.type == 'ARMATURE':
armature_obj = obj
print(f"Using first available armature: {armature_obj.name}")
break
if not armature_obj:
print("✗ ERROR: No armature object found in scene")
raise Exception("No armature object found - script aborted")
print(f"Selected armature object: {armature_obj.name}")
# Get the Settings pose bone
pose_bone = armature_obj.pose.bones.get('Settings')
if not pose_bone:
print("✗ ERROR: Settings pose bone not found in armature")
print("Available bones:", [bone.name for bone in armature_obj.pose.bones])
raise Exception("Settings pose bone not found - script aborted")
print("✓ Settings pose bone found")
# Create property name based on active object name
property_name = active_obj.name
print(f"Creating visibility property: {property_name}")
# Remove any existing property with this name to avoid duplication
if property_name in pose_bone:
del pose_bone[property_name]
print(f"Removed existing {property_name} property")
# Create custom property as boolean (default to visible)
pose_bone[property_name] = True
# Set up the property UI
ui_data = pose_bone.id_properties_ui(property_name)
ui_data.update(
description=f"Toggle {active_obj.name} visibility",
default=True
)
# Make the property overridable for linked rigs
try:
# Try the newer Blender 4.x API first
if hasattr(pose_bone, 'property_overridable_library_set'):
pose_bone.property_overridable_library_set(f'["{property_name}"]', True)
print(f"✓ Set {property_name} property as library overridable")
else:
print(f"Note: Library override API not available in this Blender version")
except Exception as e:
print(f"Note: Could not set library override: {e}")
print(f"✓ Created '{property_name}' custom property with library override support")
# Set up drivers for object visibility
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}")
except Exception as e:
print(f" Error creating {active_obj.name} drivers: {e}")
print("✓ Driver setup complete")
# Test the toggle functionality
print(f"\n=== Testing {property_name} visibility functionality ===")
# Force update to make sure drivers are working
bpy.context.view_layer.update()
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}")
# Test toggle to False (object hidden)
print(f"\nToggling to False ({active_obj.name} 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}")
# Test toggle back to True (object visible)
print(f"\nToggling to True ({active_obj.name} 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}")
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}")
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzapply_Subdiv_Wgt_9Df87(bpy.types.Operator):
bl_idname = "sna.amzapply_subdiv_wgt_9df87"
bl_label = "amz.apply_subdiv_wgt"
bl_description = "Apply all subdivision modifiers to WGT objects, so blender can draw them properly on the rig."
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def apply_subdiv_to_wgt_objects():
"""Apply subdivision surface modifier to all objects starting with 'WGT-'"""
# Find the WGT collection
wgt_collection = None
wgt_layer_collection = None
for collection in bpy.data.collections:
if collection.name.startswith("WGT"):
wgt_collection = collection
break
# Find the corresponding layer collection
if wgt_collection:
def find_layer_collection(layer_collection, target_collection):
if layer_collection.collection == target_collection:
return layer_collection
for child in layer_collection.children:
result = find_layer_collection(child, target_collection)
if result:
return result
return None
wgt_layer_collection = find_layer_collection(bpy.context.view_layer.layer_collection, wgt_collection)
# Store original collection visibility settings
original_settings = {}
if wgt_collection and wgt_layer_collection:
original_settings = {
'collection_hide_viewport': wgt_collection.hide_viewport,
'collection_hide_render': wgt_collection.hide_render,
'collection_hide_select': wgt_collection.hide_select,
'layer_exclude': wgt_layer_collection.exclude,
'layer_hide_viewport': wgt_layer_collection.hide_viewport
}
# Temporarily enable all visibility settings
wgt_collection.hide_viewport = False
wgt_collection.hide_render = False
wgt_collection.hide_select = False
wgt_layer_collection.exclude = False
wgt_layer_collection.hide_viewport = False
print(f"Temporarily enabled WGT collection '{wgt_collection.name}' visibility")
try:
# Get all objects in the scene
all_objects = bpy.context.scene.objects
# Filter objects that start with "WGT-"
wgt_objects = [obj for obj in all_objects if obj.name.startswith("WGT-")]
if not wgt_objects:
print("No objects found starting with 'WGT-'")
return
print(f"Found {len(wgt_objects)} objects starting with 'WGT-':")
applied_count = 0
for obj in wgt_objects:
print(f" - {obj.name}")
# Find and apply subdivision surface modifier if it exists
subdiv_mod = None
for mod in obj.modifiers:
if mod.type == 'SUBSURF':
subdiv_mod = mod
break
if subdiv_mod:
# Make object active
bpy.context.view_layer.objects.active = obj
# Make object single-user if it has multi-user data
if obj.data.users > 1:
obj.data = obj.data.copy()
print(f" Made {obj.name} single-user")
# Apply the subdivision modifier
bpy.ops.object.modifier_apply(modifier=subdiv_mod.name)
print(f" Applied subdivision modifier to {obj.name}")
applied_count += 1
else:
print(f" {obj.name} has no subdivision modifier to apply")
print(f"\nApplied subdivision to {applied_count} objects")
finally:
# Restore original collection visibility settings
if wgt_collection and wgt_layer_collection and original_settings:
wgt_collection.hide_viewport = original_settings['collection_hide_viewport']
wgt_collection.hide_render = original_settings['collection_hide_render']
wgt_collection.hide_select = original_settings['collection_hide_select']
wgt_layer_collection.exclude = original_settings['layer_exclude']
wgt_layer_collection.hide_viewport = original_settings['layer_hide_viewport']
print(f"Restored WGT collection '{wgt_collection.name}' visibility settings")
# For Serpens compatibility - execute the function
apply_subdiv_to_wgt_objects()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzhh_Spawn_C3A19(bpy.types.Operator):
bl_idname = "sna.amzhh_spawn_c3a19"
bl_label = "amz.hh_spawn"
bl_description = "HardHat Spawn/Parent"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
from mathutils import Matrix
ASSET_BLEND_PATH = r"A:\\1 Amazon_Active_Projects\\1 BlenderAssets\\Amazon\\amazon-asset_Hard-Hat_v1.1.blend"
ASSET_OBJECT_NAME = "hard-hat"
GN_MOD_NAME = "hard-hat-transforms"
def find_armature_with_head():
active = bpy.context.active_object
if active and active.type == 'ARMATURE' and 'head' in active.pose.bones:
return active
for obj in bpy.data.objects:
if obj.type == 'ARMATURE' and 'head' in obj.pose.bones:
return obj
return None
def append_hard_hat():
with bpy.data.libraries.load(ASSET_BLEND_PATH, link=False, assets_only=True) as (data_from, data_to):
if ASSET_OBJECT_NAME in data_from.objects:
data_to.objects = [ASSET_OBJECT_NAME]
else:
print(f"Error: '{ASSET_OBJECT_NAME}' not found in asset file")
return None
obj = bpy.data.objects.get(ASSET_OBJECT_NAME)
if not obj:
print("Error: hard-hat object failed to append")
return None
# Link to current collection (temporary)
if obj.name not in bpy.context.collection.objects:
bpy.context.collection.objects.link(obj)
# Make it no longer an asset
try:
obj.asset_clear()
except Exception:
pass
return obj
def strip_geonodes(obj):
for mod in list(obj.modifiers):
if mod.type == 'NODES' and (mod.name == GN_MOD_NAME or (mod.node_group and mod.node_group.name == GN_MOD_NAME)):
obj.modifiers.remove(mod)
def align_to_head(obj, armature):
head_pb = armature.pose.bones.get('head')
if not head_pb:
print("Error: head pose bone not found")
return
# Apply transforms: X-90°, Y90°, Z offset -0.07
import math
from mathutils import Euler, Vector
# Get head bone matrix
head_matrix = armature.matrix_world @ head_pb.matrix
# Apply rotation offsets
rotation_offset = Euler((math.radians(-90), math.radians(90), 0), 'XYZ')
rotation_matrix = rotation_offset.to_matrix().to_4x4()
# First set rotation relative to head
obj.matrix_world = head_matrix @ rotation_matrix
# Then apply global offsets (world space)
obj.matrix_world.translation += Vector((0.0, 0.0, -0.07)) # global Z
obj.matrix_world.translation += Vector((0.0, -0.004, 0.0)) # global Y
# Apply uniform scale before parenting so it sticks
try:
obj.scale *= 1.4
except Exception:
obj.scale = (1.4, 1.4, 1.4)
def get_accessories_collection():
# Prefer an existing 'Accessories' collection that already holds known items
candidates = [c for c in bpy.data.collections if c.name == 'Accessories']
def has_known(c):
names = {o.name for o in c.objects}
return any(n in names for n in ('Device', 'device-band', 'Finger-Scanner', 'Lanyard', 'Vest'))
for c in candidates:
if has_known(c):
return c
if candidates:
return candidates[0]
# Create under scene root if not found
coll = bpy.data.collections.new('Accessories')
bpy.context.scene.collection.children.link(coll)
return coll
def move_to_accessories(obj):
acc = get_accessories_collection()
# Unlink from other collections to avoid duplicates
for c in list(obj.users_collection):
try:
c.objects.unlink(obj)
except Exception:
pass
# Link to Accessories
if obj.name not in acc.objects:
acc.objects.link(obj)
def parent_to_head(obj, armature):
head_pb = armature.pose.bones.get('head')
if not head_pb:
print("Error: head pose bone not found")
return
obj.parent = armature
obj.parent_type = 'BONE'
obj.parent_bone = 'head'
obj.matrix_parent_inverse = (armature.matrix_world @ head_pb.matrix).inverted()
def run():
arm = find_armature_with_head()
if not arm:
print("Error: No armature with 'head' bone found")
return
hat = append_hard_hat()
if not hat:
return
strip_geonodes(hat)
align_to_head(hat, arm)
parent_to_head(hat, arm)
move_to_accessories(hat)
bpy.context.view_layer.objects.active = hat
hat.select_set(True)
print("HH appended, cleaned, aligned, and parented to head.")
run()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzhh_Mask_0D004(bpy.types.Operator):
bl_idname = "sna.amzhh_mask_0d004"
bl_label = "amz.hh_mask"
bl_description = "HardHat Mask"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def popup_error(message: str):
def _draw(self, context):
self.layout.label(text=message)
try:
bpy.context.window_manager.popup_menu(_draw, title="HardHat", icon='ERROR')
except Exception:
print(f"ERROR: {message}")
def selected_hair_meshes():
# Use stored scene targets if present (strict requirement per user preference)
names = (bpy.context.scene.get('HH_HairTargets') or '').split(';')
names = [n for n in names if n]
if names:
objs = [bpy.data.objects.get(n) for n in names]
return [o for o in objs if o and o.type == 'MESH']
popup_error("No HardHat hair targets set. Click 'Set HardHat Hair Targets' first.")
return []
def ensure_empty_vertex_group(obj, name):
vg = obj.vertex_groups.get(name)
if vg:
# Clear existing by removing and recreating to guarantee emptiness
obj.vertex_groups.remove(vg)
return obj.vertex_groups.new(name=name)
def ensure_full_vertex_group(obj, name):
vg = obj.vertex_groups.get(name)
if vg:
obj.vertex_groups.remove(vg)
vg = obj.vertex_groups.new(name=name)
if obj.data.vertices:
vg.add(range(len(obj.data.vertices)), 1.0, 'REPLACE')
return vg
def set_mask_modifiers(obj, main_name, hh_name):
# Remove existing
for m in list(obj.modifiers):
if m.type == 'MASK' and m.name in {main_name, hh_name}:
obj.modifiers.remove(m)
m1 = obj.modifiers.new(name=main_name, type='MASK')
m1.vertex_group = main_name
m2 = obj.modifiers.new(name=hh_name, type='MASK')
m2.vertex_group = hh_name
return m1, m2
def run():
hair_objs = selected_hair_meshes()
if not hair_objs:
print("Error: Select hair mesh object(s) before running")
return
for obj in hair_objs:
ensure_full_vertex_group(obj, 'MainMask')
ensure_empty_vertex_group(obj, 'HHMask')
set_mask_modifiers(obj, 'MainMask', 'HHMask')
print(f"Prepared '{obj.name}': MainMask=all verts, HHMask=empty, mask modifiers created")
print("hh_mask: Done. Assign weights to HHMask as needed.")
run()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzhh_Shapekey_Cddad(bpy.types.Operator):
bl_idname = "sna.amzhh_shapekey_cddad"
bl_label = "amz.hh_shapekey"
bl_description = "HardHat Shapekey"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def popup_error(message: str):
def _draw(self, context):
self.layout.label(text=message)
try:
bpy.context.window_manager.popup_menu(_draw, title="HardHat", icon='ERROR')
except Exception:
print(f"ERROR: {message}")
def selected_hair_meshes():
names = (bpy.context.scene.get('HH_HairTargets') or '').split(';')
names = [n for n in names if n]
if names:
objs = [bpy.data.objects.get(n) for n in names]
return [o for o in objs if o and o.type == 'MESH']
popup_error("No HardHat hair targets set. Click 'Set HardHat Hair Targets' first.")
return []
def ensure_basis(obj):
if not obj.data.shape_keys:
obj.shape_key_add(name='Basis', from_mix=False)
def ensure_hardhat_key(obj):
# Ensure Basis
ensure_basis(obj)
# Find or create HardHat shapekey
key = obj.data.shape_keys.key_blocks.get('HardHat')
if not key:
key = obj.shape_key_add(name='HardHat', from_mix=False)
key.value = 0.0
return key
def run():
hair_objs = selected_hair_meshes()
if not hair_objs:
print("Error: Select hair mesh object(s) before running")
return
for obj in hair_objs:
ensure_hardhat_key(obj)
print(f"Ensured 'HardHat' shapekey on '{obj.name}' (value=0.0)")
print("hh_shapekey: Done.")
run()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzhh_Settings_A8429(bpy.types.Operator):
bl_idname = "sna.amzhh_settings_a8429"
bl_label = "amz.hh_settings"
bl_description = "HardHat Settings"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def popup_error(message: str):
def _draw(self, context):
self.layout.label(text=message)
try:
bpy.context.window_manager.popup_menu(_draw, title="HardHat", icon='ERROR')
except Exception:
print(f"ERROR: {message}")
SETTINGS_BONE = 'Settings'
PROP_NAME = 'HardHat'
def find_armature():
"""Robustly resolve the armature that owns the Settings bone.
Priority:
1) Armature parenting the hard-hat object
2) Armature parenting any selected hair object
3) Active object if it's an armature with a Settings bone
4) Any armature in scene with a Settings bone
"""
# 1) Parent of hard-hat
hat = bpy.data.objects.get('hard-hat')
if hat and hat.parent and hat.parent.type == 'ARMATURE':
if SETTINGS_BONE in hat.parent.pose.bones:
return hat.parent
# 2) Parent of any selected hair object
for o in bpy.context.selected_objects or []:
p = o.parent
while p:
if p.type == 'ARMATURE' and SETTINGS_BONE in p.pose.bones:
return p
p = p.parent
# 3) Active armature
a = bpy.context.active_object
if a and a.type == 'ARMATURE' and SETTINGS_BONE in a.pose.bones:
return a
# 4) Any armature with Settings bone
for o in bpy.data.objects:
if o.type == 'ARMATURE' and SETTINGS_BONE in o.pose.bones:
return o
return None
def selected_hair_meshes():
names = (bpy.context.scene.get('HH_HairTargets') or '').split(';')
names = [n for n in names if n]
if names:
objs = [bpy.data.objects.get(n) for n in names]
return [o for o in objs if o and o.type == 'MESH']
popup_error("No HardHat hair targets set. Click 'Set HardHat Hair Targets' first.")
return []
def ensure_property(arm_obj):
pb = arm_obj.pose.bones.get(SETTINGS_BONE)
if not pb:
raise Exception("Settings pose bone not found")
# Default True (hat on by default, like Devices)
if PROP_NAME not in pb:
pb[PROP_NAME] = True
try:
ui = pb.id_properties_ui(PROP_NAME)
ui.update(description="Toggle HardHat visibility & masking", default=True)
except Exception:
pass
try:
# Mark the pose bone custom property overridable (use exact Blender path syntax with double quotes)
if hasattr(pb, 'property_overridable_library_set'):
pb.property_overridable_library_set(f'["{PROP_NAME}"]', True)
# Also mark the owning object path overridable (helps in some linked setups)
if hasattr(arm_obj, 'property_overridable_library_set'):
arm_obj.property_overridable_library_set(f'pose.bones["{SETTINGS_BONE}"]["{PROP_NAME}"]', True)
except Exception:
pass
return pb
def add_mask_drivers(arm_obj, obj, prop_path):
# MainMask: visible when HardHat=False -> invert
for name, invert in (('MainMask', True), ('HHMask', False)):
mod = next((m for m in obj.modifiers if m.type == 'MASK' and m.name == name), None)
if not mod:
continue
for prop in ('show_render', 'show_viewport'):
# Remove existing driver
try:
mod.driver_remove(prop)
except Exception:
pass
fcu = mod.driver_add(prop)
drv = fcu.driver
drv.type = 'SUM'
var = drv.variables.new()
var.name = 'hh'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = arm_obj
var.targets[0].data_path = prop_path
if invert:
gen = fcu.modifiers.new('GENERATOR')
gen.mode = 'POLYNOMIAL'
gen.poly_order = 1
gen.coefficients = (1.0, -1.0)
def add_hardhat_visibility_drivers(arm_obj, prop_path):
hat = bpy.data.objects.get('hard-hat')
if not hat:
print("Warning: 'hard-hat' object not found for visibility drivers")
return
for prop in ('hide_render', 'hide_viewport'):
try:
hat.driver_remove(prop)
except Exception:
pass
fcu = hat.driver_add(prop)
drv = fcu.driver
drv.type = 'SUM'
var = drv.variables.new()
var.name = 'hh'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = arm_obj
var.targets[0].data_path = prop_path
# invert so: visible when HardHat=True, hidden when HardHat=False
gen = fcu.modifiers.new('GENERATOR')
gen.mode = 'POLYNOMIAL'
gen.poly_order = 1
gen.coefficients = (1.0, -1.0)
# Make overridable like Devices
try:
hat.property_overridable_library_set(prop, True)
except Exception:
pass
def add_shapekey_driver(arm_obj, obj, prop_path):
if not obj.data.shape_keys:
return
key = obj.data.shape_keys.key_blocks.get('HardHat')
if not key:
return
# Remove existing
try:
key.driver_remove('value')
except Exception:
pass
fcu = key.driver_add('value')
drv = fcu.driver
drv.type = 'SUM'
var = drv.variables.new()
var.name = 'hh'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = arm_obj
var.targets[0].data_path = prop_path
def run():
arm = find_armature()
if not arm:
print("Error: No armature selected/found")
return
try:
ensure_property(arm)
except Exception as e:
print(f"Error: {e}")
return
hair_objs = selected_hair_meshes()
if not hair_objs:
# No targets set; exit without applying any drivers
return
prop_path = f'pose.bones["{SETTINGS_BONE}"]["{PROP_NAME}"]'
for obj in hair_objs:
add_mask_drivers(arm, obj, prop_path)
add_shapekey_driver(arm, obj, prop_path)
print(f"Applied drivers to '{obj.name}' (existing masks/shapekey only)")
# Also wire up the hard-hat visibility (only when hair targets exist)
add_hardhat_visibility_drivers(arm, prop_path)
# Force update to evaluate freshly created drivers
try:
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get().update()
except Exception:
pass
print("hh_settings: Done.")
run()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_OT_Amzhh_Set_Targets_A4F8B(bpy.types.Operator):
bl_idname = "sna.amzhh_set_targets_a4f8b"
bl_label = "amz.hh_set_targets"
bl_description = "Set HardHat Hair Targets"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version >= (3, 0, 0) and True:
cls.poll_message_set('')
return not False
def execute(self, context):
def selected_hair_meshes():
return [o for o in (bpy.context.selected_objects or []) if o.type == 'MESH']
def run():
hair = selected_hair_meshes()
if not hair:
print("Error: Select hair mesh object(s) to store as HardHat targets")
return
names = [o.name for o in hair]
# Store as semicolon-separated string for simplicity
bpy.context.scene['HH_HairTargets'] = ';'.join(names)
print(f"Stored HH hair targets: {len(names)} -> {names}")
run()
return {"FINISHED"}
def invoke(self, context, event):
return self.execute(context)
class SNA_PT_HELMET_938C9(bpy.types.Panel):
bl_label = 'Helmet'
bl_idname = 'SNA_PT_HELMET_938C9'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = ''
bl_order = 2
bl_parent_id = 'SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D'
bl_ui_units_x=0
@classmethod
def poll(cls, context):
return not (False)
def draw_header(self, context):
layout = self.layout
def draw(self, context):
layout = self.layout
op = layout.operator('sna.amzhh_spawn_c3a19', text='Spawn/Parent HardHat', icon_value=415, emboss=True, depress=False)
op = layout.operator('sna.amzhh_set_targets_a4f8b', text='Set HH Hair Targets', icon_value=66, emboss=True, depress=False)
op = layout.operator('sna.amzhh_mask_0d004', text='HardHat Mask', icon_value=453, emboss=True, depress=False)
op = layout.operator('sna.amzhh_shapekey_cddad', text='HardHat Shapekey', icon_value=186, emboss=True, depress=False)
op = layout.operator('sna.amzhh_settings_a8429', text='HardHat Settings', icon_value=144, emboss=True, depress=False)
def register():
global _icons
_icons = bpy.utils.previews.new()
bpy.utils.register_class(SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D)
bpy.utils.register_class(SNA_OT_Amzfresh_Devices_36Cdc)
bpy.utils.register_class(SNA_OT_Amzsetttings_Bone_F0618)
bpy.utils.register_class(SNA_OT_Amzdevices_Settings_Ac2Bd)
bpy.utils.register_class(SNA_OT_Amzwhite_World_B90B2)
bpy.utils.register_class(SNA_OT_Amzdevice_Replace_644D5)
bpy.utils.register_class(SNA_PT_GEO_46A0E)
bpy.utils.register_class(SNA_PT_DEVICES_7E7ED)
bpy.utils.register_class(SNA_OT_Amzgeo_Separator_7Ee0B)
bpy.utils.register_class(SNA_OT_Amzbody_Masker_B38E1)
bpy.utils.register_class(SNA_OT_Amzmask_Settings_12Aea)
bpy.utils.register_class(SNA_OT_Amzcustom_Vis_B9242)
bpy.utils.register_class(SNA_OT_Amzapply_Subdiv_Wgt_9Df87)
bpy.utils.register_class(SNA_OT_Amzhh_Spawn_C3A19)
bpy.utils.register_class(SNA_OT_Amzhh_Mask_0D004)
bpy.utils.register_class(SNA_OT_Amzhh_Shapekey_Cddad)
bpy.utils.register_class(SNA_OT_Amzhh_Settings_A8429)
bpy.utils.register_class(SNA_OT_Amzhh_Set_Targets_A4F8B)
bpy.utils.register_class(SNA_PT_HELMET_938C9)
def unregister():
global _icons
bpy.utils.previews.remove(_icons)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
for km, kmi in addon_keymaps.values():
km.keymap_items.remove(kmi)
addon_keymaps.clear()
bpy.utils.unregister_class(SNA_PT_AMZN_CHARACTER_TOOLS_FCE9D)
bpy.utils.unregister_class(SNA_OT_Amzfresh_Devices_36Cdc)
bpy.utils.unregister_class(SNA_OT_Amzsetttings_Bone_F0618)
bpy.utils.unregister_class(SNA_OT_Amzdevices_Settings_Ac2Bd)
bpy.utils.unregister_class(SNA_OT_Amzwhite_World_B90B2)
bpy.utils.unregister_class(SNA_OT_Amzdevice_Replace_644D5)
bpy.utils.unregister_class(SNA_PT_GEO_46A0E)
bpy.utils.unregister_class(SNA_PT_DEVICES_7E7ED)
bpy.utils.unregister_class(SNA_OT_Amzgeo_Separator_7Ee0B)
bpy.utils.unregister_class(SNA_OT_Amzbody_Masker_B38E1)
bpy.utils.unregister_class(SNA_OT_Amzmask_Settings_12Aea)
bpy.utils.unregister_class(SNA_OT_Amzcustom_Vis_B9242)
bpy.utils.unregister_class(SNA_OT_Amzapply_Subdiv_Wgt_9Df87)
bpy.utils.unregister_class(SNA_OT_Amzhh_Spawn_C3A19)
bpy.utils.unregister_class(SNA_OT_Amzhh_Mask_0D004)
bpy.utils.unregister_class(SNA_OT_Amzhh_Shapekey_Cddad)
bpy.utils.unregister_class(SNA_OT_Amzhh_Settings_A8429)
bpy.utils.unregister_class(SNA_OT_Amzhh_Set_Targets_A4F8B)
bpy.utils.unregister_class(SNA_PT_HELMET_938C9)