# 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 . 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)