鷲獅子通信

about Blender and modeling

ワークフロー②_スキニング環境

最近作成したBlenderスキニング環境を紹介します。

一言で説明するとLazyWeightToolアドオンを使います。以上。
で終わるくらいにLazyWeightToolを中心としたフローです。

LazyWeightTool

bookyakuno.com

忘却野さんが販売されているウェイトペイント用のアドオンです。¥1,800。

選択頂点に直接指定した値を設定できるのが便利です。他Operatorや選択のサポートも厚く超救われてます。
スキニングで大事なのは知識よりも優秀なツールを持っているかどうかだと思います。
このアドオンは多くのユーザーを救うと思います。

 

メインフロー

現在はマスクモードとWeight Gradientを中心に組み立てています。

  1. Armature→Meshの順で選択してWeightPaintModeに入る
    (Armatureを選択するのはPoseModeも併用するため)

  2. 頂点マスクモードで頂点を選択する

  3. Weight Gradientを使ってグラデーションを付ける

  4. Normalizeで正規化する

  5. Level(Gain:1.05/0.95)でグラデーションの強さを調整する
     

Workspace

f:id:igogo:20200606115611p:plain 普通の構成です。

  • LazyWeightToolを拡げるために3D Viewportを広く取ります。

  • いくつかポーズを登録しておくとウェイトの確認に便利なためTimelineも。

  • あとは選択オブジェクトを確認するためのOutlinerと、アクティブな頂点グループを確認するためのProperties Shelfです。
     

LazyWeightToolへの機能追加

f:id:igogo:20200606115627p:plain

こちらは少しユニークです。
WeightPaintModeで必要な操作があるため、アドオンとしてまとめて追加しました。
機能は以下の3つです。

Reset Pose

すべてのPoseBoneの変形をリセットします。Weight Paint Modeだと1つずつ選択してAlt+RGSしないといけないので若干面倒でした。

Toggle View

f:id:igogo:20200606115619p:plain Weight Paint Mode時にViewport Shadingをウェイトが塗りやすい設定に変更します。手動で毎回切り替えるのが面倒だったので。

Curve Editor

Draw ToolのCurve Editorです。このカーブはDraw Tool時にしか表示されないので、マスクモード中(Select Tool時)でも操作できるように追加しました。頂点選択→カーブ設定→Weight Gradientを切り替えなしにできて楽です。

コード

だいぶ自分特化なツールですが、必要な方は自由に改造して使ってください。

bl_info = {
    "name": "ui main plus",
    "author": "",
    "version": (0, 0, 1),
    "blender": (2, 82, 0),
    "description": "ui main plus",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "category": "3D View"
}


import bpy


class LAZYWEIGHT_OT_plus_reset_all_pose(bpy.types.Operator):
    bl_idname = "lazyweight.plus_reset_all_pose"
    bl_label = "Lazy Weight Plus "
    bl_description = "Lazy Weight Plus"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return (context.mode == 'PAINT_WEIGHT' and context.active_pose_bone) or (context.mode == 'POSE')

    def execute(self, context):
        bpy.ops.pose.select_all(action='SELECT')
        bpy.ops.pose.transforms_clear()
        bpy.ops.pose.select_all(action='DESELECT')
        return {'FINISHED'}


class LAZYWEIGHT_OT_plus_toggle_view(bpy.types.Operator):
    bl_idname = "lazyweight.toggle_view"
    bl_label = "Lazy Weight Plus "
    bl_description = "Lazy Weight Plus"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        if context.mode == 'PAINT_WEIGHT':
            context.space_data.shading.type = 'WIREFRAME'
            context.scene.tool_settings.vertex_group_user = 'ACTIVE'
            context.space_data.overlay.show_wpaint_contours = True
            context.space_data.shading.show_xray = True
            context.space_data.shading.xray_alpha = 0.9
            context.space_data.shading.xray_alpha_wireframe = 0.95
        else:
            context.space_data.shading.type = 'SOLID'
            context.space_data.shading.light = 'STUDIO'
            context.space_data.shading.show_xray = False

        return {'FINISHED'}


class LAZYWEIGHT_PT_plus_main(bpy.types.Panel):
    bl_label = "Lazy Weight Tool+"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Item"

    def draw(self, context):
        workspace = context.workspace
        layout = self.layout
        row = layout.row(align=True)
        row.operator(LAZYWEIGHT_OT_plus_reset_all_pose.bl_idname, text="Reset Pose")
        row.operator(LAZYWEIGHT_OT_plus_toggle_view.bl_idname, text="Toggle View")
        
        layout.use_property_split = True
        layout.use_property_decorate = False


class LAZYWEIGHT_PT_plus_curve(bpy.types.Panel):
    bl_label = "Falloff"
    bl_parent_id = "LAZYWEIGHT_PT_plus_main"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Item"
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(self, context):
        return context.mode == 'PAINT_WEIGHT'

    def draw(self, context):
        layout = self.layout
        settings = context.tool_settings.weight_paint
        brush = settings.brush

        if brush is None:
            return

        col = layout.column(align=True)
        row = col.row(align=True)
        row.prop(brush, "curve_preset", text="")

        if brush.curve_preset == 'CUSTOM':
            layout.template_curve_mapping(brush, "curve", brush=True)

            col = layout.column(align=True)
            row = col.row(align=True)
            row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
            row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
            row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
            row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
            row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
            row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'

        col.separator()
        row = col.row(align=True)
        row.use_property_split = True
        row.use_property_decorate = False
        row.prop(brush, "falloff_shape", expand=True)


classes = (
    LAZYWEIGHT_OT_plus_reset_all_pose,
    LAZYWEIGHT_OT_plus_toggle_view,
    LAZYWEIGHT_PT_plus_main,
    LAZYWEIGHT_PT_plus_curve,
)


def register():
    for i in classes:
        bpy.utils.register_class(i)


def unregister():
    del bpy.types.Scene.mywrkspc
    for i in classes:
        bpy.utils.unregister_class(i)

次回もスキニング関連の予定です。