python可擴展的Blender 3D插件開發(fā)匯總
Blender 3D 插件
成熟的 Blender 3D 插件是令人驚奇的事情。作為 Python 和 Blender 的新手,我經(jīng)常發(fā)現(xiàn)自己被社區(qū)中的人們創(chuàng)造的強大的東西弄得目瞪口呆。坦率地說,其中一些包看起來有點神奇,當自我懷疑或冒名頂替綜合癥的嘮叨聲音被打破時,很容易想到“如果有人能做出可以做xxx的東西就好了” 。
然后我記得,通過將好奇心和固執(zhí)與良好的文檔相結(jié)合,某人可以是任何人,X可以成為X、Y 和 Z。即使是困難的部分也可以弄清楚——尤其是因為所有固執(zhí)和好奇的人確保 Blender 的 Python文檔和stackexchange與它讓我們創(chuàng)建的 gee whiz 圖形一樣好。
同樣的方式,已經(jīng)存在的文檔和模型為從頭開始編寫 Blender 插件奠定了平滑的基礎(chǔ),在開始時為該插件提供可擴展的結(jié)構(gòu)有助于展示 Python API 的各個部分如何更多地組合在一起清楚地。換句話說,它使新編寫的代碼比原來更好,同時也使現(xiàn)有代碼更容易學習。
在本文的最后,我們將創(chuàng)建一個功能齊全且已安裝的插件,它提供了一個自定義 UI 元素來將Standoff 添加到 Blender 場景中,并帶有界面控件來調(diào)整創(chuàng)建的網(wǎng)格的直徑和高度。
一、文件結(jié)構(gòu)
本文末尾將存在的完整目錄和文件結(jié)構(gòu),我們可以使用mkdir和touch方式進行創(chuàng)建,本示例是一個填空游戲。我正在調(diào)用項目DemoRack并將其設(shè)置為我用于 Python 項目的文件夾中的頂級目錄名稱:它不一定必須是 Blender 特定的任何地方。下面是DemoRack項目的文件結(jié)構(gòu):
DemoRack |-- README.md |-- DemoRack.zip <-- will (re)compile via 'zip -r DemoRack.zip src' |-- src |-- |-- __init__.py |-- |-- standoff_mesh.py <-- from Part 2, not modified in this post |-- |-- standoff_operator.py |-- |-- standoff_panel.py |-- |-- standoff_props.py
下面我們簡單介紹下這些文件的作用:
- DemoRack.zip:已編譯src,安裝在 Blender 中的文件。
- __init__.py:為附加組件注冊所有必要的信息和類。
- standoff_mesh.py:用于生成目標幾何/網(wǎng)格數(shù)據(jù)的模塊。
- standoff_operator.py:將提供給 UI 使用的“do-er”。
- standoff_panel.py:在 UI 元素上添加插件將......添加。
- standoff_props.py:定義Panel 和 Operator所需的數(shù)據(jù)對象
簡而言之,每個新standoff_模塊都將包含register()和unregister()函數(shù)。該__init__模塊將導入這些模塊,并將兩種類型的函數(shù)捆綁到單個迭代器中。Blender Python 文檔描述了這些函數(shù)所扮演的角色:
二、__init__.py
有了這個背景,并且因為__init__模塊中的大部分代碼都與sys和importlib包有關(guān),所以我將在此處包含要點,而不會試圖去描述 Python 模塊導入的雜草。Blender插件需要注意的具體事項是module_names列表,聲明要引入register和unregister函數(shù)的的文件名,以及打開的bl_info字典。如官方插件介紹教程中所述,bl_info包含將在“首選項”窗格中找到的所有信息:
bl_info是包含附加元數(shù)據(jù)的字典,例如要顯示在首選項附加列表中的標題、版本和作者。它還指定了運行腳本所需的最低 Blender 版本;舊版本不會在列表中顯示加載項。
下面是一段示例代碼:
bl_info = { "name": "DemoRack", "description": "Make Mini Rack Units Dynamically", "author": "Jim O'Connor <hello@ocommaj.com>", "version": (0, 0, 1), "blender": (2, 90, 1), "category": "3D View" } module_names = [ 'standoff_props', 'standoff_operator', 'standoff_panel' ] import sys import importlib module_full_names = [ f"{__name__}.{module}" for module in module_names ] for module in module_full_names: if module in sys.modules: importlib.reload(sys.modules[module]) else: locals()[module] = importlib.import_module(module) setattr(locals()[module], 'module_names', module_full_names) def register(): for module in module_full_names: if module in sys.modules: if hasattr(sys.modules[module], 'register'): sys.modules[module].register() def unregister(): for module in module_full_names: if module in sys.modules: if hasattr(sys.modules[module], 'unregister'): sys.modules[module].unregister()
三、standoff_props.py
該模塊將是其中涉及最多的模塊,但它也為其他模塊提供了主干,并實現(xiàn)了一種可以廣泛重用的模式。它依賴于導入用于將一組屬性定義捆綁在一起的PropertyGroup類型(文檔),bpy.props。一旦PropertyGroup在 Blender 中注冊,它就在 Python 可腳本化數(shù)據(jù)對象的指針和底層 C 分配的內(nèi)存之間提供了一座橋梁,這些內(nèi)存完成了 Blender 的繁重工作。
我們需要在standoff_props.py類中將定義、繼承bpy.types.PropertyGroup并跟蹤 3 個屬性:
- metric_diameter: FloatProperty(**kwargs)
- height: FloatProperty(**kwargs)
- mesh: PointerProperty(type=Mesh)
其中,前兩個應(yīng)該是不言自明的,并且在實現(xiàn)中將有更多關(guān)于參數(shù)的細節(jié)。第3個PointerProperty指向內(nèi)存中的一個對象,并要求在定義時指定該對象的類型,并且它是 PropertyGroup的子類或bpy.struct.ID(即 Mesh)。這意味著任何將值設(shè)置為任何其他數(shù)據(jù)類型的實例的嘗試(在這種情況下,任何非 bpy.types.Mesh)都將引發(fā)錯誤,任何將值傳遞給期望任何其他數(shù)據(jù)的參數(shù)的嘗試也是如此類型。
在這種情況下,mesh的 PointerProperty屬性將用于保存在Standoff.mesh()的返回值,并使用存儲在metric_diameter和height后面的值進行實例化和修改。這三個屬性的完整定義如下所示:
class PG_Standoff(PropertyGroup): metric_diameter: FloatProperty( name="Inner Diameter (Metric)", min=2, max=5, step=50, precision=1, set=prop_methods("SET", "metric_diameter"), get=prop_methods("GET", "metric_diameter"), update=prop_methods("UPDATE")) height: FloatProperty( name="Standoff Height", min=2, max=6, step=25, precision=2, set=prop_methods("SET", "height"), get=prop_methods("GET", "height"), update=prop_methods("UPDATE")) mesh: PointerProperty(type=Mesh)
單獨的set 、 get和update參數(shù)都指向prop_methods函數(shù)的返回值。這些值必須是函數(shù),參數(shù)分別為(self, value)、(self)和(self, context)。這個閉包工廠可能看起來額外復雜,但它會顯著減少重復,并為與PropertyGroup 的數(shù)據(jù)屬性交互提供更大的靈活性。
要理解的一個重要區(qū)別是,每當屬性更改時都會調(diào)用update函數(shù)——它不是作為更新定義其屬性的一種方式來調(diào)用的。相反,它提供了一種將特定屬性的更改傳達給程序的其他部分的方法;這必須小心使用以避免副作用,并且因為沒有檢查來避免無限遞歸。
另一件需要注意的是,使用set和get函數(shù)意味著任何default值都必須通過顯式set調(diào)用(而不是 kwarg)來設(shè)置,但這也提供了在必要時掛鉤on_load方法的機會。最后,prop_methods函數(shù)必須在調(diào)用它的任何PropertyGroup類之前定義。
prop_methods函數(shù)的骨架代碼如下所示:
def prop_methods(call, prop=None): def getter(self): # getter function must check if prop attr has a value yet # if no value, will throw error, so must set default # can hook on load here # and either way, return self[prop] value def setter(self, value): self[prop] = value def updater(self, context): self.update(context) methods = { "GET": getter, "SET": setter, "UPDATE": updater } return methods[call]
完整的實現(xiàn)如下所示:
def prop_methods(call, prop=None): def getter(self): try: value = self[prop] except: set_default = prop_methods("SET", prop) set_default(self, self.defaults[prop]) if hasattr(self, "on_load"): self.on_load() value = self[prop] finally: return value def setter(self, value): self[prop] = value def updater(self, context): self.update(context) methods = { "GET": getter, "SET": setter, "UPDATE": updater, } return methods[call]
為此,任何具有調(diào)用prop_methods屬性的類都需要一個名為defaults的字典對象和一個名為update的方法(該函數(shù)提供,但不需要on_load方法)。在PG_Standoff類中,這些調(diào)用將用于附加Standoff.mesh()的返回值,以及無論何時修改metric_diameter或height屬性。PG_Standoff課程的其余部分可以寫成:
class PG_Standoff(PropertyGroup): # ... defaults = { "metric_diameter": 2.5, "height": 3 } standoff = Standoff() def on_load(self): if self.height and self.metric_diameter: self.__set_mesh() def update(self, context): self.__set_mesh() def __set_mesh(self): self.mesh = self.standoff.mesh( self.height, self.metric_diameter)
僅留下import語句和register 、unregister函數(shù)。在 register 函數(shù)中,我們還將從 ointerProperty的實例中指向PG_PropertyGroup類,從 Blender 的Scene類型的新屬性中引用,這將使從插件的其余部分訪問變得簡單。這將產(chǎn)生一個完整的standoff_props.py模塊,如下面的要點所示:
from bpy.props import PointerProperty, FloatProperty from bpy.types import Mesh, PropertyGroup, Scene from bpy.utils import register_class, unregister_class from .standoff_mesh import Standoff def prop_methods(call, prop=None): def getter(self): try: value = self[prop] except: set_default = prop_methods("SET", prop) set_default(self, self.defaults[prop]) if hasattr(self, "on_load"): self.on_load() value = self[prop] finally: return value def setter(self, value): self[prop] = value def updater(self, context): self.update(context) methods = { "GET": getter, "SET": setter, "UPDATE": updater, } return methods[call] class PG_Standoff(PropertyGroup): metric_diameter: FloatProperty( name="Inner Diameter (Metric)", min=2, max=5, step=50, precision=1, set=prop_methods("SET", "metric_diameter"), get=prop_methods("GET", "metric_diameter"), update=prop_methods("UPDATE")) height: FloatProperty( name="Standoff Height", min=2, max=6, step=25, precision=2, set=prop_methods("SET", "height"), get=prop_methods("GET", "height"), update=prop_methods("UPDATE")) mesh: PointerProperty(type=Mesh) defaults = { "metric_diameter": 2.5, "height": 3 } standoff = Standoff() def on_load(self): if self.height and self.metric_diameter: self.__set_mesh() def update(self, context): self.__set_mesh() def __set_mesh(self): self.mesh = self.standoff.mesh(self.height, self.metric_diameter) def register(): register_class(PG_Standoff) Scene.Standoff = PointerProperty(type=PG_Standoff) def unregister(): unregister_class(PG_Standoff) del Scene.Standoff
四、standoff_operator.py
最后兩個模塊都是短文件,實現(xiàn)起來非常簡單,因為結(jié)構(gòu)化和修改數(shù)據(jù)的繁重工作已經(jīng)以簡化其與 Blender Python API 交互的方式完成。在standoff_operator模塊中,我們將定義(并注冊)一個新bpy.types.Operator類,然后可以將其附加到任何 UI 按鈕。
如何定義一個新的Operator有一些要求,但這些都是有據(jù)可查且簡單明了的,并且通過從 CLI 啟動 Blender,警告和錯誤消息將立即顯現(xiàn)錯誤配置的Operator類有。手冊部分給出了新Operators的預(yù)期定義的詳細信息。下面腳本將定義docstring、bl_idname、bl_label和bl_options屬性的值。它還將定義一個execute方法,該方法需要self和context參數(shù),并包含將附加到任何 UI 按鈕調(diào)用注冊bl_idname 名下的Operator邏輯和事件。
import bpy from bpy.types import Operator from bpy.utils import register_class, unregister_class class DEMORACK_OT_AddNewStandoff(Operator): """adds standoff to test add-on registered ok""" bl_idname = 'scene.add_new_standoff' bl_label = 'New Standoff' bl_options = { "REGISTER", "UNDO" } def execute(self, context): name = "Standoff" standoff = context.scene.Standoff # <- set in standoff_props.register() collection = context.scene.collection obj = bpy.data.objects.new(name, standoff.mesh) collection.objects.link(obj) obj.select_set(True) context.view_layer.objects.active = obj return { "FINISHED" } def register(): register_class(DEMORACK_OT_AddNewStandoff) def unregister(): unregister_class(DEMORACK_OT_AddNewStandoff)
五、standoff_panel.py
定義一個新Panel類幾乎遵循與定義一個新Operator類相同的模式,并且從概念上講,只需將execute最后一步中的draw方法換成這一步中的方法。相關(guān)的Panel 手冊部分提供了幾個有用的示例, UI 腳本 > 模板 > Python 菜單中包含更多示例。 在手冊bpy.types.UILayout部分中可以發(fā)現(xiàn)了更多可能性,該部分記錄了Panel對象的導入項。在draw方法中,它是一個簡單(且非常開放)的過程:
- 訪問context 中的相關(guān)數(shù)據(jù)對象
- 為需要按鈕的任何Operator 調(diào)用創(chuàng)建layout.operator對象
- 為用戶可寫數(shù)據(jù)屬性創(chuàng)建layout.prop對象
當然,在該模式以及更復雜的數(shù)據(jù)類型中,還有很多擴展和變化的空間。但在基礎(chǔ)上,這是另一個由 API 處理繁重工作的地方,并遵循bpy.props和bpy.types.PropertyGroup實例的內(nèi)置使用模式。由于這種簡單性,這是另一個模塊,它足夠簡單:
from bpy.types import Panel from bpy.utils import register_class, unregister_class class DemoRackPanel: bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "DemoRack" class StandoffPanel(DemoRackPanel, Panel): bl_idname = "DEMORACK_PT_standoff_panel" bl_label = "Standoff" def draw(self, context): layout = self.layout standoff_data = context.scene.Standoff # <- set in standoff_props.register() layout.operator("scene.add_new_standoff") # <- registered in standoff_operator.py layout.prop(standoff_data, "metric_diameter") layout.prop(standoff_data, "height") def register(): register_class(StandoffPanel) def unregister(): unregister_class(StandoffPanel)
唯一的額外變化是 DemoRackPanel的類定義,它與 bpy.types.Panel一起被StandoffPanel繼承。因為這不是DemoRack插件中的唯一Panel,它們都將位于 View 3D 側(cè)抽屜中的單個選項卡下,消除 3 行重復是一件簡單的事情。layout.prop 的模式和相關(guān)函數(shù)中是將 Data 對象作為第一個參數(shù),并將該對象內(nèi)屬性的字符串標識符作為第二個參數(shù)。
剩下要做的就是從DemoRack/src/目錄編譯DemoRack.zip文件,然后像其他任何文件一樣在編輯 > 首選項 > 附加組件中安裝該本地文件。
原文鏈接:Build a Blender Add-on Ready to Scale
以上就是python可擴展的Blender 3D插件開發(fā)匯總的詳細內(nèi)容,更多關(guān)于python Blender 3D插件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python 實現(xiàn)仿微信聊天時間格式化顯示的代碼
這篇文章主要介紹了python 實現(xiàn)仿微信聊天時間格式化顯示,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-04-04numpy中np.dstack()、np.hstack()、np.vstack()用法
numpy里dstack, hstack, vstack, 都有拼接的作用,本文詳細的介紹了np.dstack()、np.hstack()、np.vstack()用法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03一文帶你了解CNN(卷積神經(jīng)網(wǎng)絡(luò))
CNN是神經(jīng)網(wǎng)絡(luò)中的一種,它的權(quán)值共享網(wǎng)絡(luò)結(jié)構(gòu)使之更類似于生物神經(jīng)網(wǎng)絡(luò),降低了網(wǎng)絡(luò)模型的復雜度,減少了權(quán)值的數(shù)量。本文主要講解了CNN(卷積神經(jīng)網(wǎng)絡(luò))的基礎(chǔ)內(nèi)容,想了解更多的小伙伴可以看一看這篇文章2021-09-09Python實戰(zhàn)之大魚吃小魚游戲的實現(xiàn)
這篇文章主要介紹了如何利用Python制作一個經(jīng)典游戲之大魚吃小魚,文中的示例代碼講解詳細,對我們學習Python有一定幫助,需要的可以參考一下2022-04-04