Skip to content

Instantly share code, notes, and snippets.

@UuuNyaa
Last active January 3, 2023 22:23
Show Gist options
  • Select an option

  • Save UuuNyaa/925be8e892f3793386ff3db4e9e5cf7e to your computer and use it in GitHub Desktop.

Select an option

Save UuuNyaa/925be8e892f3793386ff3db4e9e5cf7e to your computer and use it in GitHub Desktop.
# 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()
@UuuNyaa
Copy link
Author

UuuNyaa commented Apr 7, 2022

fragment_rigid_body.mp4

@UuuNyaa
Copy link
Author

UuuNyaa commented Apr 7, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment