今日こそやるぞ5日目(いやもう休む)

今日こそやらないページ(サービス潰れて移転五回目)

頂点法線とオブジェクトスペースノーマルカラーを行き来するスプリクトがもうちょいマシになった「Blender+Python」

更新メモ 
8/29 18:00頃スプリクトのミスと速度を改善
8/30 不具合あったので不具合3として明記

通常の想定した使い方は8/22付けの記事のほう見てもらうとして

既知の不具合だけ先に書いておく(以前の記事の方でも追記はしたけど雑多になってきてたので)


既知の不具合1
このスプリクトで入れ込んだカスタム法線を読み込んで色を付けることが出来ない
(法線が取れずに全部0が帰ってきて、結果全部灰色になる)
blenderの機能で転写したカスタム法線orカスタム法線ナシの状態ならまず普通に色がつかないことはない
要するに現状
法線->色->法線 まではいけるけどそこからもう一回
法線->色->法線->色 みたいにもう一回色となると無理っぽい?
回避策として同じ形状を一度複製して
スプリクトで法線流し込んだほう->複製できたてホヤホヤのほうに再度転写してやれば
スプリクトでいじった法線を再度色化はできるっぽいのでとりあえず作業はできる

既知の不具合2
不具合というか仕様というかだけど
今回追加した頂点色スムースはちょっとでも頂点数多いモデルにかけるとクソ重い
(処理する頂点自体が少なくても
 その選択されてるのの割り出しに頂点総当り処理している為)
改善できるやり方がネットで見つかったら更新したいけどなぁ……
またしらべていこうとは思うけど今回はとりあえず機能実装まではできたのでここまで

8/30追記
既知の不具合3
Color->Normalしたときにモデルのシャープ設定がぐちゃぐちゃになるっぽい
予め辺分離モデファイアは適応済みにしておく
コピーしたモデルでこのスプリクト使って色々編集して最後に元のモデルに転写し直すとか?
なにかしらの対応が必要
更に追記:
なんかblender標準のデータ転写機能法線転写してもシャープがグチャるっぽい
スプリクトのバグとかでなくblenderのカスタム法線周りの仕様?
それとも自分のデータの持ち方が何かしら駄目なのか?
今のところ法線はいろいろいじったらスプリクトで画像テクスチャデータとして退避させておいて
シャープが関係するようなすべての作業終わってから最後に適応させるしかなさ気?
blender内で完結もしくは他のソフトにもっていくとしてもそっちがオブジェクトスペースのマールマップつかえるならそっち使った方がいいっぽい)

今回追加した機能

頂点色スムーズの「ColorSmoothSelectVertex」ボタン
他のボタンはオブジェクトモードで使用するけど
これは編集モードでなおかつ対象頂点を選んでから使用するのに注意
当然頂点カラー情報も持ってないと駄目
オブジェクトモードで編集中オブジェクトも選ばれている必要がある
(編集モードでデータセーブして作業再開時に編集モードから始まった場合とかに
オブジェクトモードで今エディット中のオブジェクトが選択されてないとかがありえるので)

一応色に焼き付けてから色の方を今回の機能でぼかして
サイド法線に転送することで画像見たく陰影を柔らかくできる

(画像の耳は三回ほどかけた感じでやり過ぎたかも感ある二回ぐらいでよかったかも)
8/29追記:
なんか命令文の記述順序おかしくて必要以上に色を入れなおし(結果的にボカシまくり)してたっぽいので
今はそれを正確にやるようになったかわりに一回のボカシは弱くなったので
三回程度じゃ画像見たくまではボケないかなと思う

もっとスムースかけていけばどんどん陰影が柔らかくなるので
リアル系のシェーダー掛ける場合であっても光が透けるような素材の箇所やできるだけ柔らかく見せたい箇所には有効かも
箇所によってスムースの力加減は気をつけないとだめだろうけど
毛関係とか画像の耳みたく薄いところとかおっぱいとか


画像の順序としては
 14
 23
な工程な感じ

ただ上でも書いたように現状クソ重なので
今回の耳の場合だと顔だけのモデル頂点7463に両耳800ちょいの頂点数選んでスムースかけると

自分の3,4年前のPCで1分か二分?ぐらいは応答なくなる感じ
8/29追記:
スプリクトいじって多少改善した?
同じ状況下で一分まではギリかからなくなったと思うけど
まだ瞬間的に処理終わらないのでなんか無駄なことしている感
やっぱ選択している頂点取るのに全頂点総当りしてるのが原因な模様
更に追記:
重い原因の大部分はオブジェクトモードとエディットモードの行き来にあった
模様
これがオブジェクトの総頂点数に応じて処理時間取られるっぽい
任意の1頂点選ぶためと、頂点カラーをいじるために
1頂点につき計二回オブジェクトモードに移行してるんだけど
(該当箇所は下のスプリクトで赤字にした)
これが片方だけでもどうにかできれば単純計算で倍速になるっぽい感
でもおかしくね?なんで頂点選ぶ命令文がオブジェクトモード動作なの?普通に考えて編集モードで働くべきじゃね?おかしくね?(愚痴)
編集モードのままでもそちらの動作が出来る方法が見つかったら処理速度改善するので
其れまでは必要箇所だけメッシュ切り出してスムースかけるしかないかなぁ……

重いのでDOS窓から起動した場合にDOS窓に処理状況表示する機能追加したのでこっちで自分のPC、モデルと処理スピードの兼ね合いみてくだち……

こっちのスムースじゃなくてもblender標準の頂点ペイント機能のブラシでぼかしてもいいんだけど
選択した範囲に均一にボカシをかけたかったけどその手段が自分の調べた範囲だとなかったので作成した感じの機能
ちゃんとそういう機能あるよ、とか代わりになりそうな機能orやり方知ってるのなら
そっちの手段取った方がいい


前回記事からの改善
Color -> NORMAL時に値をノーマライズしてなかったので
しれっと動作おかしくなることがあったのでちゃんとするようにしました
(これやっとかないとこんかいみたくぼかした色は法線に戻せない)

色をぼかさなくても直接法線スムースすればいいじゃんとかもあるかもだけど
上の画像の使い方以外にも白黒の頂点色付けてそれをぼかして
テクスチャの色合いをつけるため、ノーマルマップ合成する時にとかのマスクにしたいとかもあったので

以下スプリクト
例によってブログに貼るために半角スペースを全部全角スペースに置き換えてるので
使いたい奇特な人がいたら要逆置き換え

import bpy
import mathutils
import bmesh

class NormalColorSmoothSelectVertex(bpy.types.Operator):
    bl_idname = "object.color_smooth_vertex_operator"
    bl_label = "ColorSmoothSelectVertex"

    def execute(self, context):
        
        
        #アクティブオブジェクト取得するのにオブジェクトモードに行かないと駄目っぽい
        bpy.ops.object.mode_set(mode='OBJECT')        
        for object in bpy.context.selected_objects:
            if object.type == 'MESH':
                object = bpy.context.scene.objects.active
                mesh = object.data
                

        #頂点情報を得るためにエディットモードに移行
        #選択拡大や頂点選択解除もエディットモードに入らないと駄目っぽい
        bpy.ops.object.mode_set(mode='EDIT')
        bm = bmesh.from_edit_mesh(object.data)
        selected_verts = [vert for vert in bm.verts if vert.select]

        #なんかオブジェクトモードに入ると取得したbmeshデータが死ぬ?っぽいので
        #頂点番号リストだけ退避
        verts_list = 
        for id in selected_verts:
            verts_list.append(id.index)

        Vertex_Color_list = 

        
        i = 0
        #取得した頂点を一つ一つ処理
        for id in verts_list:
    
            #頂点の選択解除
            bpy.ops.mesh.select_all(action='DESELECT')  
            #今処理する頂点を選択(頂点選択なのにオブジェクトモードじゃないと働かないぞこいつ)
            #ここが糞重い原因その1

            bpy.ops.object.mode_set(mode='OBJECT')
            mesh.vertices[id].select = True    
            #後々選択拡大でとなりの頂点の取得するためのモード移行を先にやっておく
            bpy.ops.object.mode_set(mode='EDIT')

            #一度取得したbmeshがモード行き来で死んでるっぽいので再取得
            bm = bmesh.from_edit_mesh(object.data)


            #今回処理する頂点のloopをまず取得
            verts_list_now = 
            #選択頂点を取得してから
            selected_verts_Now_bm = [vert for vert in bm.verts if vert.select]
            #その頂点のloopを取得
            #初回のforはいらないと思うけど、この直後のほうと記述変えるのめんどいので放置
            for now_vert in selected_verts_Now_bm:
                for loop in now_vert.link_loops:
                    verts_list_now.append(loop.index)
                
            #選択拡大
            bpy.ops.mesh.select_more()
            
            #選択拡大状態で再度loop番号取得
            more_verts_list_now = 

            selected_verts_Now_bm = [vert for vert in bm.verts if vert.select]
            for now_vert in selected_verts_Now_bm:
                for loop in now_vert.link_loops:
                    more_verts_list_now.append(loop.index)

            #色取得する命令文がこいつまたオブジェクトモードじゃないとうごかねぇ
            #糞重い原因その2前半
            bpy.ops.object.mode_set(mode='OBJECT')
            
            #アクティブな頂点カラーグループの取得
            #直前にとらないとオブジェクトモードとエディットモードの行き来で
            #なんかデータのつながりおかしくなるっぽい?ので頂点色データはここで取る
            #object、meshは取りなおさなくても大丈夫っぽい?

            VertexNormalColor = mesh.vertex_colors.active.data if len(mesh.vertex_colors) else None 
            
            NowColorR = 0
            NowColorG = 0
            NowColorB = 0
            
            for id_now in more_verts_list_now:
                vcolor = VertexNormalColor[id_now].color
                NowColorR = NowColorR + vcolor[0]
                NowColorG = NowColorG + vcolor[1]
                NowColorB = NowColorB + vcolor[2]
                
            #全部足して(足すのは直上の処理で済んでるけど)割って値的にスムース
            #後々色としてぶち込むために小数点三位以下は切り捨てもする
            NowColorR = round(NowColorR / len(more_verts_list_now), 3)
            NowColorG = round(NowColorG / len(more_verts_list_now), 3)
            NowColorB = round(NowColorB / len(more_verts_list_now), 3)       

            #処理済みの値として後々実際にぶち込むために一度リストに格納
            for id_now in verts_list_now:
                Vertex_Color_list.append([id_now,NowColorR,NowColorG,NowColorB])
            
            #最後の頂点の処理が終わっているなら色をぶち込む
            #終わってないなら今の処理状況をprintで出力
            if i == (len(verts_list) - 1):
                for NowColor in Vertex_Color_list:
                    id_now = NowColor[0]
                    VertexNormalColor[id_now].color =(NowColor[1],NowColor[2],NowColor[3])
                print('end')
            else:
                i = i + 1
                print('NowVertex:'+str(i)+'/'+str(len(verts_list)))
            
            #糞重い原因その2後半
            #色の処理終わったのでエディットモードに戻る
            bpy.ops.object.mode_set(mode='EDIT')
            
        
        #元々選択してた頂点の状態に戻そう
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')
        for id in verts_list:
            mesh.vertices[id].select = True
        bpy.ops.object.mode_set(mode='EDIT')
        
        return {'FINISHED'}


class NormalColorImput(bpy.types.Operator):

    #ボタン押して呼び出すときの名前
    bl_idname = "object.normal_imput_operator"

    #ボタンに表示するときの名前
    bl_label = "Normal -> Color"

    def execute(self, context):
  
        #選択中オブジェクトを順番に処理するためのfor?
        for object in bpy.context.selected_objects:
            #選択オブジェクトがメッシュかどうか判定
            if object.type == 'MESH':
                #選択オブジェクト取得
                bpy.context.scene.objects.active = object 
                
                #取得したオブジェクトのメッシュをさらに取得?
                #メッシュ取得しないと各種頂点情報取れない?
                mesh = object.data
                
                
                #新規頂点カラーグループを追加
                #名称はNormalColor
                mesh.vertex_colors.new(name = "NormalColor")
   
                #頂点カラー取得して色をセットする準備
                #名前指定で取らないと自分の知識では無理
                #アクティブな頂点カラーで取ろうとしても
                #スプリクト内で作ったばかりだからアクティブな状態ではないため?
                VertexNormalColor = mesh.vertex_colors["NormalColor"]
            
            #カスタム法線がある場合はそちらから取得
            

            if mesh.has_custom_normals == True:

            
                #ポリゴンを一つ一つ処理?                
                for poly in mesh.polygons:

                
                    #ポリゴンの頂点を一つ一つ処理?                      
                    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):


                        #blnederではXが左右、Yが前後、Zが上下なことに注意
                        #カスタム法線はmesh経由のnormalでなくloops経由で取れる
                        #カスタム法線セットがない場合は旧カスタム法線だとかの色を持ってくる感じ?
                        #とにかくエラー的な動作になる
                        VertexNormalColor.data[id].color.r = 0.5 - (mesh.loops[id].normal[0] / 2)
                        VertexNormalColor.data[id].color.b = 0.5 - (mesh.loops[id].normal[1] / 2)
                        VertexNormalColor.data[id].color.g = 0.5 + (mesh.loops[id].normal[2] / 2)

                        
            #カスタム法線がない場合は通常の法線から取得
            else:
                
                for poly in mesh.polygons:                    
                    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):

                        VertexNormalColor.data[id].color.r = 0.5 - (poly.normal[0] / 2)
                        VertexNormalColor.data[id].color.b = 0.5 - (poly.normal[1] / 2)
                        VertexNormalColor.data[id].color.g = 0.5 + (poly.normal[2] / 2)


        return {'FINISHED'}



class NormalColorSmooth(bpy.types.Operator):
    bl_idname = "object.normal_smooth_operator"
    bl_label = "ColorSmooth"
    
    def execute(self, context):
    
        for object in bpy.context.selected_objects:
        
            target_obj = object.data
            
            #頂点カラーがハードエッジ的につく場合のスムース            
            bpy.context.scene.objects.active = object            
            bpy.ops.object.mode_set(mode='VERTEX_PAINT')
            target_obj .use_paint_mask = False
            bpy.ops.paint.vertex_color_smooth()
            target_obj.update()
            bpy.ops.object.mode_set(mode='OBJECT')
    
        return {'FINISHED'}



class NormalColorOutput(bpy.types.Operator):
    bl_idname = "object.normal_output_operator"
    bl_label = "Color -> Normal"

    def execute(self, context):
        
        object_normal_set = []
        
        for object in bpy.context.selected_objects:
            if object.type == 'MESH':
                bpy.context.scene.objects.active = object 
                mesh = object.data
                
            #アクティブな頂点カラーグループの取得
            #今度は名前(NormalColor)指定で取得するときとは後々の処理で
            #.dataが必要なくなることに注意
            #名前指定時  VertexNormalColor.data[id].color
            #今回        VertexNormalColor[id].color
            VertexNormalColor = mesh.vertex_colors.active.data if len(mesh.vertex_colors) else None
                
                
            for poly in mesh.polygons:
                
                for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
                    
                    vcolor= VertexNormalColor[id].color
                    
                    VerR = ( vcolor[0] * -2 ) + 1
                    VerG = ( vcolor[1] * 2 ) - 1        
                    VerB = ( vcolor[2] * -2 ) + 1

                    #値を法線角度としてふさわしくなるように正規化                
                    VV = mathutils.Vector*1
                    VV.normalize()
                    #カスタム法線にセットするためのリストにAdd  
                    object_normal_set.append(VV)
                    

            #カスタム法線にimport        
            object.data.normals_split_custom_set(object_normal_set)
    
        return {'FINISHED'}





class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Normal_Color_Panell"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_category = "Tools"


    def draw(self, context):
        layout = self.layout
        
        row = layout.row()
        layout.operator("object.normal_imput_operator")
        
        row = layout.row()
        layout.operator("object.normal_smooth_operator")

        row = layout.row()
        layout.operator("object.color_smooth_vertex_operator")
        
        row = layout.row()
        layout.operator("object.normal_output_operator")

def register():
    bpy.utils.register_class(HelloWorldPanel)
    bpy.utils.register_class(NormalColorImput)
    bpy.utils.register_class(NormalColorSmooth)
    bpy.utils.register_class(NormalColorOutput)
    bpy.utils.register_class(NormalColorSmoothSelectVertex)
    

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)
    bpy.utils.unregister_class(NormalColorImput)
    bpy.utils.unregister_class(NormalColorSmooth)
    bpy.utils.unregister_class(NormalColorOutput)
    bpy.utils.unregister_class(NormalColorSmoothSelectVertex)
    
if __name__ == "__main__":
    register()

*1:VerR,VerB,VerG