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

2018年6月11日 星期一

初探Blender的script

初探Blender的script

前言

  遊戲引擎在最初的實作都是直接輸入頂點的資訊來達成的,但這可不是長久之計,如果所需要的模型較為複雜,通常採用能儲存3D模型的檔案格式(如OBJ、FBX...等),然後在製作相對應的parser在引擎端來完成模型匯入,但目前這些檔案格式都多少有一些缺陷,普遍的缺陷是無法記錄多個動畫,而FBX雖然可以支援多個動畫,但該格式常有前後版本不相容的麻煩,所以就想要自己匯出資料,而手邊沒有常見的3dsMAX與Maya,只有Blender,所以就來研究Blender的script。

內容

  在Blender要使用script時,請先開啟輸出的console,這樣可以方便看script輸出的結果,可以方式如下圖
開啟輸出的console

接著開啟Text Editor輸入程式碼後,再按下"Run Script"來執行script,如下圖
執行script
有一點要注意,這個Text Editor在要輸入時,滑鼠座標必須要在該視窗上!和一般點擊後focus該視窗的使用習慣不同。
  script的程式語言是Python,如果要查詢class的說明的話可以到Blender Documentation Contents,接著執行以下的程式碼
import bpy
import os
import bmesh
os.system("cls")
tagMesh = bpy.data.meshes["Cube"]
for i in range( len(tagMesh.vertices) ):
    print(tagMesh.vertices[i].co)

程式碼裡的bpy.data.mesh["Cube"],指的是要拿取哪一個Mesh的資料,請依據狀況修改。執行後可以在console裡得到該Mesh所有vertex的位址。看起來很簡單對吧!但是Index buffer的資料就沒這麼簡單了,如果你查詢該型別的資料,可能會認為這個資訊可能會儲存於"tagMesh.polygons",雖然可查到哪幾個Index可以形成polygon,但卻不知道triangle的index順序,如果沒有這個順序就沒辦法順利產生Index buffer的資料,所以請用以下的程式碼
import os
import bmesh
os.system("cls")
tagMesh = bpy.data.meshes["Cube"]
tagBMesh = bmesh.new()
tagBMesh.from_mesh(tagMesh)
bmesh.ops.triangulate(tagBMesh, faces=tagBMesh.faces[:], quad_method=0, ngon_method=0)
for face in tagBMesh.faces:
    for i in range(len(face.loops) ):
        print(face.loops[i].vert.index," ",end="")
    print("\n")
tagBMesh.free()

可以看到要先把Mesh轉成BMesh,轉完後,在經過triangulate()後,會將所有的polygon輸出成triangle,再從tagBMesh.faces裡取得每個triangle的index,並注意取得的triangle順序是"逆時針"。

後記

  本篇只有基本的取得vertex與index的資料,這些資料顯然是不夠的,其他像是如何寫檔與將script寫成addon,又或是取的其它的vertex資訊就日後再研究了。

參考內容

Blender Documentation Contents
triangulate mesh in python

2018年6月4日 星期一

初探瀏覽器的audio

初探瀏覽器的audio

前言

  最近想要在引擎裡加入聲音相關的功能,但以前卻從來沒有在瀏覽器寫過聲音相關的功能,在此將學習的過程做個紀錄。

內容

  在瀏覽器要撥放聲音Web Audio APIAudio element,API的部分用起來比較複雜,但可以控制較多特性,element的部分用起比較簡單,但能控制的特性就比較有限。最後我選了element,因為簡單而且工能夠用。
  使用Audio element時,和使用Image element時很像,以下是範例
var audio=new audio();
var dataLoadEnd = false;
audio.onloaded = function(){
  console.log("Load end!");
  dataLoadEnd = true;
}
function playAudio(){
  if(!dataLoadEnd)
    return;
  audio.play();
} 
function pauseAudio(){
  if(!dataLoadEnd)
    return;
  audio.pause();
}
function replayAudio(){
  if(!dataLoadEnd)
    return;
  if(!audio.paused)
    audio.pause();
  audio.currentTime = 0;
  audio.play();
}

使用play()來啟動撥放,用pause()控制停止,如果需要重新撥放的話先pause(),再把"currentTime"這個property設成0,接著再play()即可。每個Audio element可以視為一個音軌,不會有同時只能撥放一個之類的問題,在實現聲音引擎的多音軌可說是相當方便。還有一些時用的property,像是"loop",可以控制循環撥放,還有"volume",用來控制音軌的音量。

參考資料

Audio element
Web Audio API