UuuNyaa / blender_mmd_tools

MMD Tools is a blender addon for importing/exporting Models and Motions of MikuMikuDance.
GNU General Public License v3.0
1.94k stars 92 forks source link

[Feature] Partial support for library override workflow #64

Closed One-sixth closed 1 year ago

One-sixth commented 1 year ago

Hello, This is a feature request about library overrides.

This is my modified branch https://github.com/One-sixth/blender_mmd_tools/tree/support_library_override All changes are contained in three commit https://github.com/One-sixth/blender_mmd_tools/commit/7a507fc6348e3469706a5cf7af01ddd267445a80 https://github.com/One-sixth/blender_mmd_tools/commit/09220c02f9d746721efc8c836843474c903ce87c https://github.com/One-sixth/blender_mmd_tools/commit/d0caf4316e37c57dd97f859fdda0adeedb8caba9

I've added the option to allow library overrides to the props, allowing mmd_tools to do some of the work in the library override environment. Such as modifying materials This modification will not affect compatibility.

Library overrides allow us to easily pass changes to the linked model when we modify the base model, so we don't need to modify multiple copies. For example, if you add a new morph to the base model, the new morph can be automatically synced to other linked blend files. I currently use this feature to create multiple sets of materials for the model.

Because mesh data and material relationships are not editable. (The material relationships of mesh data are lost when save and reload) So the method of modifying the library override material is: Make root_obj mesh_obj library override editable, then change the material slot to bind with mesh_obj, and then add a new material. Then use the user_remap function to remap old material all references to the new material.

Due to some of the current limitations of the Blender library overriding, some features are not yet easy to use. If you want to build physics, you need to make_local arm_obj and arm_data, or precompile in the base model. If you want to rebuild addition_bone, you also need to make_local arm_obj and arm_data, or precompile them in the base model. This is because they require access to the skeleton's edit mode, which library overrides currently don't support.

I tested currently works fine with library override. (Needs in a local collection. If it is a library override collection, the collection object needs to be make_local first) Morph: Material Morph, Bone Morph, Group Morph, ShapeKey Morph Animation: Skeletal animation and Morph animation (should ensure .placeholder no exist OR delete .placeholder first OR make_local .placeholder, .placeholder data and .dummy_armature)

nagadomi commented 1 year ago

This modification will not affect compatibility.

override keyword argument is only available in Blender 2.9 or later. I am not sure mmd_tools still supports 2.83, but the following code should work fine with Blender 2.83.

allow_prop_override = dict(override={'LIBRARY_OVERRIDABLE'},) if bpy.app.version >= (2, 90, 0) else dict()
One-sixth commented 1 year ago

@nagadomi You are right, thanks for your code, I didn't take into account the case of blender version less than 3. I'll add it later.

UuuNyaa commented 1 year ago

@One-sixth Thank you for your effort 😃

I tried to add this feature before.

Then Blender's proxy mechanism was discontinued and the issue was pending. Thanks to your code, we are finally going to get this feature.

@nagadomi @One-sixth As stated in Blender Version Support Policies, we need to support the current 2.93 LTS. However there would be no need to support 2.83 LTS.

One-sixth commented 1 year ago

@UuuNyaa Although it was not completed, thank you for your previous efforts. The library overlay had serious performance issues that were not fixed until blender 3.2, https://developer.blender.org/T94059 Currently the library override feature is still not convenient enough, we can only hope it will work better in 3.3 or 3.4.

In the current implementation, I found a new bug. The rig.materials will not search the object's materials . This will cause Material Morph not work for object' materials. I've added a new modification that allows rig.materials to search the object's materials. https://github.com/One-sixth/blender_mmd_tools/commit/d0caf4316e37c57dd97f859fdda0adeedb8caba9

UuuNyaa commented 1 year ago

@One-sixth I found a way to batch patch all properties.

diff --git a/mmd_tools/properties/__init__.py b/mmd_tools/properties/__init__.py
index 0f0f272..f5095c2 100644
--- a/mmd_tools/properties/__init__.py
+++ b/mmd_tools/properties/__init__.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-

+import itertools
 import logging

 import bpy
@@ -64,6 +65,18 @@ def __set_hide(prop, value):

 def __patch(properties):  # temporary patching, should be removed in the future
+    def __patch_override(prop: bpy.props._PropertyDeferred):
+        if prop.function.__name__ in {'PointerProperty', 'CollectionProperty'}:
+            type = prop.keywords['type']
+            if type.__module__.startswith('mmd_tools.properties'):
+                for annotation in type.__annotations__.values():
+                    if isinstance(annotation, bpy.props._PropertyDeferred):
+                        __patch_override(annotation)
+        prop.keywords['override'] = {'LIBRARY_OVERRIDABLE'}
+
+    for prop in itertools.chain(*[properties[target].values() for target in [bpy.types.Object, bpy.types.Material, bpy.types.PoseBone]]):
+        __patch_override(prop)
+
     prop_obj = properties.setdefault(bpy.types.Object, {})

     prop_obj['select'] = bpy.props.BoolProperty(

I have two questions:

UuuNyaa commented 1 year ago

In the current implementation, I found a new bug. The rig.materials will not search the object's materials . This will cause Material Morph not work for object' materials. I've added a new modification that allows rig.materials to search the object's materials. https://github.com/One-sixth/blender_mmd_tools/commit/d0caf4316e37c57dd97f859fdda0adeedb8caba9

Thank you for your commit! 😃 Released as v2.5.1.

One-sixth commented 1 year ago

@UuuNyaa Great! That way we no longer need to manually add the override property for each new property. I found and fix a small bug. mmd_tools.properties.BoneMorph etc. Their depends on _MorphBase class not being applied successfully.

The code uses undocumented types, maybe we need to add some hints to help future participants understand quickly.

# Hint for bpy.props._PropertyDeferred class
# p = bpy.props.IntProperty(name='abc123')
# assert isinstance(p, bpy.props._PropertyDeferred)
# assert p.function == bpy.props.IntProperty
# assert p.keywords == {'name': 'abc123'}

def __patch(properties):  # temporary patching, should be removed in the future

    def __patch_override(prop: bpy.props._PropertyDeferred):
        # Apply recursively for each mmd_tools property class annotations
        if prop.function.__name__ in {'PointerProperty', 'CollectionProperty'}:
            type = prop.keywords['type']
            # The __annotations__ cannot be inherited. Manually search for base classes.
            types = list(type.__bases__) + [type]
            for type in types:
                if type.__module__.startswith('mmd_tools.properties'):
                    for annotation in type.__annotations__.values():
                        if isinstance(annotation, bpy.props._PropertyDeferred):
                            __patch_override(annotation)

        prop.keywords['override'] = {'LIBRARY_OVERRIDABLE'}

    for type, props in properties.items():
        if type in {bpy.types.Object, bpy.types.Material, bpy.types.PoseBone}:
            for prop in props.values():
                __patch_override(prop)

    prop_obj = properties.setdefault(bpy.types.Object, {})

About the question. I think it's better to allow library overrides for all properties, unless there are clear downsides. For example Bone Name and Material Name . These properties are automatically synchronized with the base model if the user does not modify them manually.

UuuNyaa commented 1 year ago

@One-sixth I created a pull request #65

About the question. I think it's better to allow library overrides for all properties, unless there are clear downsides.

I don't really understand the use case for library overrides. Isn't it a clear downsides to change names by mistake?

One-sixth commented 1 year ago

@UuuNyaa This is my understanding. Library override is more like a lightweight version control system. The main purpose is to allow users to modify some properties while keeping other properties associated with the base library.

I understand your concern, the current implementation does not allow modification of blender bone names and material names, which may cause some mismatches.

For MMD material names, MMD bone names, there may be some rare reasons users want to change them. For example: multiple versions of PMX models with different bone names; using different MMD material names to refer to different material combinations.

If we mark them as non-overridable, then when the user wants to modify the property, the block must be make_local, which will cause other properties of the block to lose synchronization with the base library.

In the future, if there are certain attributes that we do not want users to modify, or the modification will cause the loss of associated data, maybe we can use the method of proposal T95816 to mark. T95816 This marks some properties as recommended for editing and others as not, and provides a convenient recover button. https://developer.blender.org/T95816

This is the current development status of the library override: https://developer.blender.org/T73318 Maybe in Blender 3.6 or Blender 3.8, the library overlay will support skeleton editing and mesh editing.

UuuNyaa commented 1 year ago

@One-sixth Thank you for the detailed explanation. I agreed that all properties should be overrideable.

And I think overridable should be the default given T95816 🤔

One-sixth commented 1 year ago

Great !