2014年7月5日土曜日

blender - 頂点法線転写ハック

頂点法線転写アドオン 
http://blenderartists.org/forum/showthread.php?259554-Addon-EditNormals-Transfer-Vertex-Normals/ 

がありますが、エディットモードに入るとBlenderは頂点法線が強制的に再計算する仕様のようで 折角いじった法線がキャンセルされます。 そこで、スクリプトをちょろっと改造して、カスタムデータとして法線と頂点インデックスを保存します。(注意:この記事は人柱向けです)

def joinBoundaryVertexNormals(self, context, destobjs,
                              INFL=0.0, MAXDIST=0.01):
    '''
    Average smoothing over boundary verts, usually same-location.
    destobjs = list, generally context.selected_objects
    INFL = float, influence strength
    MAXDIST = float, distance to influence... probably not necessary
    '''
    bms = {}
    bmsrc = bmesh.new()
    scene = context.scene
 
    for obj in destobjs:
        # These type != 'MESH' checks could be alleviated by removing
        #  non-mesh objects in execute(), but, may wish to
        #  support non-mesh objects one day
        if obj.type != 'MESH':
            continue
        bms[obj.name] = bmesh.new()
        bm = bms[obj.name]
        bm.from_mesh(obj.to_mesh(scene, False, 'PREVIEW'))
        bm.transform(obj.matrix_world)
        destverts = bm.verts
     
        for otherobj in destobjs:
            if otherobj.type != 'MESH' or obj == otherobj:
                continue
            gatherSourceVerts(bmsrc, otherobj, scene, 'ONLY')
            sourceverts = bmsrc.verts
         
            indices = []
            normals = []

            for vert in destverts:
                near = nearestVertexNormal(sourceverts, vert, MAXDIST)
                if near:
                    offset = near * INFL
                    vert.normal = (vert.normal + offset) * 0.5
                    vert.normal.normalize()
                    indices.append(vert.index)
                    normals.append(vert.normal[0])
                    normals.append(vert.normal[1])
                    normals.append(vert.normal[2])

         
            otherobj["trans_normal_index"] = indices
            otherobj["trans_normal"] = normals

            bmsrc.clear()
 
    for name in bms:
        # Everything's been modified by everything else's original state,
        #  time to apply the modified data to the original objects
        bm = bms[name]
        for obj in destobjs:
            if obj.name == name:
                bm.transform(obj.matrix_world.inverted())
                bm.to_mesh(obj.data)
                bm.free()
    bmsrc.free()
def transferVertexNormals(self, context, src, destobjs,
                          INFL=0.0, MAXDIST=0.01, BOUNDS='IGNORE'):
    '''
    Transfer smoothing from one object to other selected objects.
    src = source object to transfer from
    destobjs = list of objects to influence
    INFL = influence strength
    MAXDIST = max distance to influence
    BOUNDS = ignore/include/only use boundary edges
    '''
    bm = bmesh.new()
    bmsrc = bmesh.new()
    scene = context.scene
    gatherSourceVerts(bmsrc, src, scene, BOUNDS)
    sourceverts = bmsrc.verts
 
    for obj in destobjs:
        if obj.type != 'MESH' or obj == src:
            continue
        bm.from_mesh(obj.to_mesh(scene, False, 'PREVIEW'))
        bm.transform(obj.matrix_world)
        destverts = bm.verts
     
        indices = []
        normals = []

        
        for vert in destverts:
            near = nearestVertexNormal(sourceverts, vert, MAXDIST)
            if near:
                offset = near
                #if INFL < 0.0:
                #    offset = offset * -1
                #vert.normal = vert.normal.lerp(offset,abs(INFL))
                vert.normal = offset * INFL
                vert.normal.normalize()
                indices.append(vert.index)
                normals.append(vert.normal[0])
                normals.append(vert.normal[1])
                normals.append(vert.normal[2])
     
        obj["trans_normal_index"] = indices
        obj["trans_normal"] = normals

        bm.transform(obj.matrix_world.inverted())
        bm.to_mesh(obj.data)
        bm.clear()
 
    bm.free()

で、以下のような感じの、エディットモードに入った瞬間に保存したデータに上書きするような
スクリプトを作って、
エディットモードに入って法線が元に戻される前に、テキストエディタから手動実行させます。
1回実行すると裏で常駐します。

なお、これらのコードはどちらも書き捨てコードなので、基本的に通常使用に耐えないものと認識しておいてください…。データ重くなりますし、トポロジ変更された場合などは考慮されていません。特に設定保存などしないでください。試した後は丸ごと元に戻すくらいの勢いでお願いします。。

import bpy
import bmesh
import mathutils
pre_mode = bpy.context.mode
def draw_normal_handler(scene):
    global pre_mode
    if pre_mode != bpy.context.mode:
        print("mode changed", bpy.context.mode)
        pre_mode = bpy.context.mode
     
        if bpy.context.mode == "EDIT_MESH":
            obj = bpy.context.active_object
            if "trans_normal_index" in obj and "trans_normal" in obj:
                vi_list = obj["trans_normal_index"]
                no_list = obj["trans_normal"]
                 
                bm = bmesh.from_edit_mesh(obj.data)
                for i, vi in enumerate(vi_list):
                bm.verts[vi].normal = mathutils.Vector((\
                        no_list[i * 3 + 0],\
                        no_list[i * 3 + 1],\
                        no_list[i * 3 + 2]))
def remove_draw_normal_handler():
    for handle in bpy.app.handlers.scene_update_post:
        if handle.__name__ == "draw_normal_handler":
            bpy.app.handlers.scene_update_post.remove(handle)
            break
     
#remove_draw_normal_handler()
bpy.app.handlers.scene_update_post.append(draw_normal_handler)



エディットモードに入っても、こんな感じで法線が確認できます。


でもこのハック…大変重いのです…
もうちょっと洗練して公開予定でしたが、ちょっと重すぎなので別のアプローチに移行中です…
(法線マップにベイクしたい)

--------------------------------------------------------------------------------

もうちょっと詳しい手順を張っておきます(あくまで人柱向けです)


転写プラグインは、転写先、転写元の順で選択してから、実行するっぽい


一番近い頂点の法線が転写されるだけっぽいので、転写元のほうはちょっと細かくしといたほうがよいかも(これはやりすぎた)


実行すると、なんか(4)に出てくる。このとき、上記改造をしていると非常ーーーーに重いので注意。influenceをそっと"1.0"にしよう。あとdistanceは良く分からんのでだいたいBlender Unit何個分くらい離れてるか程度入れとけばよいのでは。。

それからエディットモードにする前に、上記の下のほうのスクリプトを実行する。最初から実行していても問題ないです。一度実行するとずっと裏で動いてる系のスクリプトなのです。。解除するときは、コメントアウトしている#remove_draw_normal_handler()のほうを有効にして、(最後の行はコメントアウトして)実行すると解除されます

でこの場合はこんな感じに。ちょっと斜めの奴がいますが、まぁなんとなくいけてるんじゃなかろうか…(訳:これ俺のバグじゃないです)