2018年7月30日 星期一

從Blender裡萃取模組資料(3)

從Blender裡萃取模組資料(3)

前言

  續前篇從Blender裡萃取模組資料(2),這次說明物件的Hierarchy如何萃取,在此做個紀錄。

內容
  在Blender設定Hierarchy有3種常用的方法,一個是直接設定物件的Relations的Parent,如下圖
設定Relations的Parent
這個方法我認為是最好的方式,因為這個方法支援當Parent是Bone的狀況,第二種是"Set Parent To",用法很直覺,選取Child物件後再押住"Shift"選取Parent物件,然後按下Ctrl+p,選取需要的Parent選項即可,這方法很直覺而好用,還可以用來綁骨架,但這個方法有個缺陷,就是無法將物件的Parent設定到Bone,操作狀況如下圖
使用"Set Parent To"
接著是第三種方法,使用"Child Of Constraint",操作狀況如下圖
使用"Child Of Constraint"
這個方法雖然也可以達成Hierarchy效果,但有些相當棘手的問題,問題一,在操作狀況圖片可以看到物件的Hierarchy並沒有真正的改變,這會造成匯出模組時有些物件(Child Of Constraint的Parent物件)必須先匯出,問題二,這個方法並不會防止循環Hierarchy或是2個以上的Parent,如果先用第一或第二種方法設定Hierarchy為A物件的Child為B物件,B物件的Child為C物件,接著利用
"Child Of Constraint"將C物件的Parent設成A物件,造成循環Hierarchy且C物件具有2個Parent,Blender並不會阻止這件事的發生,所以這第三種方法就不打算支援了。
  接著就來看看由第一與第二種設定Hierarchy的資料如何萃取,萃取的範例如下
tagObj = bpy.data.objects["Cube"]
#
for obj in tagObj.children:
  print("child name:",obj.name)
#
if obj.parent != Nome:
  if obj.parent_type=='OBJECT':
    print("parent name:",obj.parent.name)
  elif obj.parent_type=='ARMATURE':
    print("parent name:",obj.parent.name)
  elif obj.parent_type=='Bone':  
    print("parent name:",obj.parent.name," bone name:",parent_bone)

只要存取物件的"Children"就可以得到該物件的Child物件,然後可以透過"Parent"來得到Parent物件,唯一要記住的特例是當物件的Parent是Bone的狀況,實際狀況會到萃取骨架資料時再詳細說明。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(2)
從Blender裡萃取模組資料(4)

2018年7月23日 星期一

從Blender裡萃取模組資料(2)

從Blender裡萃取模組資料(2)

前言

  續前篇從Blender裡萃取模組資料(1),這次會說明物件Transform的資料如何萃取,在此做個紀錄。

內容

  萃取Transform的Position與Scale相當容易,直接從object的Property取得即可,但Rotation的部分就不能直接取得了,相看下圖
Blender的旋轉模式
可以在編輯器看到旋轉模式的選項,在不同的旋轉模式要取不同的Property才能正確取到值,如下說明:
1.  Quaternion(WXYZ)模式下,取roation_quaternion
2.  XYZ Euler模式下,取roation_euler
3.  XZY Euler模式下,取roation_euler
4.  YXZ Euler模式下,取roation_euler
5.  YZX Euler模式下,取roation_euler
6.  ZXY Euler模式下,取roation_euler
7.  ZYX Euler模式下,取roation_euler
8.  Axis Angle模式下,取roation_axis_angle

旋轉模式在編輯器的預設值是"XYZ Euler",以我的需求來說,我會希望它是"Quaternion(WXYZ)",所以該怎麼轉換呢?只要將現在的旋轉模式轉到你想要的模式後,再去取對應的Property即可,整個Transform的資料萃取範例如下

import bpy

tagObj = bpy.data.objects["Cube"]
#
print('Object postion:',tagObj.location)

if obj.rotation_mode == 'QUATERNION':
  print('Object ratation(quaternion):',tagObj .rotation_quaternion)
else:
  #save old ratation mode for recover it.
  oldRotationMode = obj.rotation_mode
  #
  obj.rotation_mode = 'QUATERNION'
  print('Object ratation(quaternion):',tagObj .rotation_quaternion)
  #Recover ratation mode
  obj.rotation_mode = oldRotationMode 

#
print('Object scale:',tagObj.scale)

在取得非"Quaternion"時要把舊的模式記住,這只是為了恢復原本的操作模式,如果不介意旋轉模式被更改的話可以直接設成想要模式後取Property即可。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(1)
從Blender裡萃取模組資料(3)

2018年7月16日 星期一

從Blender裡萃取模組資料(1)

從Blender裡萃取模組資料(1)

前言

  從Blender裡匯出模型的完整範例很難搜尋到,都只能搜尋到片段的資料會出範例,所以這裡做個紀錄,由於目前並未研究透徹,只會萃取"位址"、"Normal"與"Index"的部分。

內容

  在之前的那一篇初探Blender的script裡有一個輸出的範例,那個範例輸出的資料恐怕是有問題的,如果用該範例輸出一個Cube的話,會得到36個vertex與36個index,這個和一般在Direcct3D與OpenGL的範例不一樣,Direcct3D與OpenGL的範例是24個vertex與36個index,其中的vertex是有Face的概念的,而不是將全部的Triangle一個一個輸出。
  這裡說一下輸出格式的問題,由於每個引擎有自己的格式,所以範例的用法並會包含輸出的格式,而是著重在從Blender裡將資料萃取出來,至於輸出格式的部分,相信並不困難,這裡就不記錄了。
  接著,就來看萃取的範例
import bpy
import bmesh

tagMesh = bpy.data.meshes["Cube"]
#Create bmesh from mesh
tagBMesh = bmesh.new()
tagBMesh.from_mesh(tagMesh)

#Start extract
baseIndex = 0
for face in tagBMesh.faces:
  for i in range(len(face.loops) ):
    print("Position:",face.loops[i].vert.co," ",end="")
    print("Normal:",face.normal," ",end="")
  print("Loop vertex end")
  loopLen = len(face.loops)
  for i in range( len(face.loops) - 2):
    print("index0:",baseIndex," ",end="")
    print("index1:",baseIndex + i + 1," ",end="")
    print("index2:",baseIndex + i + 2," ",end="")
  print("Loop index end")
  
#Free bmesh...
tagBMesh.free()

範例會去取"Cube"這個Mesh,如果取其它的Mesh的話,更換名稱即可。接著,會去從每個face裡取出loop的資料,這裡如果以"Cube"來說的話,loop就代表了"Cube"的面,如果用len(face.loops)取得的數值會是4,再依據每個loop的索引取得vertex的位址。Normal的部分可以直接從face拿到。接著會依據loop來輸出Index,每個面要輸出的Index應該有6個(兩個Triangle),輸出的過程如下圖
Index的輸出順序
至於BaseIndex是作為目前已第幾個Index為基準輸出Index,這樣就可以輸出6個Index了。
  輸出資料的時候要注意一些事,像是vertex的數值如果要取值時要經過round(),具體的範例如下
tagX = round(vert.co.x,3)
tagY = round(vert.co.y,3)
tagZ = round(vert.co.z,3)

取完後,才會是float的數值,原本的數值是double的。還有要注意Blender的坐標系是"右手坐標系",Index的順序是"順時針",如果需要轉換的話請自行轉換。

參考資料

Blender Documentation Contents

相關文章

從Blender裡萃取模組資料(2)

2018年7月9日 星期一

在Win32視窗程式中開啟console視窗

在Win32視窗程式中開啟console視窗

前言

  用Win32視窗程式寫出來的測試程式一直以來有個麻煩,就是切換測試項目(如測試光、測試影子...等)時會需要終止程式後,修改部分的code後再run一次,如果能有個debug的工具可以在不關閉程式的狀況下修改程式特定的參數不是很好嗎?可是如果自己了視窗的控件再交由C++來控制的話,成本實在有點高,如果有個console視窗可以輸入命令和輸出結果就可以解決上述的問題。在此把學習的過程做個紀錄。

內容

  使用的示範如下
  //Open console...
  AllocConsole();
  std::wstring strW = L"Dev Console";
  SetConsoleTitle( strW.c_str() );
  DrawMenuBar(GetConsoleWindow());
  //
  //call ReadConsole() or WriteConsole() to control console
  //...
  //Close console...
  FreeConsole();

開啟console視窗後,用ReadConsole()與WriteConsole()來控制輸入與輸出,但這兩個funciton的參數不是很用,如果可以將輸出入的結果導到stdin與stdout的話,就可以直接使用C++的IO function(scanf、printf...等),這樣用起來比較直接也比較好懂,所以導到stdin與stdout的範例如下
  //Open console...
  AllocConsole();
  std::wstring strW = L"Dev Console";
  SetConsoleTitle( strW.c_str() );
  DrawMenuBar(GetConsoleWindow());
  //Redirection IO to stdin and stdout 
  freopen( "CON", "r", stdin ) ;
  freopen( "CON", "w", stdout ) ;
  //call c++ IO function to control console
  //...
  //Close console...
  FreeConsole();

上述的範例如果要搭配Win32視窗程式使用的話,需要自己產生一個thread去控制輸入,為什麼呢?因為IO的輸入是block funciton,如果使用主thread去取得IO的輸入的話,整個程式都會停頓下來,這點請務必注意!
  接著還有一個麻煩是要將控制輸入的thread停下來的問題,當企圖停下一個thread時的標準做法是用WaitForSingleObject()來等待結束,可是scanf()或std::cin並不會自己中斷等待,所以需要給"假的"輸入來中斷IO的等待,範例如下

  //abort console inpurt
  HWND hWndConsole=GetConsoleWindow();
  INPUT ip[2];
  ip[0].type = INPUT_KEYBOARD;
  ip[0].ki.wVk = 0x41;;
  ip[0].ki.wScan = 0;
  ip[0].ki.dwFlags = 0;
  ip[0].ki.time = 0;
  ip[0].ki.dwExtraInfo = 0;
  ip[1].type = INPUT_KEYBOARD;
  ip[1].ki.wVk = VK_RETURN;
  ip[1].ki.wScan = 0;
  ip[1].ki.dwFlags = 0;
  ip[1].ki.time = 0;
  ip[1].ki.dwExtraInfo = 0;
  SendInput(2, ip, sizeof(INPUT) );

在範例中是輸入"a"和"enter"去中止console的blocking,要注意指輸入一個"enter"是無法中止blocking,必須要有一個有效的字元!

後記

  如果有看微軟的console使用說明Consoles的話,會發現還有其他的控制function可以使用,但由於本次的需求不需要用到所以都沒提到,有機會再來研究研究。

參考資料

Creation of a Console
Create window console inside main win32 window
Consoles

2018年7月2日 星期一

用Python寫Binary資料

用Python寫Binary資料

前言

  在之前的在Python中使用json,說到了要把Blender匯出的模組輸出成JSON,但如果要匯出模組到C++的話,就勢必要有parse JSON的Library,由於目前不打算引入JSON的Library,所以就想直接使用Python輸出Binary資料,在此把學習過程做個紀錄。

內容

  在學會寫Binary資料要先學會開Binary的檔案,範例如下
import os

f = open("testBinaryData.bin","wb+")
f.write(b"\x01\x02\x03\x04")
f.close()

開啟與寫入的方式和C++是相像的,但是如果要寫一個struct的資料的話,會發現只有write()是不夠用的,如果要寫入像是int、float、double...等資料時會需要一個可以把C++的基本資料型態轉成Python的Bytes這個資料型態的過程,而這個過程就是struct模組。

  接著來看struct模組的使用範例,如下
import struct

structFMT = "iIfd";
packData = struct.pack(structFMT,-1,123,3.14,-3.14 )
print("Pack data:", packData)
print("Pack data type:", type(packData) )
unpackData = struct.unpack(structFMT, packData)
print("Unpack data:", unpackData)
print("Unpack data type:", type(unpackData) )

#output
# Pack data: b'\xff\xff\xff\xff{\x00\x00\x00\xc3\xf5H@\x00\x00\x00\x00\x1f\x85\xebQ\xb8\x1e\t\xc0'
# Pack data type: <class bytes="">
# Unpack data: (-1, 123, 3.140000104904175, -3.14)
# Unpack data type: <class tuple="">

pack()是將資料編成Bytes,unpack()則是將Bytes資料轉回tuple。在這個範例中,pack()與unpack()的第一個參數都是format string,代表資料的格式,目前的格式依序如下
1.int(32位元)
2.unsigned int(32位元)
3.float
4.double
利用format string就可以自己規劃出自己的struct格式,如果想知道format string的詳細資料的話,可以到Interpret strings as packed binary data來了解。在撰寫時擔心float與double這兩個資料型態的Binary格式能不能被C++直接讀取,所以我寫了個C++的程式碼來測試,如下
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "conio.h"
struct STestStruct
{
  long inum;
  unsigned long uinum;
  float fnum;
  double dnum;
};
int main()
{
  STestStruct data;
  FILE* f= fopen("testBinaryData.bin","rb");
  fread(&data,sizeof(STestStruct),1,f);
  printf("%d\n",data.inum);
  printf("%d\n",data.uinum);
  printf("%f\n",data.fnum);
  printf("%LF\n",data.dnum);
  fclose(f);
  system("PAUSE");
  return 0;
}
//output
//-1
//123
//3.140000
//-3.140000

結果非常好,遽然不需要任何轉換就可以讀回來,看來是我多慮了。

參考資料

Interpret strings as packed binary data

2018年6月25日 星期一

在Python中使用json

在Python中使用JSON

前言

  由於要在Blender內寫匯出模組的Addon,加上希望輸出的格式是JSON,所以必須要學著在Python裡使用JSON,在此做個紀錄。

內容

  整體來說,使用的過程很簡單,但由於我並不熟Python的語言特性,所以造成我花了些時間才學起來怎麼用。
  如果搜尋過教學範例,會感覺用起來跟Javascript用JSON差不多,但由於不熟Python的語言特性,所以花了些時間在這裡。先看看以下的程式碼
var a = {};

a.propertyA = "123";
a.propertyB = 456;
console.log(JSON.stringify(a) );

這是在Javascript使用JSON的用法,如果直接翻譯成Python,會得到以下的程式碼
import json
a = new object()

a.propertyA = "123"
a.propertyB = 456
print(json.dumps(a) )

執行後會發現Python的object並不能動態增加property!後來查了些資料發現Python裡的JSON並不是直接把object直接轉成JSON的object,而是dict!所以程式碼會被變成以下
import json
a = {} #This object's type is "dict"

a["propertyA"] = "123"
a["propertyB"] = 456
print(json.dumps(a) )

這樣就可以動態的增加property,而資料型態的對應可以在Python JSON裡找到說明。
  將JSON字串轉到Python資料的範例如下
import json

a = "{'propertyA':'123', 'propertyB': 456}"
print( json.loads(a) )

用起來和Python資料轉到JSON字串的方式是一樣的。

參考資料

Python JSON

2018年6月18日 星期一

在Blender的script裡使用FileBrowser選取檔案

在Blender的script裡使用FileBrowser選取檔案

前言

  在上一篇初探Blender的script中,基本的使用了Blender的Script,因應日後要開發匯入/匯出3D模組的外掛,就來研究怎麼在Script裡使用FileBrowser選取檔案,在此做個紀錄。

內容

  在開始研究如何使用FileBrowser時,發現找不太到範例,在搜尋一陣子後才在Use Blender's file browser via python, without a UI panel裡發現原來Blender有自帶範例,開啟了方式如下
開啟範例
在開啟範例後,會看到以下的程式碼
import bpy


def write_some_data(context, filepath, use_some_setting):
    print("running write_some_data...")
    f = open(filepath, 'w', encoding='utf-8')
    f.write("Hello World %s" % use_some_setting)
    f.close()

    return {'FINISHED'}


# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator


class ExportSomeData(Operator, ExportHelper):
    """This appears in the tooltip of the operator and in the generated docs"""
    bl_idname = "export_test.some_data"  # important since its how bpy.ops.import_test.some_data is constructed
    bl_label = "Export Some Data"

    # ExportHelper mixin class uses this
    filename_ext = ".txt"

    filter_glob = StringProperty(
            default="*.txt",
            options={'HIDDEN'},
            maxlen=255,  # Max internal buffer length, longer would be clamped.
            )

    # List of operator properties, the attributes will be assigned
    # to the class instance from the operator settings before calling.
    use_setting = BoolProperty(
            name="Example Boolean",
            description="Example Tooltip",
            default=True,
            )

    type = EnumProperty(
            name="Example Enum",
            description="Choose between two items",
            items=(('OPT_A', "First Option", "Description one"),
                   ('OPT_B', "Second Option", "Description two")),
            default='OPT_A',
            )

    def execute(self, context):
        return write_some_data(context, self.filepath, self.use_setting)


# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
    self.layout.operator(ExportSomeData.bl_idname, text="Text Export Operator")


def register():
    bpy.utils.register_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.append(menu_func_export)


def unregister():
    bpy.utils.unregister_class(ExportSomeData)
    bpy.types.INFO_MT_file_export.remove(menu_func_export)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.export_test.some_data('INVOKE_DEFAULT')

在第一次看時實在是看不太懂,所以我找了另一個教學Blender 3D: Noob to Pro/Advanced Tutorials/Blender Scripting/A User Interface For Your Addon,這個教學裡會教你用Script寫Operator,並且製作UI來啟動Operator,具體的程式碼如下
import math
import bpy
import mathutils

class TetrahedronMakerPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_context = "objectmode"
    bl_category = "Create"
    bl_label = "Add Tetrahedron"

    def draw(self, context):
        TheCol = self.layout.column(align=True)
        TheCol.operator("mesh.make_tetrahedron", text="Add Tetrahedron")
    #end draw

#end TetrahedronMakerPanel

class MakeTetrahedron(bpy.types.Operator):
    bl_idname = "mesh.make_tetrahedron"
    bl_label = "Add Tetrahedron"
    bl_options = {"UNDO"}

    def invoke(self, context, event):
        Vertices = \
          [
            mathutils.Vector((0, -1 / math.sqrt(3),0)),
            mathutils.Vector((0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((-0.5, 1 / (2 * math.sqrt(3)), 0)),
            mathutils.Vector((0, 0, math.sqrt(2 / 3))),
          ]
        NewMesh = bpy.data.meshes.new("Tetrahedron")
        NewMesh.from_pydata \
          (
            Vertices,
            [],
            [[0, 1, 2], [0, 1, 3], [1, 2, 3], [2, 0, 3]]
          )
        NewMesh.update()
        NewObj = bpy.data.objects.new("Tetrahedron", NewMesh)
        context.scene.objects.link(NewObj)
        return {"FINISHED"}
    #end invoke

#end MakeTetrahedron

bpy.utils.register_class(MakeTetrahedron)
bpy.utils.register_class(TetrahedronMakerPanel)

可以看到是Panel與Operator,TetrahedronMakerPanel是UI的部分,MakeTetrahedron則是Operator,接著最後透過register來註冊後使用。TetrahedronMakerPanel透過繼承Panel後再修改相關Property,接著透過Draw來新增一個按鍵來驅動Operator。bl_space_type是Editor的Type,在這個範例是註冊在3DView的Editor,bl_context代表的是在ObjectMode才會出現,bl_category 則是標籤的名稱,如果是不存在的標籤會自動新增,bl_label則是Panel的顯示名稱。接著是 MakeTetrahedron,透過繼承Operator後,修改相關的Property,接著透過invoke來執行程式碼。bl_idname是Operator要註冊的名稱,記住不要大寫! bl_label則是Operator的註解要顯示的字串。從這兩個例子可以看出Blender寫外掛時的模式是透過繼承後修改相關Property並重仔相關Method後,再透過register後使用。
  回到輸出檔案的範例, 可以看到ExportSomeData繼承Panel與ExportHelper,透過修改相關Property後,重載execute(),但這裡的重載Method的執行時機不太一樣,這個Method會在File browser裡的"Export Some Data"按下時才執行,如果是按下"Cancel"的話就不會被執行。在execute()裡可以在self.filepath這個Property取得儲存檔案的位置。write_some_data()這裡就是Python的基本開檔寫資料。在最後可以看到bpy.ops.export_test.some_data('INVOKE_DEFAULT')這行,這行是直接執行Operator的做法,不用透過UI驅動,可以學起來。

後記

  雖然第一次看到Blender的範例看不懂,但在經過學習後發現這個做法的程式碼相當簡潔,可真是學習了,本篇文章只看了export的範例,import的範例就不再寫了,因為作法是相像的。

參考資料

Use Blender's file browser via python, without a UI panel
Blender 3D: Noob to Pro/Advanced Tutorials/Blender Scripting/A User Interface For Your Addon