ハイポリメッシュのシェイプキーをローポリメッシュに複製してみました。アドオンをインストールして使用します。
概要
ローポリメッシュの頂点を走査し、指定の範囲内にあるハイポリメッシュの頂点リストを記憶します。ハイポリメッシュをシェイプキーで変形した後の頂点の平均値を新しい頂点位置として、ローポリメッシュにシェイプキーを作成します。
スクリプト
__init__.py
- # -*- coding: utf-8 -*-
-
- bl_info = {
- "name": "ShapeKey Transmuter",
- "description": "Transfers shape keys between meshes based on vertex proximity.",
- "author": "Your Name",
- "version": (1, 0),
- "blender": (2, 93, 0),
- "location": "View3D > Sidebar > Tools Tab",
- "category": "Object"
- }
-
-
- import bpy
- from .main_logic import perform_action
-
- def get_vertex_groups(self, context):
- items = [('NONE', "None", "")]
- if self.target_object:
- items.extend([(vg.name, vg.name, "") for vg in self.target_object.vertex_groups])
- return items
-
- def get_shape_keys(self, context):
- items = []
- if self.source_object and self.source_object.data.shape_keys:
- for key in self.source_object.data.shape_keys.key_blocks:
- items.append((key.name, key.name, ""))
- return items
-
- class ShapeKeyToolProperties(bpy.types.PropertyGroup):
- radius: bpy.props.FloatProperty(name="Radius")
- source_object : bpy.props.PointerProperty(
- name="Source Object",
- type=bpy.types.Object,
- poll=lambda self, object: object.type == 'MESH'
- )
- target_object : bpy.props.PointerProperty(
- name="Target Object",
- type=bpy.types.Object,
- poll=lambda self, object: object.type == 'MESH'
- )
- vertex_group: bpy.props.EnumProperty(name="Vertex Group", items=get_vertex_groups)
- shape_key: bpy.props.EnumProperty(name="Shape Key", items=get_shape_keys)
-
-
- class SKT_ShapeKeyOperator(bpy.types.Operator):
- bl_idname = "object.skt_shape_key_operator"
- bl_label = "Apply Shape Key"
-
- @classmethod
- def poll(cls, context):
- props = context.scene.shape_key_tool_props
- # オブジェクト、シェイプキー、頂点グループが適切に選択されていることを確認
- return props.source_object and props.target_object and props.shape_key and props.vertex_group
-
- def execute(self, context):
- perform_action(context)
- return {'FINISHED'}
-
-
- class SKT_SimplePanel(bpy.types.Panel):
- bl_label = "ShapeKey Transmuter Panel"
- bl_idname = "OBJECT_PT_custom"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'Tools'
-
- def draw(self, context):
- layout = self.layout
- tool_props = context.scene.shape_key_tool_props
- layout.prop(tool_props, "source_object")
- if tool_props.source_object:
- layout.prop(tool_props, "shape_key")
- layout.prop(tool_props, "target_object")
- if tool_props.target_object:
- layout.prop(tool_props, "vertex_group")
- layout.prop(tool_props, "radius")
- layout.operator("object.skt_shape_key_operator")
-
- # カスタムプロパティをシーンに追加
- def register():
- bpy.utils.register_class(ShapeKeyToolProperties)
- bpy.types.Scene.shape_key_tool_props = bpy.props.PointerProperty(type=ShapeKeyToolProperties)
- bpy.utils.register_class(SKT_SimplePanel)
- bpy.utils.register_class(SKT_ShapeKeyOperator)
- # その他の登録処理
-
- def unregister():
- del bpy.types.Scene.shape_key_tool_props
- bpy.utils.unregister_class(ShapeKeyToolProperties)
- bpy.utils.unregister_class(SKT_SimplePanel)
- bpy.utils.unregister_class(SKT_ShapeKeyOperator)
- # その他の解除処理
-
- if __name__ == "__main__":
- register()
main_logic.py
- import bpy
- import bmesh
- from mathutils import Vector
- import numpy as np
-
- def perform_action(context):
- # シェイプキー名をプロパティから取得
- shape_key_name = context.scene.shape_key_tool_props.shape_key
-
- if not shape_key_name:
- # 適切なシェイプキーが設定されていない場合のエラー処理
- raise Exception("シェイプキーが設定されていません。")
-
- # ターゲットメッシュ(変更対象)とソースメッシュ(変更元)を決定
- target_mesh = context.scene.shape_key_tool_props.target_object
- source_mesh = context.scene.shape_key_tool_props.source_object
-
- if not target_mesh or target_mesh.type != 'MESH':
- raise Exception("ターゲットオブジェクトが適切に設定されていないか、メッシュタイプではありません。")
-
- if not source_mesh or source_mesh.type != 'MESH':
- raise Exception("ソースオブジェクトが適切に設定されていないか、メッシュタイプではありません。")
-
-
- # メッシュデータを取得
- target_bmesh = bmesh.new()
- target_bmesh.from_mesh(target_mesh.data)
- source_bmesh = bmesh.new()
- source_bmesh.from_mesh(source_mesh.data)
-
- # 頂点の影響範囲を定義
- radius = context.scene.shape_key_tool_props.radius
-
- # 頂点グループを取得
- vertex_group_name = context.scene.shape_key_tool_props.vertex_group
-
- # 頂点グループが設定されていることを確認
- if not vertex_group_name:
- raise Exception("頂点グループが設定されていません。")
-
- # ユーザーが「None」を選択した場合は全頂点を対象とする
- if vertex_group_name != 'NONE':
- vertex_group = target_mesh.vertex_groups.get(vertex_group_name)
- if vertex_group:
- vgroup_indices = {v.index for v in target_mesh.data.vertices if vertex_group.index in [g.group for g in v.groups]}
- else:
- vgroup_indices = set(range(len(target_mesh.data.vertices)))
-
-
- # 全てのシェイプキーを0に設定
- for key_block in source_mesh.data.shape_keys.key_blocks:
- key_block.value = 0.0
-
- # 頂点グループを考慮して半径内の対応する頂点を探す
- vertex_groups = [[] for _ in range(len(target_bmesh.verts))]
- for i, target_vert in enumerate(target_bmesh.verts):
- if target_vert.index in vgroup_indices:
- closest_vert = None
- min_dist = float('inf')
- for source_vert in source_bmesh.verts:
- dist = (target_vert.co - source_vert.co).length
- if dist <= radius:
- vertex_groups[i].append(source_vert.index)
- elif dist < min_dist:
- min_dist = dist
- closest_vert = source_vert
- if not vertex_groups[i]:
- vertex_groups[i].append(closest_vert.index)
-
- # 指定されたシェイプキーのみを1に設定
- source_mesh.data.shape_keys.key_blocks[shape_key_name].value = 1.0
- source_mesh.data.update()
-
- # 新しい頂点位置を計算
- new_vertex_positions = [None] * len(target_bmesh.verts)
- for target_vert, group in zip(target_bmesh.verts, vertex_groups):
- if target_vert.index in vgroup_indices and group:
- total_position = Vector((0.0, 0.0, 0.0))
- for vert_idx in group:
- shaped_vert = source_mesh.data.shape_keys.key_blocks[shape_key_name].data[vert_idx].co
- total_position += shaped_vert
- average_position = total_position / len(group)
- new_position = average_position
- new_vertex_positions[target_vert.index] = new_position
- elif target_vert.index in vgroup_indices:
- new_vertex_positions[target_vert.index] = target_vert.co
-
- # ターゲットメッシュのシェイプキーデータを確認し、必要に応じてBasisキーを作成
- if not target_mesh.data.shape_keys:
- target_mesh.shape_key_add(name='Basis')
-
- # 新しいシェイプキーを追加
- new_shape_key = target_mesh.shape_key_add(name=shape_key_name)
-
-
- # 新規シェイプキーに頂点位置を適用
- for vert_idx, new_pos in enumerate(new_vertex_positions):
- if new_pos:
- new_shape_key.data[vert_idx].co = new_pos
-
- # ソースメッシュのシェイプキーの値を0に設定
- source_mesh.data.shape_keys.key_blocks[shape_key_name].value = 0.0
- source_mesh.data.update()
-
- # 解放処理
- target_bmesh.free()
- source_bmesh.free()
-
- # すべてのオブジェクトの選択を解除
- bpy.ops.object.select_all(action='DESELECT')
-
- # ターゲットメッシュだけを選択
- target_mesh.select_set(True)
- context.view_layer.objects.active = target_mesh
-
-
設定
上記のpyファイルのみを含んだフォルダを作ります。
フォルダを圧縮してzipファイルにします。
Blenderで、Edit > Preferences... を開きます。
Add-onsで「Install...」ボタンから、zipファイルをインストールします。
チェックボックスを入れてアドオンを有効化します。
横の▷をクリックするとアドオンの情報が開きます。「Remove」ボタンでアドオンを削除できます。
3Dビューポートのサイドバーの「Tools」タブにパネルが表示されます。
使い方
BlenderでMonkeyを2つ追加しました。
片方はDecimateモディファイアが適用されていて、ポリゴン数が減っています。
ハイポリメッシュにシェイプキーを追加して、表情を作ります。
パネルの「Source Object」にハイポリメッシュを設定し、「Target Object」にローポリメッシュを設定します。
「Shape Key」で、追加したシェイプキーを選択します。
「Radius」を設定して、Applyボタンを押します。
ローポリメッシュにシェイプキーが追加されます。
シェイプキーのValueを1にすると、ハイポリメッシュに設定した表情になりました。
頂点グループを適用
ローポリメッシュに頂点グループを追加します。
編集モードで一部の頂点のみを選択します。
頂点グループにウェイト1で割り当てます。
パネルの「Vertex Group」でこの頂点グループを選択します。
Applyボタンを押すと、再度シェイプキーが追加されます。頂点グループに適用した部分のみが変形します。
これでハイポリメッシュからローポリメッシュへシェイプキーを複製できました。
プロパティ
パネルで設定するオブジェクト参照や各値はプロパティに設定されます。オブジェクトのプロパティはメッシュタイプのオブジェクトだけを受け入れます。
- class ShapeKeyToolProperties(bpy.types.PropertyGroup):
- radius: bpy.props.FloatProperty(name="Radius")
- source_object : bpy.props.PointerProperty(
- name="Source Object",
- type=bpy.types.Object,
- poll=lambda self, object: object.type == 'MESH'
- )
- target_object : bpy.props.PointerProperty(
- name="Target Object",
- type=bpy.types.Object,
- poll=lambda self, object: object.type == 'MESH'
- )
- vertex_group: bpy.props.EnumProperty(name="Vertex Group", items=get_vertex_groups)
- shape_key: bpy.props.EnumProperty(name="Shape Key", items=get_shape_keys)
-
- # ...
-
- def register():
- bpy.utils.register_class(ShapeKeyToolProperties)
- bpy.types.Scene.shape_key_tool_props = bpy.props.PointerProperty(type=ShapeKeyToolProperties)
現在のシーンのコンテキストを使用して、プロパティグループ内のプロパティの値を取得できます。
- target_mesh = context.scene.shape_key_tool_props.target_object
- source_mesh = context.scene.shape_key_tool_props.source_object
スクリプト
シェイプキーやオブジェクト、半径などのプロパティ値を取得します。オブジェクトからメッシュデータを作成します。
- import bpy
- import bmesh
- from mathutils import Vector
- import numpy as np
-
- def perform_action(context):
- # シェイプキー名をプロパティから取得
- shape_key_name = context.scene.shape_key_tool_props.shape_key
-
- if not shape_key_name:
- # 適切なシェイプキーが設定されていない場合のエラー処理
- raise Exception("シェイプキーが設定されていません。")
-
- # ターゲットメッシュ(変更対象)とソースメッシュ(変更元)を決定
- target_mesh = context.scene.shape_key_tool_props.target_object
- source_mesh = context.scene.shape_key_tool_props.source_object
-
- if not target_mesh or target_mesh.type != 'MESH':
- raise Exception("ターゲットオブジェクトが適切に設定されていないか、メッシュタイプではありません。")
-
- if not source_mesh or source_mesh.type != 'MESH':
- raise Exception("ソースオブジェクトが適切に設定されていないか、メッシュタイプではありません。")
-
-
- # メッシュデータを取得
- target_bmesh = bmesh.new()
- target_bmesh.from_mesh(target_mesh.data)
- source_bmesh = bmesh.new()
- source_bmesh.from_mesh(source_mesh.data)
-
- # 頂点の影響範囲を定義
- radius = context.scene.shape_key_tool_props.radius
頂点グループを取得します。パネルで「None」が選択されていると頂点グループを使用しません。
- # 頂点グループを取得
- vertex_group_name = context.scene.shape_key_tool_props.vertex_group
-
- # 頂点グループが設定されていることを確認
- if not vertex_group_name:
- raise Exception("頂点グループが設定されていません。")
-
- # ユーザーが「None」を選択した場合は全頂点を対象とする
- if vertex_group_name != 'NONE':
- vertex_group = target_mesh.vertex_groups.get(vertex_group_name)
- if vertex_group:
- vgroup_indices = {v.index for v in target_mesh.data.vertices if vertex_group.index in [g.group for g in v.groups]}
- else:
- vgroup_indices = set(range(len(target_mesh.data.vertices)))
ハイポリメッシュの全てのシェイプキーの値を0にします。
- # 全てのシェイプキーを0に設定
- for key_block in source_mesh.data.shape_keys.key_blocks:
- key_block.value = 0.0
ローポリメッシュの頂点ごとに頂点リストを保持します。ハイポリメッシュの頂点を走査し、距離が半径以下ならリストに追加します。範囲内になければもっとも近くの頂点を追加しています。シェイプキーでの変形を追跡できるようにインデックスを記憶しています。
- # 頂点グループを考慮して半径内の対応する頂点を探す
- vertex_groups = [[] for _ in range(len(target_bmesh.verts))]
- for i, target_vert in enumerate(target_bmesh.verts):
- if target_vert.index in vgroup_indices:
- closest_vert = None
- min_dist = float('inf')
- for source_vert in source_bmesh.verts:
- dist = (target_vert.co - source_vert.co).length
- if dist <= radius:
- vertex_groups[i].append(source_vert.index)
- elif dist < min_dist:
- min_dist = dist
- closest_vert = source_vert
- if not vertex_groups[i]:
- vertex_groups[i].append(closest_vert.index)
ハイポリメッシュのシェイプキーの値を1にします。シェイプキーはパネルで設定されています。
- # 指定されたシェイプキーのみを1に設定
- source_mesh.data.shape_keys.key_blocks[shape_key_name].value = 1.0
- source_mesh.data.update()
ローポリメッシュの頂点を一つずつ見ていって、リスト内の現在の頂点位置の平均値を新しい頂点位置にします。リスト内の頂点の位置はシェイプキーの値によって移動されています。
- # 新しい頂点位置を計算
- new_vertex_positions = [None] * len(target_bmesh.verts)
- for target_vert, group in zip(target_bmesh.verts, vertex_groups):
- if target_vert.index in vgroup_indices and group:
- total_position = Vector((0.0, 0.0, 0.0))
- for vert_idx in group:
- shaped_vert = source_mesh.data.shape_keys.key_blocks[shape_key_name].data[vert_idx].co
- total_position += shaped_vert
- average_position = total_position / len(group)
- new_position = average_position
- new_vertex_positions[target_vert.index] = new_position
- elif target_vert.index in vgroup_indices:
- new_vertex_positions[target_vert.index] = target_vert.co
ローポリメッシュに、指定のシェイプキーと同じ名前のシェイプキーを追加します。Basisがなければ作成します。
- # ターゲットメッシュのシェイプキーデータを確認し、必要に応じてBasisキーを作成
- if not target_mesh.data.shape_keys:
- target_mesh.shape_key_add(name='Basis')
-
- # 新しいシェイプキーを追加
- new_shape_key = target_mesh.shape_key_add(name=shape_key_name)
作成した頂点をシェイプキーに適用します。
- # 新規シェイプキーに頂点位置を適用
- for vert_idx, new_pos in enumerate(new_vertex_positions):
- if new_pos:
- new_shape_key.data[vert_idx].co = new_pos
最後にシェイプキーの値を0に戻したり、メモリの解放、選択解除などを行っています。
- # ソースメッシュのシェイプキーの値を0に設定
- source_mesh.data.shape_keys.key_blocks[shape_key_name].value = 0.0
- source_mesh.data.update()
-
- # 解放処理
- target_bmesh.free()
- source_bmesh.free()
-
- # すべてのオブジェクトの選択を解除
- bpy.ops.object.select_all(action='DESELECT')
-
- # ターゲットメッシュだけを選択
- target_mesh.select_set(True)
- context.view_layer.objects.active = target_mesh