import bpy 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 ===")