2016年12月17日土曜日

Windowsで.blendファイルのプレビュー

Blenderはレンダリング画像をプレビューとして保存することができて
エクスプローラーでファイルの中身を確認できるのが便利だったのですが
家のマシンをWindeow10にしたところ プレビューが表示できなくなってしまっていて、 

対処法を探しても 検索一発で見つけることができずに多少時間がかかったので メモ


blenderartists.org の
.Blend files not using preview thumbnails in windows explorerに書いてあることそのままで

スタートメニューを右クリックするメニューからコマンドプロンプト(管理者)を選んで実行して

黒画面のコマンドプロンプト上で

chdir c:\program files\blender foundation\blender

と入力してEnter(Blenderの実行ファイルのあるところに移動)

blender -R


と入力してうまくいっていると
エクスプローラー上で.blendファイルを再選択すると プレビューが表示されるようになってる
はず。
単純なことなものの
新マシン導入時にしか使わなくて忘れてしまいそうなのでメモってみましたがどうでしょうか。

2016年12月16日金曜日

オブジェクトを前後左右からレンダリングしたい!

他の人に作っているモデルを見せたい場合に
前後左右のキャプチャ画面を渡しているってことが多いかと思います。

いちいちキャプをとって画像処理ソフトでつなげるのも面倒なので
一気にレンダリングしてしまうスクリプトを書いてみました

3Dビューで選択している形状が収まるようにカメラの設定を調整して
前後左右平行投影でのレンダリングと 現在のカメラ設定でレンダリングした画像を作成し [Combineed_IMG]という名前で横に並べた画像を UV/画像エディッタに作成します

import bpy
import math
import os
import numpy as np
import mathutils
#レンダリングの幅を固定
render_width = 300


#レンダリング画像の保存パス
render_path = bpy.context.scene.render.filepath

def render_func(context):
    #シーンで使用しているカメラ
    camera = context.scene.camera
    if len(context.selected_objects) == 0: return
    ###############################
    ###########設定の退避###########
    ##レンダリングサイズ
    render = context.scene.render
    ref_render_x = render.resolution_x
    ref_render_y = render.resolution_y
    ref_render_percentage =render.resolution_percentage
    ##カメラタイプ(透視投影,平行投影など)
    ref_camera_type = camera.data.type
    ##位置
    ref_location = camera.location.copy()
    ref_rotate = camera.rotation_euler.copy()
    ##################################
    
    ##オブジェクトモードに
    bpy.ops.object.mode_set(mode = 'OBJECT')
    #bpy.ops.object.select_all(action="DESELECT")
    ##################################
    ##カメラ設定
    ###レンダリングサイズ
    (bbox_width, center_pos) = get_bound_data(context)
    set_render_size(context, bbox_width)
    #初期状態でレンダリング
    bpy.ops.render.render()
    #保存
    f_name = "view4.png"
    seve_render(f_name)
    ###平行投影
    camera.data.type = 'ORTHO'
    #平行投影のスケールを設定(幅がBU基準)
    camera.data.ortho_scale = max(bbox_width)*1.2
    #カメラ位置を(0, -10, 0.3) 回転を(90°, 0, 0)に
    camera_pos =get_camera_pos_top(bbox_width, center_pos)
    camera.location = camera_pos
    camera.rotation_euler = (math.radians(90.0), 0, 0)
    #########################################
    pos = mathutils.Vector((camera_pos))
    mat_rot1 = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'Z')
    mat_trs = mathutils.Matrix.Translation(mathutils.Vector(center_pos))
    mat_rot = mat_trs *mat_rot1 *mat_trs.inverted()
    for i in range(4):
        #レンダリング
        bpy.ops.render.render()
        #保存
        f_name = "view%s.png" % i
        seve_render(f_name)
        #90度回転
        pos = mat_rot * pos
        camera.location = pos
        cam_rot = (math.radians(90.0), 0, math.radians(90.0)*(i+1))
        camera.rotation_euler = cam_rot
    ##################################
    ###########設定の書き戻し###########
    ###レンダリングサイズ
    render.resolution_x = ref_render_x
    render.resolution_y = ref_render_y
    render.resolution_percentage = ref_render_percentage
    ##カメラタイプ
    camera.data.type = ref_camera_type
    ##位置
    camera.location = ref_location
    camera.rotation_euler = ref_rotate
    #########################################
    combine_image(render_path)

#選択形状から描画範囲のデータを取得(中心点, 幅)
def get_bound_data(context):
    objects = context.selected_objects
    bb_point_list = []
    #選択形状のbbox値をグローバル座標で取得
    for obj in objects:
        bbox_list = [mathutils.Vector(v[:]) for v in obj.bound_box]
        mat = obj.matrix_world
        bb_point_list += [mat*v for v in bbox_list]
    #範囲を取得
    bbox_width = []
    center_pos = []
    for i in range(3):
        min_i = min(bb_point_list, key = (lambda x: x[i]))[i]
        max_i = max(bb_point_list, key = (lambda x: x[i]))[i]
        bbox_width.append( max_i - min_i )
        center_pos.append( (max_i + min_i)/2 )
    return(bbox_width, center_pos)
#レンダリングサイズの設定(幅を固定)
def set_render_size(context, bbox_width):
    render = context.scene.render
    print(bbox_width)
    #縦の長さを取得
    render_height = int(render_width*(bbox_width[2]/max(bbox_width[:2])))
    render.resolution_x = render_width
    render.resolution_y = render_height
    render.resolution_percentage = 100
#正面画像レンダリング用のカメラ位置を設定
def get_camera_pos_top(bbox_width, center_pos):
    distance = max(bbox_width)
    return(center_pos[0], center_pos[1]-distance,center_pos[2])
#画像の保存
def seve_render(f_name):
    img_path = os.path.join(render_path,f_name)
    bpy.data.images['Render Result'].save_render(filepath=img_path)

#画像の読み込み
def load_tex(f_path):
    img = bpy.data.images.load(f_path)
    return(img)

#rgbaの画像をnparrayに変換
def img_to_nparray(img):
    bit_len = len(img.pixels)
    (width,height) = img.size
    channels = img.channels #色数
    #numpy arrayを作成
    pixlist = np.array(img.pixels)
    pixlist = pixlist.reshape( height, width, 4)
    return( pixlist )
    
#結合処理
def combine_image(render_path):
    width = 0
    height = 0
    image_list = []
    for i in range(5):
        f_name = "view%s.png" % i
        img_path = os.path.join(render_path,f_name)
        img = load_tex(img_path)
        image_list.append(img)
        width += img.size[0]
        height = max(height, img.size[1])
    combine_img_np = np.zeros((height, width, 4))
    offset = 0
    for img in image_list:
        #読み込んだ画像をnumpy配列に
        np_array = img_to_nparray(img)
        (height, width, deps) = np_array.shape
        #結合処理
        combine_img_np[0:height, offset:offset + width] = np_array
        combine_img_np[:,offset] = np.ones(4)
        offset += width
    #numpy配列からBlender画像データオブジェクトを作成
    img_name = 'Combineed_IMG'
    (height, width, deps) = combine_img_np.shape
    image_object = bpy.data.images.new(name=img_name, width=width, height=height)
    image_object.pixels = list(combine_img_np.flatten())
    
render_func(bpy.context)

選択形状を基準にレンダリングするため 形状を選択していない場合は動かなかったりと
書き捨てに近いスクリプトですが いかがでしょうか

2016年12月5日月曜日

BlenderからPhotoshopを制御4 -選択UVからPhotoshpの選択範囲

Blender Advent Calendar 2016 12/05の記事です。

こちらのサイトではBlenderのスクリプトからPhotoshopを制御をする方法の解説を書いてきました


BlenderからPhotoshopを制御1-Blender-PyhtonとPhotoshop-JavaScriptについて
BlenderからPhotoshopを制御2-レンダリングした画像をPhotoshpで開く
BlenderからPhotoshopを制御3-画像をPhotoshpで開くアドオン作成
これまでは 画像を開くだけの比較的シンプルな動作でしたが
今回はより実用的なものを作成したいと思います。

Blenderのテクスチャを作成する場合 3Dペイントの機能や他の専門のソフトを使うこともあるでしょうが、Photoshopで作成する人も多いかと思います。
特にローポリゴンの場合 UVの面に合わせた範囲で塗り分けることもあるのではないでしょうか

そこでBlenderで選択している面のUVをPhotoshpの選択範囲にするアドオンを作成しようかと思います。

まず基本的な考え方から
UV値というのは画面の幅と高さを基準に特定の座標がどの割合の位置にあるか表したものです
(例えば 640*480pixelの画像の [320pixel,240pixel]の座標のUV値は[0.5,0.5] )
テクスチャ画像と3Dソフトのポリゴンがどの部分の画像使用するかの対応付けはこの方式で表されます



また、Blenderでは面が頂点をどういう順番で結んで作られているかという情報(loops)をデータとして持っていて、そこから面がテクスチャ上のどこを囲って使用しているのかということも取得できます

一方 Photoshopには多角形選択ツールがあり 多角形の頂点座標を指定することによりスクリプトからも選択範囲が作れます
つまり これらを連携することができれば Blnderで指定したUV座標で選択範囲が作れることになります。

では、実際のアドオンコードを例示していきます。
今回も複数ファイルで構成されたアドオンになるため、アドオン必要なファイル一式を収める「UV_to_PhotoshopSelection」というフォルダを作成します

bl_info = {
    "name": "UV to Photoshop",
    "description": "選択したUV面をPhotoshopの選択範囲に",
    "author": "Yukimi",
    "version": (0,4),
    "blender": (2, 6, 0),
    "location": "image",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "http://yukimi-blend.blogspot.jp/",
    "category": "Import-Export"}

import bpy
if "bpy" in locals():
    import imp
    imp.reload(uv_to_photoshop)
else:
    from . import uv_to_photoshop

###################################################
class UvSelectionToPSD(bpy.types.Operator):
 '''SelectedUV to Photoshop selection'''
 bl_idname = "action.uv_to_photoshop"
 bl_label = "SelectedUV to Photoshop selection"
 def execute(self, context):
  UV_to_photoshop(context)
  return {'FINISHED'}
# メニューの構築処理
def menu_func(self, context):
 self.layout.operator("action.uv_to_photoshop", 
  text="選択UVをPhotoshop選択範囲に" )
# アドオン有効化時の処理
def register():
 bpy.utils.register_module(__name__)
 bpy.types.IMAGE_MT_uvs.prepend(menu_func)
# アドオン無効化時の処理
def unregister():
 bpy.utils.unregister_module(__name__)
 bpy.types.IMAGE_MT_uvs.remove(menu_func)

if __name__ == "__main__":
 register()
##########################################################
アドオンとして動かすためのスクリプトを書いたファイルです
実際の処理はuv_to_photoshop.pyに記述してあり そちらを読み込んでいます

import bpy
import os
import subprocess
import time
import random
import bmesh

#実行するjavascriptの名前
js_name = "UV2Selection.jsx"
#このスクリプトのあるディレクトリのパス
mydoc_dir = os.path.dirname(__file__)
#実行するスクリプトのパス
VB_Hub = os.path.abspath(os.path.join(mydoc_dir, "VB_Hub.vbs"))
jscript = os.path.abspath(os.path.join(mydoc_dir, js_name))
#Blenderの一時ファイルのパス
tmp_dir = bpy.context.user_preferences.filepaths.temporary_directory

#UVの頂点の値を取得
def get_uvloop(loops, uv_layer):
    uv_list = [list(v[uv_layer].uv) for v in loops]
    return(uv_list)

#面が選択されているかの判別
def uv_face_selected(bm_face, uv_layer):
    #use_uv_select_sync = True の時の取得できる面とFalseの時に取得できる選択頂点は異なる
    if bpy.context.scene.tool_settings.use_uv_select_sync:
        #面の選択状態
        return( bm_face.select )
    else:
        #3Dビューの選択面とUVの選択が独立している場合
        if bm_face.select :
            l = len( bm_face.loops)
            i = 0
            #ループが全て選択されている面を判別
            for v in bm_face.loops:
                if v[uv_layer].select: i += 1
            return( i == l )
        else: return(False)
#UVで選択されている面の取得
def get_uv_list():
    #状態の更新
    bpy.context.edit_object.update_from_editmode()
    #選択形状のデータ
    mesh = bpy.context.active_object.data
    bm = bmesh.new()    # 空の BMeshを作成
    bm.from_mesh(mesh)   # メッシュからBMeshを作成
    #アクティブなUV_layer(bmeshで使うにはUVlayerもBMeshオブジェクトでないといけない)
    uv_layer = bm.loops.layers.uv.active
    #選択UVの面データを取得
    uv_list = []
    for f in bm.faces:
        if uv_face_selected(f, uv_layer):
            #UVの頂点の値を取得
            vert_uv_list = get_uvloop(f.loops, uv_layer)
            uv_list.append(vert_uv_list)
    return(uv_list)
    
def UV_to_photoshop(context):
    #ファイル名の作成
    source_str = 'abcdefghijklmnopqrstuvwxyz'
    f_name = time.strftime("%y%m%d%H%M") + "".join([random.choice(source_str) for x in range(3)]) + ".txt"
    file_path  = os.path.join(txt_dir,f_name)
    #UVデータの取得
    txt = str(get_uv_list())
    #ファイルへの書き込み
    with open(file_path, "w") as f:
        f.write(txt)
        f.close
    #スクリプトの実行
    subprocess.call(["CScript", VB_Hub, jscript, img_path, "//nologo"])
選択している各面を構成する頂点のUV座標をリストとして取得して そのまま文字列に変換してテキストファイルに書き出しています
Photoshop側ではJSONファイルとして解釈して情報を取得するようにしています
Blenderで編集モードのポリゴンの情報を取得するのにはbmeshライブラリをimportし BMeshのオブジェクトからデータを取得する必要があります

#target photoshop;
#include "json2.js";

//ドキュメントの設定を退避
var refRulerUnits = app.preferences.rulerUnits;
var refTypeUnits = app.preferences.typeUnits;
var refDisplayDialogs = app.displayDialogs;
//画像の単位をピクセルに
app.preferences.rulerUnits = Units.PIXELS;
app.preferences.typeUnits = TypeUnits.PIXELS;
app.displayDialogs = DialogModes.NO;

//ファイルの読み込み
var txt_file =  File(arguments[0]);
var txt = load_text(txt_file);
//jsonでパース
var layoutJson = JSON.parse(txt);
for(var i =0 ; i < layoutJson.length; i++){
    //選択範囲の作成
    uv2selection(layoutJson[i])
}
//ドキュメント設定を書き戻す
app.preferences.rulerUnits = refRulerUnits;
app.preferences.typeUnits = refTypeUnits;
app.displayDialogs = refDisplayDialogs;

//UVデータから選択範囲の作成
function uv2selection(uv_list){
    var point_list = new Array()
    width = activeDocument.width
    height = activeDocument.height
    for(var i = 0; i < uv_list.length; i++){
        var pos_x = uv_list[i][0] * width
        var pos_y = (1 - uv_list[i][1])  * height
        point_list.push( [pos_x, pos_y] )
        }
    //選択範囲を追加
    activeDocument.selection.select(point_list, SelectionType.EXTEND)
    }

//テキストファイルの読み込み
function load_text(f_path){ 
    if (f_path){
        fileObj = new File(f_path);
        flag= fileObj.open("r");
        if (flag == true){
            var text = fileObj.read();
            fileObj.close();  
   }
  else{
            alert("ファイルが開けませんでした");
   }
  }
    return(text)
 }

テキストファイルを読み込んで必要なデータを取得するのにjson2.jsライブラリをダウンロードして利用しています
activeDocument.selection...の部分でリストで与えた頂点で囲った選択範囲を作成しています
因みにスクリプト実行時に既に選択範囲があった場合 そちらに追加選択する形になります

これら3つのスクリプトと ライブラリのjson2.js 前回まで同様に連携用スクリプトのVB_Hub.vbsを同じフォルダに収めて アドオンとして読み込ませれば完成です  …が
Photoshopの多角形選択で隣り合う面の選択範囲を作成すると Photoshopの誤差で微妙な隙間ができてしまいます

そちらの対応もしたアドオンのZipファイルを作成しました


これを「ユーザー設定」の「ファイルからインストール」で読み込んで アドオンを有効にすると UV/画像エディッターのUVの項目に「選択UVをPhotoshop選択範囲に」が追加されるかと思います

いかがでしょうか
 Blender Advent Calender 2016 明日の記事は___monta___ さんによる「あまり丁寧に紹介されていないノードの情報を、無駄に丁寧に紹介する」です。どうぞお楽しみに!