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.95k stars 92 forks source link

Fixed bone morph exception, _getVisibilityOfMMDRigArmature exception and rigidbody data loss and regeneration. #70

Closed One-sixth closed 1 year ago

One-sixth commented 1 year ago

Hello, I found 3 new issue. My fix code you can found here. https://github.com/One-sixth/blender_mmd_tools/commit/aa4ae05295d8e61444028d63a8491522a2ad0fb0#diff-8b0a509ad46738648aa4ee1d9579ee6f59712a9b965f1da7c75c23780bbbe75a

  1. bone morph exception and _getVisibilityOfMMDRigArmature exception. Open the link.blend file and you will see two exceptions in the console.

  2. rigidbody data loss after rigidbody_world_remove. Open the ori.blend. Build model physics. Update world. Play 5 frame. Remove the physical environment by bpy.ops.rigidbody.world_remove(). d3

Select any MMD rigidbody, you can see the rigidbody object has disappeared. d1 d2

This is the test file. 奥托2.zip

UuuNyaa commented 1 year ago

Thanks for fixing the bugs 😃 Please check the #73 pull request.

I looked into "rigidbody data loss" and found that the problem is in Blender itself. The following steps also reproduce the problem:

  1. Open the ori.blend.
  2. Play 5 frame.
  3. Remove the physical environment by bpy.ops.rigidbody.world_remove().

After this, "joint data" also becomes unstable.

I have considered radical fixes, but without fixing Blender itself it is difficult to fix this problem.

One-sixth commented 1 year ago

@UuuNyaa I checked the code and thought it was okay. About the joint data unstable. I found that just clicking the Update World button then works fine again. This is because after rebuilding the physics, the new RigidBodyConstraints.XXX collection only contains ncc joints. If we check and add origin joints to the new RigidBodyConstraints.XXX collection, we can avoid using Update Wolrd. Or, maybe we can reuse the old RigidBodyWorld and RigidBodyConstraints collection. Because they have the fake_user flag, they are not automatically deleted.

UuuNyaa commented 1 year ago

@One-sixth Thank you for your review.

I fixed mmd_tools.rigid_body_world_update operator to add rigid body objects to the new rigid body world collection. Now we can rebuild the physics with the following steps:

  1. Open the ori.blend.
  2. Play 5 frame.
  3. Remove the physical environment by bpy.ops.rigidbody.world_remove().
  4. Update Rigid Body World by bpy.ops.mmd_tools.rigid_body_world_update().
  5. Move to the first frame.
  6. Play 5 frame.
One-sixth commented 1 year ago

@UuuNyaa Thanks to your efforts, I found one thing that could be improved.

  1. Open the ori.blend.
  2. Build model physics. <- new step
  3. Play 5 frame.
  4. Remove the physical environment by bpy.ops.rigidbody.world_remove().
  5. Update Rigid Body World by bpy.ops.mmd_tools.rigid_body_world_update().
  6. Move to the first frame.
  7. Display all rigid bodies.
  8. Play 5 frame. All rigid bodies fall vertically.

I made some improvements that will automatically rebuild the physics.

Due to a bug in Blender, the first play after rebuild physics will crash. Save it before, Then open new Blender and load it again. Then play it and seem work fine.

  1. Open the ori.blend.
  2. Build model physics.
  3. Play 5 frame.
  4. Remove the physical environment by bpy.ops.rigidbody.world_remove().
  5. Update Rigid Body World by bpy.ops.mmd_tools.rigid_body_world_update().
  6. Save blend file.
  7. Play 5 frame. Blender crash.
  8. Open new blender and load the blend file just saved.
  9. Move to the first frame.
  10. Display all rigid bodies.
  11. Play 5 frame. It work fine.
class UpdateRigidBodyWorld(Operator):
    bl_idname = 'mmd_tools.rigid_body_world_update'
    bl_label = 'Update Rigid Body World'
    bl_description = 'Update rigid body world and references of rigid body constraint according to current scene objects (experimental)'
    bl_options = {'REGISTER', 'UNDO'}

    @staticmethod
    def __get_rigid_body_world_objects():
        rigid_body.setRigidBodyWorldEnabled(True)
        rbw = bpy.context.scene.rigidbody_world
        if bpy.app.version < (2, 80, 0):
            if not rbw.group:
                rbw.group = bpy.data.groups.new('RigidBodyWorld')
                rbw.group.use_fake_user = True
            if not rbw.constraints:
                rbw.constraints = bpy.data.groups.new('RigidBodyConstraints')
                rbw.constraints.use_fake_user = True
            return rbw.group.objects, rbw.constraints.objects

        if not rbw.collection:
            rbw.collection = bpy.data.collections.new('RigidBodyWorld')
            rbw.collection.use_fake_user = True
        if not rbw.constraints:
            rbw.constraints = bpy.data.collections.new('RigidBodyConstraints')
            rbw.constraints.use_fake_user = True

        if hasattr(bpy.context.scene.rigidbody_world, 'substeps_per_frame'):
            bpy.context.scene.rigidbody_world.substeps_per_frame = 1
            bpy.context.scene.rigidbody_world.solver_iterations = 60

        return rbw.collection.objects, rbw.constraints.objects

    def execute(self, context):
        scene_objs = set(context.scene.objects)
        scene_objs.union(o for x in context.scene.objects if x.instance_type == 'COLLECTION' and x.instance_collection for o in x.instance_collection.objects)

        def _update_group(obj, group):
            if obj in scene_objs:
                if obj not in group.values():
                    group.link(obj)
                return True
            elif obj in group.values():
                group.unlink(obj)
            return False

        def _references(obj):
            yield obj
            if getattr(obj, 'proxy', None):
                yield from _references(obj.proxy)
            if getattr(obj, 'override_library', None):
                yield from _references(obj.override_library.reference)

        _find_root = mmd_model.FnModel.find_root
        need_rebuild_physics = bpy.context.scene.rigidbody_world is None or bpy.context.scene.rigidbody_world.collection is None or bpy.context.scene.rigidbody_world.constraints is None
        rb_objs, rbc_objs = self.__get_rigid_body_world_objects()
        objects = bpy.data.objects
        table = {}

        # Perhaps due to a bug in Blender,
        # when bpy.ops.rigidbody.world_remove(),
        # Object.rigid_body are removed,
        # but Object.rigid_body_constraint are retained.
        # Therefore, it must be checked with Object.mmd_type.
        for i in (x for x in objects if x.mmd_type == 'RIGID_BODY'):
            if not _update_group(i, rb_objs):
                continue

            rb_map = table.setdefault(_find_root(i), {})
            if i in rb_map: # means rb_map[i] will replace i
                rb_objs.unlink(i)
                continue
            for r in _references(i):
                rb_map[r] = i

            # TODO Modify mmd_rigid to allow recovery of the remaining rigidbody parameters.
            # mass, friction, restitution, linear_dumping, angular_dumping

        for i in (x for x in objects if x.rigid_body_constraint):
            if not _update_group(i, rbc_objs):
                continue

            rbc, root = i.rigid_body_constraint, _find_root(i)
            rb_map = table.get(root, {})
            rbc.object1 = rb_map.get(rbc.object1, rbc.object1)
            rbc.object2 = rb_map.get(rbc.object2, rbc.object2)

        if need_rebuild_physics:
            for root in bpy.context.scene.objects:
                if root.mmd_type != 'ROOT':
                    continue
                if root.mmd_root.is_built:
                    from ..bpyutils import activate_layer_collection

                    with activate_layer_collection(root):
                        rig = mmd_model.Model(root)
                        # rig.clean()
                        rig.build(1.5, 1e-6)
                        # After rebuild. irst play. Will be crash! But saved it before. Reload after crash. The play can be work.

        return { 'FINISHED' }
One-sixth commented 1 year ago

I opened a blender bug report about rigidbody setting lost. https://developer.blender.org/T101656 From the response, not sure it can be fixed.

UuuNyaa commented 1 year ago

@One-sixth I adopted your code snippet. Indeed, in this situation, blender crashes when rebuilding the physics.

I opened a blender bug report about rigidbody setting lost. https://developer.blender.org/T101656 From the response, not sure it can be fixed.

Umm..., there seems to be no rigid body physics area maintainer. The Blender's current physics system would be replaced by a new Nodes & Physics system.

One-sixth commented 1 year ago

@UuuNyaa I reviewed the code. I think there is no problem. For new physical systems. I think it may take a long time. It's too far away. For the crash problem, I think they may fix it faster, because Blender 3.3 also has a physical update. I also submitted a bug report. https://developer.blender.org/T101681

UuuNyaa commented 1 year ago

@One-sixth Thank you for your review. It was released as v2.7.0 🎉

For new physical systems. I think it may take a long time. It's too far away.

I agree with you.

For the crash problem, I think they may fix it faster, because Blender 3.3 also has a physical update.

OK, let's hope the problems are solved.

One-sixth commented 1 year ago

Great !