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 DupulicateActionAtCurrentTime(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の項目に「」が追加されるかと思います

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

2016年11月30日水曜日

BlenderからPhotoshopを制御3-画像をPhotoshpで開くアドオン作成

前回までは基本的な動作を中心に説明してきましたが
今回からはBlenderアドオンとして仕上げます

アドオン作成の細かい説明についてはnutti様が公開している「はじめてのBlenderアドオン開発」がよくまとまっているので、そちらを参考にしていただくといいかと思います

前回までの解説の通り
VB_HubやPhotoshop制御用のJavaScript等複数のファイルで構成される形になっているので
複数ファイルで構成されるBlenderアドオンの作り方で作成を進めていきます。

今回は「UV/画像エディッター」で表示している画像をPhotoshopの現在の書類で開くものを作ります
前のものはレンダリング画像のみが対象でしたが テクスチャペイントで作成した画像等も開くことができるようになります

まずは「img_to_photoshop」という名前でのフォルダ作成してます
ここに今回使用するスクリプトファイル(__init__.py VB_Hub.vbs img_open.jsx)を収めることとします
以下それそれのファイルを解説をします

bl_info = {
    "name": "image to Photoshop",
    "description": "イメージをPhotoshopで開く",
    "author": "Yukimi",
    "version": (0,3),
    "blender": (2, 6, 0),
    "location": "image",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Import-Export"}

import bpy
import os
import subprocess
import time
import random

#実行するjavascriptの名前
js_name = "img_open.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

def to_photoshop(context):
    #現在の.blendファイルのあるフォルダに一時ファイルを保存する場合は以下2行をコメントアウト
    #blendpath = bpy.data.filepath
    #tmp_dir  = os.path.join(os.path.split(blendpath)[0],"tmp")
    #日時+3桁のランダムアルファベットでファイル名の作成
    source_str = 'abcdefghijklmnopqrstuvwxyz'
    f_name = time.strftime("%y%m%d%H%M") + "".join([random.choice(source_str) for x in range(3)]) + ".png"
    img_path  = os.path.join(tmp_dir,f_name)
    #表示している画像の保存
    bpy.ops.image.save_as(save_as_render=False, filepath=img_path, relative_path=True)
    #スクリプトの実行
    subprocess.call(["CScript", VB_Hub, jscript, img_path, "//nologo"])
    

###################################################
class DupulicateActionAtCurrentTime(bpy.types.Operator):
    '''add Roop  '''
    bl_idname = "action.to_photoshop"
    bl_label = "open imege on Photoshop"
    def execute(self, context):
        to_photoshop(context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator("action.to_photoshop", 
        text="Photoshopで開く" )

def register():
    bpy.utils.register_module(__name__)
    bpy.types.IMAGE_MT_image.prepend(menu_func)

def unregister():
    bpy.utils.unregister_module(__name__)
    bpy.types.IMAGE_MT_image.remove(menu_func)

if __name__ == "__main__":
    register()
##########################################################
フォルダにあるファイルをモジュールとして読み込ませるのに必要なファイルです
通常はアドオンとして読み込ませるための設定と メニューに項目を登録する処理を記述しますが
今回は 本体のスクリプトも短いために こちらに記述しています

処理自体は前回の画像読み込みのスクリプトとほとんど同じですが
前回ではデスクトップに置いていた関連ファイルが同じフォルダ内にあるようにする処理と
表示している画像に日時+ランダムな文字列で Blenderのテンポラリフォルダに保存する処理が加えてあります


他のVB_Hub.vbs img_open.jsxは前回使ったものと同じですが書いておきます
VB_Hub.vbs
Option Explicit
'コマンドライン引数の取得
Dim oParam
Set oParam = WScript.Arguments
'Photoshopを指定
Dim objApp
Set objApp = CreateObject("Photoshop.Application")
Dim arg_count
arg_count = oParam.Count -1 

If arg_count = 0 Then
    '追加の引数がない場合
    objApp.DoJavaScriptFile oParam(0)
Else
    Dim id
 '引数をjavascriptに渡すための配列
    Dim ArgArray()
 ReDim ArgArray(arg_count - 1  )
    '第一引数がスクリプトのパスのため取り除く
    For id = 0 To arg_count - 1
        ArgArray(id) = oParam(id + 1)
    Next
    ''スクリプトの実行
    objApp.DoJavaScriptFile oParam(0),ArgArray
End If


var active_doc = app.activeDocument
//引数で指定したドキュメントを開いて現在のPSDに複製
if (arguments.length > 0){
    var img_file =  File(arguments[0])
    var img = app.open( img_file );
    img.artLayers[0].duplicate(active_doc)
    img.close()
    }
以上のファイルを保存したらフォルダごとアドオンのフォルダにコピーするか
フォルダをzip圧縮してBlenderの「ユーザー設定」の「アドオン」タブ下部にある「ファイルからインストール」をでzipファイルを読み込んで
アドオンを有効にします

正常に読み込まれた場合は
「UV/画像エディッター」の画像メニューに「Photoshopで開く」という項目が追加され
実行するとビュー上で表示している画像をPhotoshopの開いている画像に送ることができるようになります

今回のZipファイルをこちらにアップロードしておきます
いかがでしょうか

2016年11月10日木曜日

BlenderからPhotoshopを制御2-レンダリングした画像をPhotoshpで開く


前回はBlenderのPythonからJavaScriptを実行しましたが
今回はさらにPhotoshopにデータを受け渡すことを考えます。

Blenderのレンダリング画像を保存して、引数で保存した画像のファイルパスを渡しPhotoshopで開くものとします
今回もスクリプト等はデスクトップに置きます

VBHubに受け取った引数をそのままJavaScriptに受け渡す処理を追加します
VB_Hub.vbs
Option Explicit
'コマンドライン引数の取得
Dim oParam
Set oParam = WScript.Arguments
'Photoshopを指定
Dim objApp
Set objApp = CreateObject("Photoshop.Application")
Dim arg_count
arg_count = oParam.Count -1 

If arg_count = 0 Then
    '追加の引数がない場合
    objApp.DoJavaScriptFile oParam(0)
Else
    Dim id
 '引数をjavascriptに渡すための配列
    Dim ArgArray()
 ReDim ArgArray(arg_count - 1  )
    '第一引数がスクリプトのパスのため取り除く
    For id = 0 To arg_count - 1
        ArgArray(id) = oParam(id + 1)
    Next
    ''スクリプトの実行
    objApp.DoJavaScriptFile oParam(0),ArgArray
End If
VBscriptのWScript.Argumentsで引数を配列の形で受け取ります
配列の最初の項目はJavaScriptのパスになるので それ以降をJavaScriptに渡しています


次にJavaScriptです

Photoshopで画像を開くだけなら 実はJavaScriptを使わなくてもいいので
開いた画像を今開いている書類に複製する処理にします。
var active_doc = app.activeDocument
//引数で指定したドキュメントを開いて現在のPSDに複製
if (arguments.length > 0){
    var img_file =  File(arguments[0])
    var img = app.open( img_file );
    img.artLayers[0].duplicate(active_doc)
    img.close()
    }
引数で設定された値は arguments で受け取ることができ、
配列形式で値を取り出すことができます
ここではarguments[0]でファイルパスを受け取り 画像を開くし処理をしています

最後にBlenderのスクリプト
import bpy
import os
import subprocess

#デスクトップフォルダのパス
desktop_path = os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH") + "\\Desktop"
#実行するjavascriptのファイル名
js_name = "img_open.jsx"
#ファイルパスを実行するOSの形式で取得
js_path = os.path.normcase( os.path.join(desktop_path, js_name) )
VB_Hub = os.path.normcase( os.path.join(desktop_path, "VB_Hub.vbs") )
#画像を保存するパス
img_path  = os.path.join(desktop_path, "render_img.png")
#レンダリング画像を保存
bpy.data.images['Render Result'].save_render( filepath= img_path)
#VBScript経由でjavascriptを実行
subprocess.call([ "CScript",VB_Hub, js_path, img_path, "//nologo" ])
レンダリング画像をデスクトップに保存して JavaScriptのパスといっしょに保存したファイルのパスを渡しています。

実行前にPhotoshopには適当な書類を開いて置き Blenderでレンダリングで画像を生成しておいてください
スクリプトの実行で Potoshopにレンダリング画像がレイヤーとして読み込まれたでしょうか?

ここまでは説明のためにシンプルな動作のものを作成しましたが
次は実用的なようにBlenderのアドオンにしたものを紹介したいと思います

BlenderからPhotoshopを制御1-Blender-PyhtonとPhotoshop-JavaScriptについて


3Dソフトと Photoshop等と連携してより効率的に作業するためのメモ
同じようなやり方でMacOSXでもできますが 今回はWindows環境での話

3Dソフトのレンダリング結果をPhotoshopの画像処理に利用したり
UVテクスチャを作画したりしていると、よりシームレスに作業できればと感じることが多々あります。

PhotoshopはJavaScript等を使って色々な処理を追加できるので、
Blenderの自動処理言語でもあるPythonを使った連携を考えます

どうやってjavascriptを実行するのか?

Pythonではsubprocessモジュールを使えば
コマンドプロンプトで実行するように他のプログラムを実行させたりできます 

これでjavascriptをBlenderから直接実行できればいいのですが 色々と問題がありうまくいきません

そこで、一旦VBScriptの中継ソフトを介してしてJavaScriptを実行することにします
今回作成するのは実行したいJavaScriptのファイルパスをPhotoshopに受け渡すだけのシンプルなものです
この中継ソフトをVB_Hubと名づけることにします。
(実はPhotoshopはVBScriptでも操作ができるのですが、JavaScriptの方が情報が多かったり扱いやすいです)




今回はデスクトップに 中継ソフトの VB_Hub.vbs テスト用のJavaScriptのtest.jsx を置いて
それを実行してみます

デスクトップにVB_Hub.vbsとtest.jsxという名前でテキストファイルを作成して下記のコードをそれぞれに保存し
Photoshopを起動して空白の書類を開いておいてください

VB_Hub.vbs
Option Explicit
'コマンドライン引数の取得
Dim oParam
Set oParam = WScript.Arguments
'実行するアプリケーションとしてPhotoshopを指定
Dim objApp
Set objApp = CreateObject("Photoshop.Application")

''スクリプトの実行
objApp.DoJavaScriptFile oParam(0)
var active_doc = activeDocument
//レイヤの作成
active_doc.artLayers.add()
JavaScriptはPhotoshopで開いている書類に新しいレイヤを作る簡単なものです

最後にBlenderのテキストエディッタに下記コードをコピペしてスクリプト実行をします
import subprocess
import os
#デスクトップフォルダのパス
desktop_path = os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH") + "\\Desktop"
#実行するjavascriptのファイル名
js_name = "test.jsx"
#ファイルパスを実行するOSの形式で取得
js_path = os.path.normcase( os.path.join(desktop_path,js_name) )
VB_Hub = os.path.normcase( os.path.join(desktop_path,"VB_Hub.vbs") )
#VBScript経由でjavascriptを実行
subprocess.call([ "CScript",VB_Hub, js_path, "//nologo" ])
うまく新しいレイヤが作成されたでしょうか

今回は単純にJavaScriptを実行しただけですが
BlenderのPyhtonから情報を引数で受け渡して処理をかえることもできます
それは今後の記事で書いていきたいと思います

2016年10月18日火曜日

Sketchfab設定メモ

他にもブログを作っているけど、Blender関連の話題を分けたいので立ち上げました

SketchfabというWeb上の3Dビューアーサイトがあります

そちらに期間を決めてデータあげる企画があって
事前のリサーチにBlenderのデータをアップロードしてみたのでメモ



今更ですが、動きのついた3DデータがWebブラウザで見れるというのはいいものですね

さて、SketchfabはBlenderのファイルに対応していて
基本的にファイルを選択してアップロードするだけなので
英語サイトとはいえ 簡単なものなのですが
他の日本語で解説されてる方も含めて躓いているポイントがいくつかあって

  • テクスチャ画像
  • モデファイア
  • 動きのループ範囲
  • NLAエディッタやアクションとの関係


このこの辺りについて少し書いてみようかと思います

テクスチャ画像

Blenderのテクスチャ画像は通常は外部参照ファイルのなので
そのまま.blendファイルをアップロードするとテクスチャが剥がれてしまいます

「ファイル>外部データ>すべて.blendにパック」を実行すると
.blendファイル内に画像データが保存されます
また UV/画像エディッタ の 「画像>画像をパック」 でも同様に
表示中の画像をファイル内に保存されるようです。

添付画像中の パックの項目に下にある 「すべてを相対パスに」 と併せて
他のマシンにファイルを移動したい時などにも有効な方法ですね

モデファイア

モデリング途中にいくつかモデファイアを併用して
表示の 使用/不使用を切り替えているのですが
Sketchfabではモデファイアが残った状態だと全て適応された状態になるようです
上の画像では 指定した頂点グループを消すことができるマスクと
細分割のモデファイアがありますが
利用のチェックが外れた状態でも Sketchfab上ではこの適応状態での表示になりました

動きのループ範囲

Blenderでは 選択したオレンジの範囲でループするように設定してモーションを作成しました


ところがSketchfabでは全てのキーフレーム(画像で選択していない白色のキーフレーム)
の範囲でループすることになるようです。

NLAエディッタとアクション

上記の問題からNLAエディッタを使ってみることにしました

キーフレームアニメーションの一部をトリミングしたり 複数のアニメーションを合成したりできる仕組みで
UnrealEngeneへモーションデータを範囲付きで読み込ませるのに
NLAエディッタの範囲指定を使うようですが 逆に全くモーションが読み込まれなくなりました

色々試した結果
NLAエディッタには ストリップが一切ない状態にしないといけないようです
つまりループさせたい場合は ループ部分と始端と終端にキーフレームを打たないといけないようですね


NLAでのアクションのトリミングや重ね合わせはできない代わりに
複数のアクションをファイルに入れて Sketchfabのビューアー上で切り替えることはできるようです

今回はモーション中心の検証でしたが
スペキュラーやノーマル 発光といったテクスチャをビューアー上で設定できるようですし
カラーバランスやブルームといったポストエフェクトも色々あるようなので色々試していきたいところです