2042 lines
94 KiB
Python
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)
|