Last active
January 3, 2023 22:23
-
-
Save UuuNyaa/925be8e892f3793386ff3db4e9e5cf7e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This program is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation; either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, but | |
| # WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| # General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| """ | |
| このプログラムはフラグメント化されたメッシュに対して次の処理を行う: | |
| 1. MMDオブジェクトを作り、メッシュを取り付ける | |
| 2. フラグメント毎に頂点グループを作成する | |
| 3. フラグメント毎にボーンを作成する | |
| 4. フラグメント毎にリジッドボディを作成する | |
| This program performs the following operations on the fragmented mesh: | |
| 1. Create an MMD object and attach the mesh | |
| 2. Create vertex groups for each fragment | |
| 3. Create bones for each fragment | |
| 4. Create rigid bodies for each fragment | |
| Requirements: | |
| - Blender 2.93 LTS or above | |
| - MMD Tools v2.2.4 or above | |
| Discord Server: MMD & Blender: | |
| https://discord.gg/zRgUkuaPWw | |
| """ | |
| import sys | |
| import time | |
| from dataclasses import dataclass | |
| from typing import List, Set, Tuple | |
| import bmesh | |
| import bpy | |
| import mathutils | |
| import mmd_tools | |
| from mathutils import Vector | |
| bl_info = { | |
| 'name': 'mmd_rigidbody_tools', | |
| 'author': 'UuuNyaa', | |
| 'description': "", | |
| 'blender': (2, 93, 0), | |
| 'version': (0, 0, 1), | |
| 'location': '3D View > Menu > Object', | |
| 'warning': '', | |
| 'category': 'Physics' | |
| } | |
| @dataclass | |
| class Fragment: | |
| vids: Set[int] # vertex ids | |
| center: Vector # center of gravity | |
| inradius: float # insphere radius | |
| def setup_mmd_physics(mesh_object: bpy.types.Object, depsgraph: bpy.types.Depsgraph): | |
| # create MMD Model | |
| mmd_model = mmd_tools.core.model.Model.create( | |
| 'New MMD Model Name (Japanese)', | |
| 'New MMD Model Name (English)', | |
| 0.008, # scale | |
| add_root_bone=False | |
| ) | |
| # attach meshes | |
| mmd_model.attachMeshes([mesh_object], True) | |
| mesh_bmesh = bmesh.new(use_operators=False) | |
| mesh_bmesh.from_object(mesh_object, depsgraph) | |
| try: | |
| fragments = collect_fragments(mesh_bmesh) | |
| finally: | |
| mesh_bmesh.free() | |
| assign_vertex_weights(mesh_object, fragments) | |
| create_bones(mmd_model, fragments) | |
| create_rigid_bodies(mmd_model, fragments) | |
| def collect_fragments(mesh_bmesh: bmesh.types.BMesh) -> List[Fragment]: | |
| fid2vids = collect_fragment_vids(mesh_bmesh.verts) | |
| fid2centers = calc_fragment_centers(mesh_bmesh, fid2vids) | |
| fid2inradiuses = calc_fragment_inradiuses(mesh_bmesh, fid2vids, fid2centers) | |
| return [ | |
| Fragment(vids, center, inradius) | |
| for vids, center, inradius in zip(fid2vids, fid2centers, fid2inradiuses) | |
| ] | |
| def collect_fragment_vids(bmesh_verts: bmesh.types.BMVertSeq) -> List[Set[int]]: | |
| bmesh_verts.ensure_lookup_table() | |
| verts_kdtree = mathutils.kdtree.KDTree(len(bmesh_verts)) | |
| vert: bmesh.types.BMVert | |
| for vert in bmesh_verts: | |
| verts_kdtree.insert(vert.co, vert.index) | |
| verts_kdtree.balance() | |
| def collect_linked_vids(vert: bmesh.types.BMVert) -> Set[int]: | |
| vids: Set[int] = set() | |
| vert_stack = [vert] | |
| while len(vert_stack) > 0: | |
| vert = vert_stack.pop() | |
| if vert.index in vids: | |
| continue | |
| vids.add(vert.index) | |
| vert_stack.extend([ | |
| v | |
| for e in vert.link_edges for v in [e.other_vert(vert)] | |
| if v.index not in vids | |
| ]) | |
| vert_stack.extend([ | |
| bmesh_verts[vid] | |
| for _, vid, _ in verts_kdtree.find_range(vert.co, 0.0) | |
| if vid not in vids | |
| ]) | |
| return vids | |
| fragment_vids: List[Set[int]] = [] | |
| processed_vids: Set[int] = set() | |
| vert: bmesh.types.BMVert | |
| for vert in bmesh_verts: | |
| if vert.index in processed_vids: | |
| continue | |
| vids = collect_linked_vids(vert) | |
| fragment_vids.append(vids) | |
| processed_vids.update(vids) | |
| return fragment_vids | |
| def calc_fragment_centers(mesh_bmesh: bmesh.types.BMesh, fid2vids: List[Set[int]]) -> List[Vector]: | |
| # vertex_id to fragment_id map | |
| vid2fids = {vid: fid for fid, vids in enumerate(fid2vids) for vid in vids} | |
| fid2tri_centers: List[Vector] = [Vector() for _ in range(len(fid2vids))] | |
| fid2areas: List[float] = [0.0] * len(fid2vids) | |
| tri_loops: Tuple[bmesh.types.BMLoop, bmesh.types.BMLoop, bmesh.types.BMLoop] | |
| for tri_loops in mesh_bmesh.calc_loop_triangles(): | |
| fid = vid2fids[tri_loops[0].vert.index] | |
| v0, v1, v2 = tri_loops[0].vert.co, tri_loops[1].vert.co, tri_loops[2].vert.co | |
| area_tri: float = mathutils.geometry.area_tri(v0, v1, v2) | |
| fid2tri_centers[fid] += area_tri / 3 * (v0+v1+v2) | |
| fid2areas[fid] += area_tri | |
| return [ | |
| tri_center / area | |
| for tri_center, area in zip(fid2tri_centers, fid2areas) | |
| ] | |
| def calc_fragment_inradiuses(mesh_bmesh: bmesh.types.BMesh, fid2vids: List[Set[int]], fid2centers: List[Vector]) -> List[float]: | |
| # vertex_id to fragment_id map | |
| vid2fids = {vid: fid for fid, vids in enumerate(fid2vids) for vid in vids} | |
| fid2inradiuses: List[float] = [sys.float_info.max] * len(fid2vids) | |
| tri_loops: Tuple[bmesh.types.BMLoop, bmesh.types.BMLoop, bmesh.types.BMLoop] | |
| for tri_loops in mesh_bmesh.calc_loop_triangles(): | |
| fid = vid2fids[tri_loops[0].vert.index] | |
| fragment_center = fid2centers[fid] | |
| closest_length_on_tri = ( | |
| fragment_center | |
| - mathutils.geometry.closest_point_on_tri( | |
| fragment_center, | |
| tri_loops[0].vert.co, | |
| tri_loops[1].vert.co, | |
| tri_loops[2].vert.co, | |
| ) | |
| ).length | |
| inradius = fid2inradiuses[fid] | |
| if inradius > closest_length_on_tri: | |
| fid2inradiuses[fid] = closest_length_on_tri | |
| return fid2inradiuses | |
| def assign_vertex_weights(mesh_object: bpy.types.Object, fragments: List[Fragment]): | |
| for fid, fragment in enumerate(fragments): | |
| fragment_vertex_group = mesh_object.vertex_groups.new(name=f'fragment.{fid:03d}') | |
| fragment_vertex_group.add(list(fragment.vids), 1.0, 'REPLACE') | |
| def create_bones(mmd_model: mmd_tools.core.model.Model, fragments: List[Fragment]): | |
| armature_object: bpy.types.Object = mmd_model.armature() | |
| armature: bpy.types.Armature = armature_object.data | |
| edit_bones = armature.edit_bones | |
| mmd_tools.utils.enterEditMode(armature_object) | |
| for fid, fragment in enumerate(fragments): | |
| edit_bone = edit_bones.new(f'fragment.{fid:03d}') | |
| edit_bone.head = fragment.center | |
| edit_bone.tail = fragment.center + Vector((0.0, 0.0, fragment.inradius)) | |
| bpy.ops.object.mode_set(mode='OBJECT') | |
| def create_rigid_bodies(mmd_model: mmd_tools.core.model.Model, fragments: List[Fragment]): | |
| # For speed, rigid body objects are generated in advance. | |
| rigid_body_objects: List[bpy.types.Object] = mmd_model.createRigidBodyPool(len(fragments)) | |
| for fid, fragment in enumerate(fragments): | |
| fratment_name = f'fragment.{fid:03d}' | |
| mmd_model.createRigidBody( | |
| name=fratment_name, | |
| name_e=fratment_name, | |
| location=fragment.center, | |
| rotation=Vector(), | |
| size=Vector((fragment.inradius, fragment.inradius, fragment.inradius)), | |
| shape_type=mmd_tools.core.rigid_body.SHAPE_BOX, | |
| dynamics_type=mmd_tools.core.rigid_body.MODE_DYNAMIC, | |
| collision_group_number=0, | |
| collision_group_mask=[False] * 16, | |
| mass=1.0, | |
| friction=0.5, | |
| bounce=0.5, | |
| linear_damping=0.04, | |
| angular_damping=0.1, | |
| bone=fratment_name, | |
| obj=rigid_body_objects[fid], | |
| ) | |
| class SetupTestOperator(bpy.types.Operator): | |
| bl_idname = 'mmd_rigidbody_tools.setup_test' | |
| bl_label = 'Setup Test Operator' | |
| bl_options = {'REGISTER', 'UNDO'} | |
| def execute(self, context: bpy.types.Context): | |
| mesh_object: bpy.types.Object = context.active_object | |
| if mesh_object.type != 'MESH': | |
| self.report(type={'ERROR'}, message='Selected object is not a MESH') | |
| return {'CANCELLED'} | |
| if mmd_tools.core.model.FnModel.find_root(mesh_object) is not None: | |
| self.report(type={'ERROR'}, message='Selected object is already MMD model') | |
| return {'CANCELLED'} | |
| start_time = time.perf_counter() | |
| setup_mmd_physics(mesh_object, context.evaluated_depsgraph_get()) | |
| end_time = time.perf_counter() | |
| self.report(type={'INFO'}, message=f'processing time: {end_time - start_time} secs') | |
| return {'FINISHED'} | |
| def setup_test_button(self, _context): | |
| self.layout.operator( | |
| SetupTestOperator.bl_idname, | |
| text="Setup Test", | |
| icon='PLUGIN' | |
| ) | |
| def register(): | |
| bpy.utils.register_class(SetupTestOperator) | |
| bpy.types.VIEW3D_MT_object.append(setup_test_button) | |
| def unregister(): | |
| bpy.types.VIEW3D_MT_object.remove(setup_test_button) | |
| bpy.utils.unregister_class(SetupTestOperator) | |
| if __name__ == '__main__': | |
| register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
fragment_rigid_body.mp4