Joining Armatures #84750

Open
opened 2021-01-15 15:51:24 +01:00 by Demeter Dzadik · 11 comments
Member

Joining armatures is something that will become common in my workflow in the coming months and knowing this, I had to write an operator to facilitate this. But it feels like this should just work without an addon. I'd like to know if the rest of the animation module agrees, and this is something that could go into Blender's Join operator, or if any of these changes are controversial.

This is something I just might be able to implement myself, but I'd rather let a competent developer do it, if there are any volunteers. :)

Here are the current problems when simply trying to join two armatures:

Bone Groups
Bone Groups of the active armature are preserved, but bone groups of the source armature(s) are completely nuked, or worse, assigned seemingly at random.
Sometimes two armatures will have bone groups with the same name. In this case it would be best to "merge" these bone groups. It could happen that identically named bone groups have different colors, in which case this color data will be lost. In this case such bone groups could be suffixed with a .001 instead of being merged. But strictly only if the bone group colors do not match, otherwise just go ahead and merge them.

Parenting
Currently, when joining Armature A into Armature B, all objects that were parented to A are now parented to B. This is good! However, if the armatures weren't in the same place, the parented objects will move because their transform matrix and parent inverse matrix is not set. (Currently in my script I set the transform matrix, but I guess changing the parent inverse matrix would be more correct.)

Modifier's References
Sticking to the above A->B example, armature modifiers that were referencing A now reference nothing. I think these references should be redirected to Armature B.
Perhaps this can be applied more generally, so that when any objects are joined into an object O, all references to the merged objects should be remapped to O? Are there any cases where this would be bad?
(Note: I tried doing this with the current bpy.ops.outliner.remap_users() operator, but it segfaulted.)

Vertex Groups
Bone names can clash, and then they need to be appended with a .001 in their name. That's fine, but in this case the objects that were rigged with the original bone names now have vertex group names that did not get renamed. Fixing this is very painful. So, these vertex groups should be renamed along with the bones.

Speed
Just a nitpick, but I find it odd that joining complex rigs can take up to 10 seconds! I've added some timers to my python operator to show how just the Join operation is taking super long. Maybe some low hanging fruit optimization is possible there?

Test files:
simple_armatures_with_objects.blend (Just press Ctrl+J to see the usual result, and Alt+H to see the desired result)
complex_armatures.blend (For testing the speed)

CC @dr.sybren @jpbouza-4 @LucianoMunoz and any other people interested in rigging, for opinions :)

Joining armatures is something that will become common in my workflow in the coming months and knowing this, I had to write an [operator](https://github.com/Mets3D/mets_tools/blob/master/armature_merge.py) to facilitate this. But it feels like this should just work without an addon. I'd like to know if the rest of the animation module agrees, and this is something that could go into Blender's Join operator, or if any of these changes are controversial. This is something I just might be able to implement myself, but I'd rather let a competent developer do it, if there are any volunteers. :) Here are the current problems when simply trying to join two armatures: **Bone Groups** Bone Groups of the active armature are preserved, but bone groups of the source armature(s) are completely nuked, or worse, assigned seemingly at random. Sometimes two armatures will have bone groups with the same name. In this case it would be best to "merge" these bone groups. It could happen that identically named bone groups have different colors, in which case this color data will be lost. In this case such bone groups could be suffixed with a .001 instead of being merged. But strictly only if the bone group colors do not match, otherwise just go ahead and merge them. **Parenting** Currently, when joining Armature A into Armature B, all objects that were parented to A are now parented to B. This is good! However, if the armatures weren't in the same place, the parented objects will move because their transform matrix and parent inverse matrix is not set. (Currently in my script I set the transform matrix, but I guess changing the parent inverse matrix would be more correct.) **Modifier's References** Sticking to the above A->B example, armature modifiers that were referencing A now reference nothing. I think these references should be redirected to Armature B. Perhaps this can be applied more generally, so that when any objects are joined into an object O, all references to the merged objects should be remapped to O? Are there any cases where this would be bad? (Note: I tried doing this with the current bpy.ops.outliner.remap_users() operator, but it segfaulted.) **Vertex Groups** Bone names can clash, and then they need to be appended with a .001 in their name. That's fine, but in this case the objects that were rigged with the original bone names now have vertex group names that did not get renamed. Fixing this is very painful. So, these vertex groups should be renamed along with the bones. **Speed** Just a nitpick, but I find it odd that joining complex rigs can take up to 10 seconds! I've added some timers to my python operator to show how just the Join operation is taking super long. Maybe some low hanging fruit optimization is possible there? Test files: [simple_armatures_with_objects.blend](https://archive.blender.org/developer/F9577649/simple_armatures_with_objects.blend) (Just press Ctrl+J to see the usual result, and Alt+H to see the desired result) [complex_armatures.blend](https://archive.blender.org/developer/F9577615/complex_armatures.blend) (For testing the speed) CC @dr.sybren @jpbouza-4 @LucianoMunoz and any other people interested in rigging, for opinions :)
Author
Member

Added subscriber: @Mets

Added subscriber: @Mets

Added subscriber: @JerBot

Added subscriber: @JerBot
Author
Member

Added subscribers: @LucianoMunoz, @jpbouza-4, @dr.sybren

Added subscribers: @LucianoMunoz, @jpbouza-4, @dr.sybren

But strictly only if the bone group colors do not match, otherwise just go ahead and merge [vertex groups with the same name].

I feel this could feel a bit arbitrary. Especially when the user doesn't know the color is the decisive factor here. I can imagine a situation where a rigger is less strict with the coloring; in that case a minute difference in hue/brightness would already impact whether the vertex groups are merged or not. How about a choice "Merge: Never/Same Name/Same Name And Color"? That way it's at least explicit that color is a factor in this.

the parented objects will move because their transform matrix and parent inverse matrix is not set. (Currently in my script I set the transform matrix, but I guess changing the parent inverse matrix would be more correct.)

Yes, this is exactly what the Parent Inverse matrix is for: to make it possible to change the parent without moving the object.

armature modifiers that were referencing A now reference nothing. I think these references should be redirected to Armature B.

I agree. It's a "Join" operation, not a "Delete an armature and add new bones to an unrelated armature that just by sheer coincidence happen to match what was deleted" operator.

(Note: I tried doing this with the current bpy.ops.outliner.remap_users() operator, but it segfaulted.)

Calling operators should be avoided in Python code. You should be able to call armature_a.users_remap(armature_b) and maybe also armature_object_a.users_remap(armature_object_b).

vertex groups should be renamed along with the bones.

Certainly.

Just a nitpick, but I find it odd that joining complex rigs can take up to 10 seconds!

That's a long time indeed! Do you have any idea where in the code this time is spent?

> But strictly only if the bone group colors do not match, otherwise just go ahead and merge [vertex groups with the same name]. I feel this could feel a bit arbitrary. Especially when the user doesn't know the color is the decisive factor here. I can imagine a situation where a rigger is less strict with the coloring; in that case a minute difference in hue/brightness would already impact whether the vertex groups are merged or not. How about a choice "Merge: Never/Same Name/Same Name And Color"? That way it's at least explicit that color is a factor in this. > the parented objects will move because their transform matrix and parent inverse matrix is not set. (Currently in my script I set the transform matrix, but I guess changing the parent inverse matrix would be more correct.) Yes, this is exactly what the Parent Inverse matrix is for: to make it possible to change the parent without moving the object. > armature modifiers that were referencing A now reference nothing. I think these references should be redirected to Armature B. I agree. It's a "Join" operation, not a "Delete an armature and add new bones to an unrelated armature that just by sheer coincidence happen to match what was deleted" operator. > (Note: I tried doing this with the current bpy.ops.outliner.remap_users() operator, but it segfaulted.) Calling operators should be avoided in Python code. You should be able to call `armature_a.users_remap(armature_b)` and maybe also `armature_object_a.users_remap(armature_object_b)`. > vertex groups should be renamed along with the bones. Certainly. > Just a nitpick, but I find it odd that joining complex rigs can take up to 10 seconds! That's a long time indeed! Do you have any idea where in the code this time is spent?
Author
Member

How about a choice

I'm all for choice! :D I guess the safest default then might be "Never".

armature_a.users_remap(armature_b)

As far as I can tell this isn't exposed to PyAPI? 👀 If you're saying it should be, I certainly agree! Heck, there should be some convention to always expose operator code as non-operator functions for PyAPI, but that's another discussion... :D

Do you have any idea where in the code this time is spent?

Nope, I didn't try touch or look at the C code, I just slapped my Python operator together and called it a day for now. But, I'd love to spend some of my free time banging my head against some C code again sometime :) (coding nights? ^^)

> How about a choice I'm all for choice! :D I guess the safest default then might be "Never". > armature_a.users_remap(armature_b) As far as I can tell this isn't exposed to PyAPI? :eyes: If you're saying it should be, I certainly agree! Heck, there should be some convention to always expose operator code as non-operator functions for PyAPI, but that's another discussion... :D > Do you have any idea where in the code this time is spent? Nope, I didn't try touch or look at the C code, I just slapped my Python operator together and called it a day for now. But, I'd love to spend some of my free time banging my head against some C code again sometime :) (coding nights? ^^)

In #84750#1095571, @Mets wrote:

armature_a.users_remap(armature_b)

As far as I can tell this isn't exposed to PyAPI?

It is, but only if you spell it properly (my mistake): https://docs.blender.org/api/master/bpy.types.ID.html#bpy.types.ID.user_remap

there should be some convention to always expose operator code as non-operator functions for PyAPI, but that's another discussion... :D

I agree (on both statements ;-) )

Do you have any idea where in the code this time is spent?

Nope, I didn't try touch or look at the C code, I just slapped my Python operator together and called it a day for now. But, I'd love to spend some of my free time banging my head against some C code again sometime :) (coding nights? ^^)

Ok, if "the C code" is the best we get, that's fine :)

> In #84750#1095571, @Mets wrote: >> armature_a.users_remap(armature_b) > As far as I can tell this isn't exposed to PyAPI? It is, but only if you spell it properly (my mistake): https://docs.blender.org/api/master/bpy.types.ID.html#bpy.types.ID.user_remap > there should be some convention to always expose operator code as non-operator functions for PyAPI, but that's another discussion... :D I agree (on both statements ;-) ) >> Do you have any idea where in the code this time is spent? > Nope, I didn't try touch or look at the C code, I just slapped my Python operator together and called it a day for now. But, I'd love to spend some of my free time banging my head against some C code again sometime :) (coding nights? ^^) Ok, if "the C code" is the best we get, that's fine :)
Author
Member

In #84750#1095648, @dr.sybren wrote:

In #84750#1095571, @Mets wrote:

armature_a.users_remap(armature_b)

As far as I can tell this isn't exposed to PyAPI?

It is, but only if you spell it properly (my mistake)

It doesn't help that I'm blind! :D

Ok, if "the C code" is the best we get, that's fine :)

Yepyep, the existing Join operator seems to take several seconds when joining two complex armatures.

> In #84750#1095648, @dr.sybren wrote: >> In #84750#1095571, @Mets wrote: >>> armature_a.users_remap(armature_b) >> As far as I can tell this isn't exposed to PyAPI? > > It is, but only if you spell it properly (my mistake) It doesn't help that I'm blind! :D > Ok, if "the C code" is the best we get, that's fine :) Yepyep, the existing Join operator seems to take several seconds when joining two complex armatures.

Added subscriber: @AquaticNightmare

Added subscriber: @AquaticNightmare
Member

Added subscriber: @lichtwerk

Added subscriber: @lichtwerk
Member

Changed status from 'Needs Triage' to: 'Confirmed'

Changed status from 'Needs Triage' to: 'Confirmed'
Member

Sounds like this is Confirmed.

Sounds like this is `Confirmed`.
Philipp Oeser removed the
Interest
Animation & Rigging
label 2023-02-09 14:35:55 +01: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
No project
No Assignees
5 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#84750
No description provided.