def _set_subdivision_levels(prop, value): cloth_mesh_object: bpy.types.Object = prop.id_data cloth_modifier: bpy.types.ClothModifier = MeshEditor( prop.id_data).find_subsurface_modifier( name='mmd_uuunyaa_physics_cloth_subsurface') cloth_modifier.levels = value cloth_modifier.render_levels = value for obj in bpy.data.objects: if obj.type != 'MESH': continue for modifier in obj.modifiers: if modifier.type != 'SURFACE_DEFORM': continue if modifier.target != cloth_mesh_object: continue # bind/unbind -> unbind/bind # pylint: disable=redundant-keyword-arg bpy.ops.object.surfacedeform_bind({'object': obj}, modifier=modifier.name) bpy.ops.object.surfacedeform_bind({'object': obj}, modifier=modifier.name) return
def mmd_model_cloths_method(self): for obj in self.allObjects(self.clothGroupObject()): if obj.type != 'MESH': continue if MeshEditor(obj).find_cloth_modifier() is None: continue yield obj
def poll(cls, context: bpy.types.Context): if context.mode != 'OBJECT': return False active_object = context.active_object if active_object is None: return False if active_object.type != 'MESH': return False return MeshEditor(active_object).find_cloth_modifier() is not None
def execute(self, context: bpy.types.Context): try: for obj in context.selected_objects: if obj.type != 'MESH': continue MeshEditor(obj).remove_cloth_modifier() except MessageException as ex: self.report(type={'ERROR'}, message=str(ex)) return {'CANCELLED'} return {'FINISHED'}
def execute(self, context: bpy.types.Context): from_object = context.active_object from_settings = from_object.mmd_uuunyaa_tools_collision_settings from_modifier = MeshEditor(from_object).get_collision_modifier() for to_object in context.selected_objects: if to_object.type != 'MESH': continue if from_object == to_object: continue MeshEditor(to_object).get_collision_modifier(from_modifier.name) to_settings = to_object.mmd_uuunyaa_tools_collision_settings to_settings.presets = from_settings.presets to_settings.damping = from_settings.damping to_settings.thickness_outer = from_settings.thickness_outer to_settings.thickness_inner = from_settings.thickness_inner to_settings.cloth_friction = from_settings.cloth_friction return {'FINISHED'}
def execute(self, context: bpy.types.Context): from_object = context.active_object from_settings = from_object.mmd_uuunyaa_tools_cloth_settings from_modifier = MeshEditor(from_object).get_cloth_modifier() for to_object in context.selected_objects: if to_object.type != 'MESH': continue if from_object == to_object: continue MeshEditor(to_object).get_cloth_modifier(from_modifier.name) to_settings = to_object.mmd_uuunyaa_tools_cloth_settings to_settings.presets = from_settings.presets to_settings.mass = from_settings.mass to_settings.stiffness = from_settings.stiffness to_settings.damping = from_settings.damping to_settings.collision_quality = from_settings.collision_quality to_settings.distance_min = from_settings.distance_min to_settings.impulse_clamp = from_settings.impulse_clamp return {'FINISHED'}
def execute(self, context: bpy.types.Context): key_object = context.active_object key_settings = key_object.mmd_uuunyaa_tools_collision_settings obj: bpy.types.Object for obj in self.filter_only_in_mmd_model( key_object) if self.same_mmd_model else bpy.data.objects: if obj.type != 'MESH': continue if MeshEditor(obj).find_collision_modifier() is None: continue if self.same_physics_settings and not key_settings.physics_equals( obj.mmd_uuunyaa_tools_collision_settings): continue obj.select_set(True) return {'FINISHED'}
def execute(self, context: bpy.types.Context): key_object = context.active_object key_rigid_body_object = key_object.find_rigid_body_object() obj: bpy.types.Object for obj in self.filter_only_in_mmd_model( key_object) if self.only_in_mmd_model else bpy.data.objects: if obj.type != 'MESH': continue rigid_body_object = MeshEditor(obj).find_rigid_body_object() if rigid_body_object is None: continue if self.only_same_settings and rigid_body_object != key_rigid_body_object: continue obj.select_set(True) return {'FINISHED'}
def draw(self, context: bpy.types.Context): layout = self.layout mesh_object: bpy.types.Object = context.active_object cloth_settings = mesh_object.mmd_uuunyaa_tools_cloth_settings box = layout.box() col = box.column() col.prop(cloth_settings, 'presets') col.prop(cloth_settings, 'mass') col.prop(cloth_settings, 'stiffness') col.prop(cloth_settings, 'damping') col = box.column() col.label(text=_('Collision:')) col.prop(cloth_settings, 'collision_quality') col.prop(cloth_settings, 'distance_min', slider=True) col.prop(cloth_settings, 'impulse_clamp') col = box.column() col.label(text=_('Batch Operation:')) col.operator(CopyClothAdjusterSettings.bl_idname, text=_('Copy to Selected'), icon='DUPLICATE') col = layout.column(align=True) col.label(text=_('Cache:')) row = col.row(align=True) row.prop(cloth_settings, 'frame_start', text=_('Simulation Start')) row.prop(cloth_settings, 'frame_end', text=_('Simulation End')) if MeshEditor(mesh_object).find_subsurface_modifier( 'mmd_uuunyaa_physics_cloth_subsurface') is None: return col = layout.column(align=True) col.label(text=_('Subdivision:')) col.prop(cloth_settings, 'subdivision_levels', text=_('Subdivision Levels'))
def _set_damping(prop, value): cloth_settings: bpy.types.ClothSettings = MeshEditor( prop.id_data).find_cloth_settings() cloth_settings.tension_damping = value cloth_settings.compression_damping = value cloth_settings.shear_damping = value
class ClothAdjusterSettingsPropertyGroup(bpy.types.PropertyGroup): @staticmethod def _update_presets(prop, _): TUNERS[prop.presets](prop.id_data).execute() presets: bpy.props.EnumProperty(name=_('Presets'), items=TUNERS.to_enum_property_items(), update=_update_presets.__func__, default=None) mass: bpy.props.FloatProperty( name=_('Vertex Mass'), min=0, soft_max=10, step=10, unit='MASS', get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_settings(), 'mass', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_settings(), 'mass', v), ) @staticmethod def _set_stiffness(prop, value): cloth_settings: bpy.types.ClothSettings = MeshEditor( prop.id_data).find_cloth_settings() cloth_settings.tension_stiffness = value cloth_settings.compression_stiffness = value cloth_settings.shear_stiffness = value stiffness: bpy.props.FloatProperty( name=_('Stiffness'), min=0, soft_max=50, max=10000, precision=3, step=10, get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_settings(), 'tension_stiffness', 0 ), set=_set_stiffness.__func__, ) @staticmethod def _set_damping(prop, value): cloth_settings: bpy.types.ClothSettings = MeshEditor( prop.id_data).find_cloth_settings() cloth_settings.tension_damping = value cloth_settings.compression_damping = value cloth_settings.shear_damping = value damping: bpy.props.FloatProperty( name=_('Damping'), min=0, max=50, precision=3, step=10, get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_settings(), 'tension_damping', 0), set=_set_damping.__func__, ) collision_quality: bpy.props.IntProperty( name=_('Collision Quality'), min=1, max=20, get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'collision_quality', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'collision_quality', v), ) distance_min: bpy.props.FloatProperty( name=_('Minimum Distance'), min=0.001, max=1.000, step=10, unit='LENGTH', get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'distance_min', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'distance_min', v), ) impulse_clamp: bpy.props.FloatProperty( name=_('Impulse Clamping'), min=0, max=100, precision=3, step=10, get=lambda p: getattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'impulse_clamp', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_collision_settings(), 'impulse_clamp', v), ) frame_start: bpy.props.IntProperty( name=_('Simulation Start'), min=0, max=1048574, get=lambda p: getattr( getattr( MeshEditor(p.id_data).find_cloth_modifier(), 'point_cache', None), 'frame_start', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_modifier().point_cache, 'frame_start', v), ) frame_end: bpy.props.IntProperty( name=_('Simulation End'), min=1, max=1048574, get=lambda p: getattr( getattr( MeshEditor(p.id_data).find_cloth_modifier(), 'point_cache', None), 'frame_end', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_cloth_modifier().point_cache, 'frame_end', v), ) @staticmethod def _set_subdivision_levels(prop, value): cloth_mesh_object: bpy.types.Object = prop.id_data cloth_modifier: bpy.types.ClothModifier = MeshEditor( prop.id_data).find_subsurface_modifier( name='mmd_uuunyaa_physics_cloth_subsurface') cloth_modifier.levels = value cloth_modifier.render_levels = value for obj in bpy.data.objects: if obj.type != 'MESH': continue for modifier in obj.modifiers: if modifier.type != 'SURFACE_DEFORM': continue if modifier.target != cloth_mesh_object: continue # bind/unbind -> unbind/bind # pylint: disable=redundant-keyword-arg bpy.ops.object.surfacedeform_bind({'object': obj}, modifier=modifier.name) bpy.ops.object.surfacedeform_bind({'object': obj}, modifier=modifier.name) return subdivision_levels: bpy.props.IntProperty( name=_('Subdivision Levels'), min=0, soft_max=2, max=6, get=lambda p: getattr( MeshEditor(p.id_data).find_subsurface_modifier( name='mmd_uuunyaa_physics_cloth_subsurface'), 'levels', 0), set=_set_subdivision_levels.__func__, ) def physics_equals(self, obj): return (isinstance(obj, ClothAdjusterSettingsPropertyGroup) and self.presets == obj.presets and self.mass == obj.mass and self.stiffness == obj.stiffness and self.damping == obj.damping and self.collision_quality == obj.collision_quality and self.distance_min == obj.distance_min and self.impulse_clamp == obj.impulse_clamp) def cache_equals(self, obj): return (isinstance(obj, ClothAdjusterSettingsPropertyGroup) and self.frame_start == obj.frame_start and self.frame_end == obj.frame_end) @staticmethod def register(): # pylint: disable=assignment-from-no-return bpy.types.Object.mmd_uuunyaa_tools_cloth_settings = bpy.props.PointerProperty( type=ClothAdjusterSettingsPropertyGroup) @staticmethod def unregister(): del bpy.types.Object.mmd_uuunyaa_tools_cloth_settings
def poll(cls, context: bpy.types.Context): return MeshEditor( context.active_object).find_cloth_modifier() is not None
class CollisionAdjusterSettingsPropertyGroup(bpy.types.PropertyGroup): @staticmethod def _update_presets(prop, _): TUNERS[prop.presets](prop.id_data).execute() presets: bpy.props.EnumProperty(name=_('Presets'), items=TUNERS.to_enum_property_items(), update=_update_presets.__func__, default=None) damping: bpy.props.FloatProperty( name=_('Damping'), min=0.000, max=1.000, precision=3, get=lambda p: getattr( MeshEditor(p.id_data).find_collision_settings(), 'damping', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_collision_settings(), 'damping', v), ) thickness_outer: bpy.props.FloatProperty( name=_('Thickness Outer'), min=0.001, max=1.000, precision=3, get=lambda p: getattr( MeshEditor(p.id_data).find_collision_settings(), 'thickness_outer', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_collision_settings(), 'thickness_outer', v), ) thickness_inner: bpy.props.FloatProperty( name=_('Thickness Inner'), min=0.001, max=1.000, precision=3, get=lambda p: getattr( MeshEditor(p.id_data).find_collision_settings(), 'thickness_inner', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_collision_settings(), 'thickness_inner', v), ) cloth_friction: bpy.props.FloatProperty( name=_('Cloth Friction'), min=0.000, max=80.000, step=10, precision=3, get=lambda p: getattr( MeshEditor(p.id_data).find_collision_settings(), 'cloth_friction', 0), set=lambda p, v: setattr( MeshEditor(p.id_data).find_collision_settings(), 'cloth_friction', v), ) def physics_equals(self, obj): return (isinstance(obj, CollisionAdjusterSettingsPropertyGroup) and self.presets == obj.presets and self.damping == obj.damping and self.thickness_outer == obj.thickness_outer and self.thickness_inner == obj.thickness_inner and self.cloth_friction == obj.cloth_friction) @staticmethod def register(): # pylint: disable=assignment-from-no-return bpy.types.Object.mmd_uuunyaa_tools_collision_settings = bpy.props.PointerProperty( type=CollisionAdjusterSettingsPropertyGroup) @staticmethod def unregister(): del bpy.types.Object.mmd_uuunyaa_tools_collision_settings
def assign_deform_weights(pyramid_armature_object: bpy.types.Object, deform_mesh_object: bpy.types.Object, target_bone_name: str, boundary_expansion_hop_count: int): mesh_editor = MeshEditor(deform_mesh_object) deform_bmesh: bmesh.types.BMesh = bmesh.new() start_time = datetime.datetime.now() print(f'assign deform weights:begin: {target_bone_name}') # pylint: disable=no-member depsgraph: bpy.types.Depsgraph = bpy.context.evaluated_depsgraph_get() deform_bmesh.from_object(mesh_editor.mesh_object, depsgraph) deform_bmesh.transform(deform_mesh_object.matrix_world) vid2weight = expand_boundary( to_vid2weight(deform_bmesh, mesh_editor.get_vertex_group(target_bone_name).index), deform_bmesh, boundary_expansion_hop_count ) deform_bmesh_verts = deform_bmesh.verts print(f'assign deform weights:build_adjacencies: vid_count={len(vid2weight)}, {datetime.datetime.now() - start_time}') adjacencies, vid2uid = build_adjacencies(deform_bmesh_verts, vid2weight) # unified vertex id to numpy id uid2nid: Dict[int, int] = {uid: nid for nid, uid in enumerate(set(vid2uid.values()))} uid2vids: Dict[int, List[int]] = {uid: [vid for vid in vid2uid if vid2uid[vid] == uid] for uid in vid2uid.values()} nid2uid: Dict[int, int] = {v: k for k, v in uid2nid.items()} sink_nids: Set[int] = set() for index, weight in vid2weight.items(): if weight > 0: continue sink_nids.add(uid2nid[vid2uid[index]]) nid_count = len(uid2nid) adjacency_matrix = np.zeros((nid_count, nid_count)) for (from_uid, to_uid), span in adjacencies.items(): magnitude = math.exp(-span) adjacency_matrix[uid2nid[from_uid], uid2nid[to_uid]] = magnitude adjacency_matrix[uid2nid[to_uid], uid2nid[from_uid]] = magnitude deform_bmesh_verts.ensure_lookup_table() vertex_kdtree = mathutils.kdtree.KDTree(nid_count) for uid in uid2nid: if vid2weight[uid] == 0: continue vertex_kdtree.insert(deform_bmesh_verts[uid].co, uid) vertex_kdtree.balance() def collect_nid2weight(position: Vector, scale: float): max_weight: Union[float, None] = None nid2weight: Dict[int, float] = {} for _co, near_uid, near_span in vertex_kdtree.find_n(position, 16): weight = math.exp(-near_span/scale) if max_weight is None: max_weight = weight weight = 1 else: weight = weight / max_weight nid2weight[uid2nid[near_uid]] = weight if weight < 0.4: break return nid2weight bone_names = PyramidBoneNames(target_bone_name) pyramid_armature: bpy.types.Armature = pyramid_armature_object.data pyramid_origin = pyramid_armature_object.location bone_length = pyramid_armature.bones[bone_names.apex].length bone_name2nid2weight: Dict[str, Dict[int, float]] = { bone_names.apex: collect_nid2weight(pyramid_armature.bones[bone_names.apex].tail_local + pyramid_origin, bone_length), bone_names.base_a: collect_nid2weight(pyramid_armature.bones[bone_names.base_a].head_local + pyramid_origin, bone_length), bone_names.base_b: collect_nid2weight(pyramid_armature.bones[bone_names.base_b].head_local + pyramid_origin, bone_length), bone_names.base_c: collect_nid2weight(pyramid_armature.bones[bone_names.base_c].head_local + pyramid_origin, bone_length), bone_names.base_d: collect_nid2weight(pyramid_armature.bones[bone_names.base_d].head_local + pyramid_origin, bone_length), } print(f'assign deform weights:auto_weight: nid_count={nid_count}, {datetime.datetime.now() - start_time}') laplacian_matrix = np.diag(np.sum(adjacency_matrix, axis=1)) laplacian_matrix -= adjacency_matrix eigen_values, eigen_vector = np.linalg.eigh(laplacian_matrix) diffusion = np.exp(-eigen_values*2) for bone_name in bone_name2nid2weight: nid2weight = { nid: (weight * 10 if b == bone_name else -weight) for b, n2w in bone_name2nid2weight.items() for nid, weight in n2w.items() } print(f'assign deform weights:auto_weight: {bone_name}, {datetime.datetime.now() - start_time}') weights = np.zeros(nid_count) for _iteration in range(min(60, nid_count//2)): for nid, weight in nid2weight.items(): if weight > 0: weights[nid] += weight else: weights[nid] = 0 weights = eigen_vector.T @ weights weights *= diffusion weights = eigen_vector @ weights for nid in sink_nids: weights[nid] = 0 # normalize weights[weights < 0] = 0 weights = weights / np.max(weights) mesh_editor.edit_vertex_group(bone_name, [ (uid2vids[nid2uid[nid]], weights[nid].item() * vid2weight[nid2uid[nid]]) for nid in range(nid_count) ]) deform_bmesh.free() print(f'assign deform weights:finish: {datetime.datetime.now() - start_time}')
def convert(cls, mmd_root_object: bpy.types.Object, rigid_body_objects: List[bpy.types.Object], mesh_objects: List[bpy.types.Object], subdivision_level: int, ribbon_stiffness: float, physics_mode: PhysicsMode, extend_ribbon_area: bool): # pylint: disable=too-many-arguments # pylint: disable=too-many-locals, too-many-statements mmd_model = import_mmd_tools().core.model.Model(mmd_root_object) mmd_mesh_object = mesh_objects[0] mmd_armature_object = mmd_model.armature() rigid_bodys_count = len(rigid_body_objects) rigid_body_index_dict = { rigid_body_objects[i]: i for i in range(rigid_bodys_count) } pose_bones: List[bpy.types.PoseBone] = [] for rigid_body_object in rigid_body_objects: pose_bone = mmd_armature_object.pose.bones.get( rigid_body_object.mmd_rigid.bone) if pose_bone is None: raise MessageException( iface_( 'No bones related with {rigid_body_name}, Please relate a bone to the Rigid Body.' ).format(rigid_body_name=rigid_body_object.name)) pose_bones.append(pose_bone) def remove_objects(objects: Iterable[bpy.types.Object]): for obj in objects: bpy.data.objects.remove(obj) joint_objects, joint_edge_indices, side_joint_objects = cls.collect_joints( mmd_model, rigid_body_index_dict) remove_objects(joint_objects) cloth_mesh = bpy.data.meshes.new('physics_cloth') cloth_mesh.from_pydata([r.location for r in rigid_body_objects], joint_edge_indices, []) cloth_mesh.validate() cloth_mesh_object = bpy.data.objects.new('physics_cloth', cloth_mesh) cloth_mesh_object.parent = mmd_model.clothGroupObject() cloth_mesh_object.hide_render = True cloth_mesh_object.display_type = 'WIRE' # 标记出特殊边和点 # These are special edge and vertex cloth_bm: bmesh.types.BMesh = bmesh.new() cloth_bm.from_mesh(cloth_mesh) cls.clean_mesh(cloth_bm, joint_edge_indices) # 标出头部,尾部,飘带顶点 # try mark head,tail,ribbon vertex cloth_bm.verts.ensure_lookup_table() cloth_bm.edges.ensure_lookup_table() vertices = cls.collect_vertices(cloth_bm, pose_bones, physics_mode, extend_ribbon_area) edges = cls.collect_edges(cloth_bm, vertices) new_up_verts = cls.extend_up_edges(cloth_bm, pose_bones, vertices, edges, physics_mode) new_down_verts = cls.extend_down_edges(cloth_bm, pose_bones, vertices, edges) cls.fill_faces(cloth_bm, edges.up_edges, new_up_verts) cls.fill_faces(cloth_bm, edges.down_edges, new_down_verts) cloth_bm.verts.index_update() cloth_bm.faces.ensure_lookup_table() new_side_verts = cls.extend_side_vertices(cloth_bm, vertices, edges) cls.fill_faces(cloth_bm, edges.side_edges, new_side_verts) new_ribbon_verts = cls.extend_ribbon_vertices(cloth_bm) cls.fill_faces(cloth_bm, [e for e in cloth_bm.edges if e.is_wire], new_ribbon_verts) cls.normals_make_consistent(cloth_bm) cloth_bm.verts.ensure_lookup_table() cloth_bm.edges.ensure_lookup_table() cloth_bm.to_mesh(cloth_mesh) pin_vertex_group = cls.new_pin_vertex_group(cloth_mesh_object, side_joint_objects, new_up_verts, new_side_verts, rigid_body_index_dict) remove_objects(side_joint_objects) deform_vertex_group: bpy.types.VertexGroup = mmd_mesh_object.vertex_groups.new( name='physics_cloth_deform') mesh_editor = MeshEditor(cloth_mesh_object) mesh_editor.link_to_active_collection() mesh_editor.add_subsurface_modifier('physics_cloth_subsurface', subdivision_level, subdivision_level) mesh_editor.add_armature_modifier('physics_cloth_armature', mmd_armature_object, vertex_group=pin_vertex_group.name) mesh_editor.edit_cloth_modifier( 'physics_cloth', vertex_group_mass=pin_vertex_group.name) corrective_smooth_modifier = mesh_editor.add_corrective_smooth_modifier( 'physics_cloth_smooth', smooth_type='LENGTH_WEIGHTED', rest_source='BIND') bpy.ops.object.correctivesmooth_bind( modifier=corrective_smooth_modifier.name) if subdivision_level == 0: corrective_smooth_modifier.show_viewport = False deform_vertex_group_index = deform_vertex_group.index vertices_ribbon_verts = vertices.ribbon_verts cls.bind_mmd_mesh(mmd_mesh_object, cloth_mesh_object, cloth_bm, pose_bones, deform_vertex_group_index, vertices_ribbon_verts, physics_mode) cls.set_pin_vertex_weight(pin_vertex_group, vertices_ribbon_verts, ribbon_stiffness, physics_mode) remove_objects(rigid_body_objects) if not vertices.all_ribbon and physics_mode in { PhysicsMode.AUTO, PhysicsMode.SURFACE_DEFORM }: bpy.context.view_layer.objects.active = mmd_mesh_object bpy.ops.object.surfacedeform_bind(modifier=MeshEditor( mmd_mesh_object).add_surface_deform_modifier( 'physics_cloth_deform', target=cloth_mesh_object, vertex_group=deform_vertex_group.name).name) cloth_bm.free()
def poll(cls, context: bpy.types.Context): return MeshEditor( context.active_object).find_rigid_body_object() is not None