Copy Driver to Selected #120518

Closed
opened 2024-04-11 13:14:19 +02:00 by Simon Thommes · 3 comments
Member

Add an operator to copy a driver from the active to all selected objects at the same data path in the context menu of driven properties on object ids.

  • if a driver at that path already exists it should be overwritten
  • same as for the existing Copy to Selected functionality there needs to be an alternative split into Copy Single ... and Copy All ... for array properties

I spoke to @dr.sybren and @nathanvegdahl about creating this task for reference.

Here is a script that provides a mockup for this operator (missing support for custom properties and arrays):

import bpy
from rigify.utils.misc import copy_attributes

def copy_driver(
    from_fcurve: bpy.types.FCurve, target: bpy.types.ID, data_path=None, index=None
) -> bpy.types.FCurve:
    """Copy an existing FCurve containing a driver to a new ID, by creating a copy
    of the existing driver on the target ID.

    Args:
        from_fcurve (bpy.types.FCurve): FCurve containing a driver
        target (bpy.types.ID): ID that can have drivers added to it
        data_path (_type_, optional): Data Path of existing driver. Defaults to None.
        index (_type_, optional): array index of the property drive. Defaults to None.

    Returns:
        bpy.types.FCurve: Fcurve containing copy of driver on target ID
    """
    if not data_path:
        data_path = from_fcurve.data_path

    new_fc = None
    if index:
        new_fc = target.driver_add(data_path, index)
    else:
        new_fc = target.driver_add(data_path)

    copy_attributes(from_fcurve, new_fc)
    copy_attributes(from_fcurve.driver, new_fc.driver)

    # Remove default modifiers, variables, etc.
    for m in new_fc.modifiers:
        new_fc.modifiers.remove(m)
    for v in new_fc.driver.variables:
        new_fc.driver.variables.remove(v)

    # Copy modifiers
    for m1 in from_fcurve.modifiers:
        m2 = new_fc.modifiers.new(type=m1.type)
        copy_attributes(m1, m2)

    # Copy variables
    for v1 in from_fcurve.driver.variables:
        v2 = new_fc.driver.variables.new()
        copy_attributes(v1, v2)
        for i in range(len(v1.targets)):
            copy_attributes(v1.targets[i], v2.targets[i])

    return new_fc

def data_path_from_button_context(context):
    # TODO: support custom properties
    # TODO: support array properties
    return f'{context.button_pointer.path_from_id()}.{context.button_prop.identifier}'

def copy_driver_to_selected_poll(context):
    # check for button context
    if not hasattr(context, 'button_pointer'):
        return False
    # assign pointer and null-check
    b_pointer = getattr(context, 'button_pointer')
    if not b_pointer:
        return False
    # check if id is an object
    id_data = b_pointer.id_data
    if not type(id_data) == bpy.types.Object:
        return False
    # check if animation_data exists on object
    if not id_data.animation_data:
        return False
    # check if driver exists at path
    rna_path = data_path_from_button_context(context)
    for fc in id_data.animation_data.drivers:
        if fc.data_path == rna_path:
            return True
    return False

class UT_OT_copy_driver_to_selected(bpy.types.Operator):
    """
    Copy driver from context to all selected objects at the same relative rna path.
    """
    bl_idname = "utils.copy_driver_to_selected"
    bl_label = "Copy Driver to Selected"
    bl_description = " Copy driver from context to all selected objects at the same relative rna path"
    bl_options = {"REGISTER", "UNDO"}
    
    single: bpy.props.BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        return copy_driver_to_selected_poll(context)

    def execute(self, context):
        
        id_data = context.button_pointer.id_data
        rna_path = data_path_from_button_context(context)
        f_curve = None
        for fc in id_data.animation_data.drivers:
            if fc.data_path == rna_path:
                f_curve = fc
                break

        fails = []
        for ob in context.selected_editable_objects:
            if ob == context.object:
                continue
            try :
                d = copy_driver(f_curve, ob)
            except:
                fails += [ob.name]
        
        if fails:
            print(f'Warning: Failed to copy driver on {len(fails)} objects at {rna_path}:')
            print(fails)

        return {"FINISHED"}

class WM_MT_button_context(bpy.types.Menu):
    bl_label = ""

    def draw(self, context):
        pass

def draw(self, context):
    layout = self.layout
    if not copy_driver_to_selected_poll(context):
        return
    layout.separator()
    layout.operator('utils.copy_driver_to_selected', icon='DECORATE_DRIVER')

if __name__ == "__main__":
    bpy.utils.register_class(UT_OT_copy_driver_to_selected)
    
    # Register menu only if it doesn't already exist.
    rcmenu = getattr(bpy.types, "WM_MT_button_context", None)
    if rcmenu is None:
        bpy.utils.register_class(WM_MT_button_context)
        rcmenu = WM_MT_button_context

    # Retrieve a python list for inserting draw functions.
    draw_funcs = rcmenu._dyn_ui_initialize()
    draw_funcs.append(draw)
Add an operator to copy a driver from the active to all selected objects at the same data path in the context menu of driven properties on object ids. - if a driver at that path already exists it should be overwritten - same as for the existing `Copy to Selected` functionality there needs to be an alternative split into `Copy Single ...` and `Copy All ...` for array properties I spoke to @dr.sybren and @nathanvegdahl about creating this task for reference. Here is a script that provides a mockup for this operator (missing support for custom properties and arrays): <details> ```Py import bpy from rigify.utils.misc import copy_attributes def copy_driver( from_fcurve: bpy.types.FCurve, target: bpy.types.ID, data_path=None, index=None ) -> bpy.types.FCurve: """Copy an existing FCurve containing a driver to a new ID, by creating a copy of the existing driver on the target ID. Args: from_fcurve (bpy.types.FCurve): FCurve containing a driver target (bpy.types.ID): ID that can have drivers added to it data_path (_type_, optional): Data Path of existing driver. Defaults to None. index (_type_, optional): array index of the property drive. Defaults to None. Returns: bpy.types.FCurve: Fcurve containing copy of driver on target ID """ if not data_path: data_path = from_fcurve.data_path new_fc = None if index: new_fc = target.driver_add(data_path, index) else: new_fc = target.driver_add(data_path) copy_attributes(from_fcurve, new_fc) copy_attributes(from_fcurve.driver, new_fc.driver) # Remove default modifiers, variables, etc. for m in new_fc.modifiers: new_fc.modifiers.remove(m) for v in new_fc.driver.variables: new_fc.driver.variables.remove(v) # Copy modifiers for m1 in from_fcurve.modifiers: m2 = new_fc.modifiers.new(type=m1.type) copy_attributes(m1, m2) # Copy variables for v1 in from_fcurve.driver.variables: v2 = new_fc.driver.variables.new() copy_attributes(v1, v2) for i in range(len(v1.targets)): copy_attributes(v1.targets[i], v2.targets[i]) return new_fc def data_path_from_button_context(context): # TODO: support custom properties # TODO: support array properties return f'{context.button_pointer.path_from_id()}.{context.button_prop.identifier}' def copy_driver_to_selected_poll(context): # check for button context if not hasattr(context, 'button_pointer'): return False # assign pointer and null-check b_pointer = getattr(context, 'button_pointer') if not b_pointer: return False # check if id is an object id_data = b_pointer.id_data if not type(id_data) == bpy.types.Object: return False # check if animation_data exists on object if not id_data.animation_data: return False # check if driver exists at path rna_path = data_path_from_button_context(context) for fc in id_data.animation_data.drivers: if fc.data_path == rna_path: return True return False class UT_OT_copy_driver_to_selected(bpy.types.Operator): """ Copy driver from context to all selected objects at the same relative rna path. """ bl_idname = "utils.copy_driver_to_selected" bl_label = "Copy Driver to Selected" bl_description = " Copy driver from context to all selected objects at the same relative rna path" bl_options = {"REGISTER", "UNDO"} single: bpy.props.BoolProperty(default=False) @classmethod def poll(cls, context): return copy_driver_to_selected_poll(context) def execute(self, context): id_data = context.button_pointer.id_data rna_path = data_path_from_button_context(context) f_curve = None for fc in id_data.animation_data.drivers: if fc.data_path == rna_path: f_curve = fc break fails = [] for ob in context.selected_editable_objects: if ob == context.object: continue try : d = copy_driver(f_curve, ob) except: fails += [ob.name] if fails: print(f'Warning: Failed to copy driver on {len(fails)} objects at {rna_path}:') print(fails) return {"FINISHED"} class WM_MT_button_context(bpy.types.Menu): bl_label = "" def draw(self, context): pass def draw(self, context): layout = self.layout if not copy_driver_to_selected_poll(context): return layout.separator() layout.operator('utils.copy_driver_to_selected', icon='DECORATE_DRIVER') if __name__ == "__main__": bpy.utils.register_class(UT_OT_copy_driver_to_selected) # Register menu only if it doesn't already exist. rcmenu = getattr(bpy.types, "WM_MT_button_context", None) if rcmenu is None: bpy.utils.register_class(WM_MT_button_context) rcmenu = WM_MT_button_context # Retrieve a python list for inserting draw functions. draw_funcs = rcmenu._dyn_ui_initialize() draw_funcs.append(draw) ``` </details>
Simon Thommes added the
Type
To Do
label 2024-04-11 13:14:19 +02:00
Simon Thommes added this to the Animation & Rigging project 2024-04-11 13:14:22 +02:00
Member

Hello,
I think this task is heavily related (if not duplicated) : #111276

Hello, I think this task is heavily related (if not duplicated) : #111276
Author
Member

Related, sure, but I don't think this is a duplicate. Would be great if that task could be resolved before though!

Related, sure, but I don't think this is a duplicate. Would be great if that task could be resolved before though!
Member

Sure, having a PyAPI for copy driver from the other one ticket will help having the operator from this one!

Sure, having a PyAPI for copy driver from the other one ticket will help having the operator from this one!
Sybren A. Stüvel added the
Module
Animation & Rigging
label 2024-04-11 15:56:47 +02:00
Nathan Vegdahl self-assigned this 2024-04-12 11:56:16 +02:00
Blender Bot added the
Status
Resolved
label 2024-04-29 18:35:10 +02:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset System
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Code Documentation
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Viewport & EEVEE
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Asset Browser Project
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Module
Viewport & EEVEE
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Severity
High
Severity
Low
Severity
Normal
Severity
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#120518
No description provided.