PyAPI for copying drivers #111276

Closed
opened 2023-08-18 19:22:52 +02:00 by Demeter Dzadik · 9 comments
Member

Copying drivers via the Python API is currently not possible.

This would mean copying a driver's type, expression, variables, curves, etc, to some other property, potentially on another ID. So, exactly what the right click menu's Copy/Paste Driver button does.

Use cases:

  • Rigify needs to copy drivers from Metarigs to its generated rigs (and then potentially modify them).
  • At Blender Studio, our Asset Pipeline combines data from different artists' files to create a final asset. It needs to copy drivers from the rigging file over to the final asset.
  • The Copy Attributes add-on could copy drivers along with constraints and modifiers. (I'd actually like to work on this - I have my own version of this add-on that does copy drivers, and it's great.)

The first and second currently implement their own driver copy function which is large and yet incomplete; We have to account for every possible thing a driver could have, including keyframes, fcurve modifiers, and future features, like the new fallback option. It's a lot of repeated code in many add-ons.

Proposals
Myself and @angavrilov discussed a number of additions that could be made to the PyAPI to enable copying of drivers:

  • A new copy() function to animation_data.drivers:
my_obj.animation_data.drivers.copy(new_path, src_driver, new_index=0)
  • A new copy_from param to the existing driver_add() function:
src_drv = my_sphere.animation_data.drivers.find('constraints["Copy Location"].influence')
my_cube.driver_add('constraints["Copy Location"].influence', copy_from=src_drv)
  • Update the is_empty property on Drivers, such that its value is True if the driver still has the default set-up that it got created with.
fcurve = obj.driver_add(..., replace=False)

if fcurve.is_empty:
  # initialize fcurve.driver ...

Feedback and further discussion welcome.

Copying drivers via the Python API is currently not possible. This would mean copying a driver's type, expression, variables, curves, etc, to some other property, potentially on another ID. So, exactly what the right click menu's Copy/Paste Driver button does. **Use cases**: - **Rigify** needs to copy drivers from Metarigs to its generated rigs (and then potentially modify them). - At Blender Studio, our **Asset Pipeline** combines data from different artists' files to create a final asset. It needs to copy drivers from the rigging file over to the final asset. - The **Copy Attributes** add-on could copy drivers along with constraints and modifiers. (I'd actually like to work on this - I have my own version of this add-on that does copy drivers, and it's great.) The first and second currently implement their own driver copy function which is large and yet incomplete; We have to account for every possible thing a driver could have, including keyframes, fcurve modifiers, and future features, like the [new fallback option](https://projects.blender.org/blender/blender/pulls/110135). It's a lot of repeated code in many add-ons. **Proposals** Myself and @angavrilov discussed a number of additions that could be made to the PyAPI to enable copying of drivers: - A new `copy()` function to animation_data.drivers: ``` my_obj.animation_data.drivers.copy(new_path, src_driver, new_index=0) ``` - A new `copy_from` param to the existing `driver_add()` function: ``` src_drv = my_sphere.animation_data.drivers.find('constraints["Copy Location"].influence') my_cube.driver_add('constraints["Copy Location"].influence', copy_from=src_drv) ``` - Update the `is_empty` property on Drivers, such that its value is True if the driver still has the default set-up that it got created with. ``` fcurve = obj.driver_add(..., replace=False) if fcurve.is_empty: # initialize fcurve.driver ... ``` Feedback and further discussion welcome.
Demeter Dzadik added the
Type
Design
label 2023-08-18 19:22:52 +02:00
Alexander Gavrilov was assigned by Demeter Dzadik 2023-08-18 19:22:52 +02:00
Demeter Dzadik added this to the Animation & Rigging project 2023-08-18 19:22:54 +02:00
Author
Member

@angavrilov In our chat, you also had an idea for an is_empty flag for drivers, but I'm wondering if the replace keyword being added to the adding functions doesn't address the same issue.
I think both things would take care of the concern of the caller being unsure if their driver is fresh or not - I mean, if we want a fresh driver, we'll just pass replace=True, right? So we don't really need an is_empty flag, imo.

@angavrilov In our chat, you also had an idea for an `is_empty` flag for drivers, but I'm wondering if the `replace` keyword being added to the adding functions doesn't address the same issue. I think both things would take care of the concern of the caller being unsure if their driver is fresh or not - I mean, if we want a fresh driver, we'll just pass `replace=True`, right? So we don't really need an `is_empty` flag, imo.
Member

This feature can be very useful for any add-on developer / riggers.

We discussed it, on animation-module channel, a few days ago with Alexander too.
On glTF exporter, this feature will also enable a workaround that avoid a full depsgraph update during export (by copying a temp driver on objects that really need to be updated during animation export)

This feature can be very useful for any add-on developer / riggers. We discussed it, on animation-module channel, a few days ago with Alexander too. On glTF exporter, this feature will also enable a workaround that avoid a full depsgraph update during export (by copying a temp driver on objects that really need to be updated during animation export)

@angavrilov In our chat, you also had an idea for an is_empty flag for drivers, but I'm wondering if the replace keyword being added to the adding functions doesn't address the same issue.
I think both things would take care of the concern of the caller being unsure if their driver is fresh or not - I mean, if we want a fresh driver, we'll just pass replace=True, right? So we don't really need an is_empty flag, imo.

I think adding is_empty is necessary in order to make the replace=False case useful at all. For backward compatibility it would probably still remain the default (or will we change it?), and having a useless default is quite bad I think. If you can somehow determine if the driver was added or not, you can code a behavior that preserves an existing one, like:

fcurve = obj.driver_add(..., replace=False)

if fcurve.is_empty:
  initialize fcurve.driver ...

Technically speaking this doesn't exactly detect whether the driver was added by the call, but initializing the driver if it is empty of any data is a close enough substitute.

> @angavrilov In our chat, you also had an idea for an `is_empty` flag for drivers, but I'm wondering if the `replace` keyword being added to the adding functions doesn't address the same issue. > I think both things would take care of the concern of the caller being unsure if their driver is fresh or not - I mean, if we want a fresh driver, we'll just pass `replace=True`, right? So we don't really need an `is_empty` flag, imo. I think adding `is_empty` is necessary in order to make the `replace=False` case useful at all. For backward compatibility it would probably still remain the default (or will we change it?), and having a useless default is quite bad I think. If you can somehow determine if the driver was added or not, you can code a behavior that preserves an existing one, like: ``` fcurve = obj.driver_add(..., replace=False) if fcurve.is_empty: initialize fcurve.driver ... ``` Technically speaking this doesn't exactly detect whether the driver was added by the call, but initializing the driver if it is empty of any data is a close enough substitute.

@Mets It's not exactly new, is_empty already exists, but only checks if the fcurve has no keyframes (it's basically for conveniently checking if you should delete the fcurve too after deleting some keyframes). I suggest extending it to check if the driver has any non-default settings for driver fcurves.

@Mets It's not exactly new, `is_empty` already exists, but only checks if the fcurve has no keyframes (it's basically for conveniently checking if you should delete the fcurve too after deleting some keyframes). I suggest extending it to check if the driver has any non-default settings for driver fcurves.
Author
Member

Thanks for the clarification, added it to the task description.

Thanks for the clarification, added it to the task description.
Member

I know this is only a design task for now, but any idea of the timeline here? Is there a target milestone (4.0, 4.1, later ?) for this (awesome) feature?

I know this is only a design task for now, but any idea of the timeline here? Is there a target milestone (4.0, 4.1, later ?) for this (awesome) feature?
Author
Member

I think it's mostly up to how fast the patch review bottleneck gets unclogged by our lovely Amsterdam devs, and of course Alexander is a volunteer dev so it's also up to his availability and mood (But he tends to be very quick as long as his patches get reviewed). I think 4.0 is in feature freeze already, so fingers crossed for 4.1. :)

I think it's mostly up to how fast the patch review bottleneck gets unclogged by our lovely Amsterdam devs, and of course Alexander is a volunteer dev so it's also up to his availability and mood (But he tends to be very quick as long as his patches get reviewed). I think 4.0 is in feature freeze already, so fingers crossed for 4.1. :)
Sybren A. Stüvel added the
Module
Animation & Rigging
label 2024-04-11 15:57:22 +02:00
Member

While working on #120518 I discovered that there is already a function for duplicating drivers: from_existing(). You call it on animation_data.drivers, and it duplicates the given driver into that list of drivers. The passed driver can be either from the same ID or a different one--it doesn't matter.

The created driver will be an exact duplicate, and therefore have the same data_path and array_index. The function does no checks for if there's already a driver with that data_path and array_index, so you can end up with multiple redundant drivers if you're not careful. But the data_path and array_index can of course be changed, so if you want to change the property the driver is for you can just do it after you duplicate.

I think this might already satisfy the requirements of this design task...?

While working on #120518 I discovered that there is already a function for duplicating drivers: [from_existing()](https://docs.blender.org/api/current/bpy.types.AnimDataDrivers.html#bpy.types.AnimDataDrivers.from_existing). You call it on `animation_data.drivers`, and it duplicates the given driver into that list of drivers. The passed driver can be either from the same ID or a different one--it doesn't matter. The created driver will be an exact duplicate, and therefore have the same `data_path` and `array_index`. The function does no checks for if there's already a driver with that `data_path` and `array_index`, so you can end up with multiple redundant drivers if you're not careful. But the `data_path` and `array_index` can of course be changed, so if you want to change the property the driver is for you can just do it after you duplicate. I think this might already satisfy the requirements of this design task...?
Author
Member

Wow, yeah, that works, huh. LOL.

Thank you Nathan, great find! I have some code to delete now!

Wow, yeah, that works, huh. LOL. Thank you Nathan, great find! I have some code to delete now!
Blender Bot added the
Status
Archived
label 2024-04-12 14:51:22 +02:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
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
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
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
EEVEE & Viewport
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
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
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
4 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#111276
No description provided.