python可擴(kuò)展的Blender 3D插件開(kāi)發(fā)匯總
Blender 3D 插件
成熟的 Blender 3D 插件是令人驚奇的事情。作為 Python 和 Blender 的新手,我經(jīng)常發(fā)現(xiàn)自己被社區(qū)中的人們創(chuàng)造的強(qiáng)大的東西弄得目瞪口呆。坦率地說(shuō),其中一些包看起來(lái)有點(diǎn)神奇,當(dāng)自我懷疑或冒名頂替綜合癥的嘮叨聲音被打破時(shí),很容易想到“如果有人能做出可以做xxx的東西就好了” 。
然后我記得,通過(guò)將好奇心和固執(zhí)與良好的文檔相結(jié)合,某人可以是任何人,X可以成為X、Y 和 Z。即使是困難的部分也可以弄清楚——尤其是因?yàn)樗泄虉?zhí)和好奇的人確保 Blender 的 Python文檔和stackexchange與它讓我們創(chuàng)建的 gee whiz 圖形一樣好。
同樣的方式,已經(jīng)存在的文檔和模型為從頭開(kāi)始編寫(xiě) Blender 插件奠定了平滑的基礎(chǔ),在開(kāi)始時(shí)為該插件提供可擴(kuò)展的結(jié)構(gòu)有助于展示 Python API 的各個(gè)部分如何更多地組合在一起清楚地。換句話(huà)說(shuō),它使新編寫(xiě)的代碼比原來(lái)更好,同時(shí)也使現(xiàn)有代碼更容易學(xué)習(xí)。
在本文的最后,我們將創(chuàng)建一個(gè)功能齊全且已安裝的插件,它提供了一個(gè)自定義 UI 元素來(lái)將Standoff 添加到 Blender 場(chǎng)景中,并帶有界面控件來(lái)調(diào)整創(chuàng)建的網(wǎng)格的直徑和高度。
一、文件結(jié)構(gòu)
本文末尾將存在的完整目錄和文件結(jié)構(gòu),我們可以使用mkdir和touch方式進(jìn)行創(chuàng)建,本示例是一個(gè)填空游戲。我正在調(diào)用項(xiàng)目DemoRack并將其設(shè)置為我用于 Python 項(xiàng)目的文件夾中的頂級(jí)目錄名稱(chēng):它不一定必須是 Blender 特定的任何地方。下面是DemoRack項(xiàng)目的文件結(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
下面我們簡(jiǎn)單介紹下這些文件的作用:
- DemoRack.zip:已編譯src,安裝在 Blender 中的文件。
- __init__.py:為附加組件注冊(cè)所有必要的信息和類(lèi)。
- standoff_mesh.py:用于生成目標(biāo)幾何/網(wǎng)格數(shù)據(jù)的模塊。
- standoff_operator.py:將提供給 UI 使用的“do-er”。
- standoff_panel.py:在 UI 元素上添加插件將......添加。
- standoff_props.py:定義Panel 和 Operator所需的數(shù)據(jù)對(duì)象
簡(jiǎn)而言之,每個(gè)新standoff_模塊都將包含register()和unregister()函數(shù)。該__init__模塊將導(dǎo)入這些模塊,并將兩種類(lèi)型的函數(shù)捆綁到單個(gè)迭代器中。Blender Python 文檔描述了這些函數(shù)所扮演的角色:
二、__init__.py
有了這個(gè)背景,并且因?yàn)開(kāi)_init__模塊中的大部分代碼都與sys和importlib包有關(guān),所以我將在此處包含要點(diǎn),而不會(huì)試圖去描述 Python 模塊導(dǎo)入的雜草。Blender插件需要注意的具體事項(xiàng)是module_names列表,聲明要引入register和unregister函數(shù)的的文件名,以及打開(kāi)的bl_info字典。如官方插件介紹教程中所述,bl_info包含將在“首選項(xiàng)”窗格中找到的所有信息:
bl_info是包含附加元數(shù)據(jù)的字典,例如要顯示在首選項(xiàng)附加列表中的標(biāo)題、版本和作者。它還指定了運(yùn)行腳本所需的最低 Blender 版本;舊版本不會(huì)在列表中顯示加載項(xiàng)。
下面是一段示例代碼:
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
該模塊將是其中涉及最多的模塊,但它也為其他模塊提供了主干,并實(shí)現(xiàn)了一種可以廣泛重用的模式。它依賴(lài)于導(dǎo)入用于將一組屬性定義捆綁在一起的PropertyGroup類(lèi)型(文檔),bpy.props。一旦PropertyGroup在 Blender 中注冊(cè),它就在 Python 可腳本化數(shù)據(jù)對(duì)象的指針和底層 C 分配的內(nèi)存之間提供了一座橋梁,這些內(nèi)存完成了 Blender 的繁重工作。
我們需要在standoff_props.py類(lèi)中將定義、繼承bpy.types.PropertyGroup并跟蹤 3 個(gè)屬性:
- metric_diameter: FloatProperty(**kwargs)
- height: FloatProperty(**kwargs)
- mesh: PointerProperty(type=Mesh)
其中,前兩個(gè)應(yīng)該是不言自明的,并且在實(shí)現(xiàn)中將有更多關(guān)于參數(shù)的細(xì)節(jié)。第3個(gè)PointerProperty指向內(nèi)存中的一個(gè)對(duì)象,并要求在定義時(shí)指定該對(duì)象的類(lèi)型,并且它是 PropertyGroup的子類(lèi)或bpy.struct.ID(即 Mesh)。這意味著任何將值設(shè)置為任何其他數(shù)據(jù)類(lèi)型的實(shí)例的嘗試(在這種情況下,任何非 bpy.types.Mesh)都將引發(fā)錯(cuò)誤,任何將值傳遞給期望任何其他數(shù)據(jù)的參數(shù)的嘗試也是如此類(lèi)型。
在這種情況下,mesh的 PointerProperty屬性將用于保存在Standoff.mesh()的返回值,并使用存儲(chǔ)在metric_diameter和height后面的值進(jìn)行實(shí)例化和修改。這三個(gè)屬性的完整定義如下所示:
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)
單獨(dú)的set 、 get和update參數(shù)都指向prop_methods函數(shù)的返回值。這些值必須是函數(shù),參數(shù)分別為(self, value)、(self)和(self, context)。這個(gè)閉包工廠可能看起來(lái)額外復(fù)雜,但它會(huì)顯著減少重復(fù),并為與PropertyGroup 的數(shù)據(jù)屬性交互提供更大的靈活性。
要理解的一個(gè)重要區(qū)別是,每當(dāng)屬性更改時(shí)都會(huì)調(diào)用update函數(shù)——它不是作為更新定義其屬性的一種方式來(lái)調(diào)用的。相反,它提供了一種將特定屬性的更改傳達(dá)給程序的其他部分的方法;這必須小心使用以避免副作用,并且因?yàn)闆](méi)有檢查來(lái)避免無(wú)限遞歸。
另一件需要注意的是,使用set和get函數(shù)意味著任何default值都必須通過(guò)顯式set調(diào)用(而不是 kwarg)來(lái)設(shè)置,但這也提供了在必要時(shí)掛鉤on_load方法的機(jī)會(huì)。最后,prop_methods函數(shù)必須在調(diào)用它的任何PropertyGroup類(lèi)之前定義。
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]
完整的實(shí)現(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屬性的類(lèi)都需要一個(gè)名為defaults的字典對(duì)象和一個(gè)名為update的方法(該函數(shù)提供,但不需要on_load方法)。在PG_Standoff類(lèi)中,這些調(diào)用將用于附加Standoff.mesh()的返回值,以及無(wú)論何時(shí)修改metric_diameter或height屬性。PG_Standoff課程的其余部分可以寫(xiě)成:
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語(yǔ)句和register 、unregister函數(shù)。在 register 函數(shù)中,我們還將從 ointerProperty的實(shí)例中指向PG_PropertyGroup類(lèi),從 Blender 的Scene類(lèi)型的新屬性中引用,這將使從插件的其余部分訪(fǎng)問(wèn)變得簡(jiǎn)單。這將產(chǎn)生一個(gè)完整的standoff_props.py模塊,如下面的要點(diǎn)所示:
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
最后兩個(gè)模塊都是短文件,實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,因?yàn)榻Y(jié)構(gòu)化和修改數(shù)據(jù)的繁重工作已經(jīng)以簡(jiǎn)化其與 Blender Python API 交互的方式完成。在standoff_operator模塊中,我們將定義(并注冊(cè))一個(gè)新bpy.types.Operator類(lèi),然后可以將其附加到任何 UI 按鈕。
如何定義一個(gè)新的Operator有一些要求,但這些都是有據(jù)可查且簡(jiǎn)單明了的,并且通過(guò)從 CLI 啟動(dòng) Blender,警告和錯(cuò)誤消息將立即顯現(xiàn)錯(cuò)誤配置的Operator類(lèi)有。手冊(cè)部分給出了新Operators的預(yù)期定義的詳細(xì)信息。下面腳本將定義docstring、bl_idname、bl_label和bl_options屬性的值。它還將定義一個(gè)execute方法,該方法需要self和context參數(shù),并包含將附加到任何 UI 按鈕調(diào)用注冊(cè)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
定義一個(gè)新Panel類(lèi)幾乎遵循與定義一個(gè)新Operator類(lèi)相同的模式,并且從概念上講,只需將execute最后一步中的draw方法換成這一步中的方法。相關(guān)的Panel 手冊(cè)部分提供了幾個(gè)有用的示例, UI 腳本 > 模板 > Python 菜單中包含更多示例。 在手冊(cè)bpy.types.UILayout部分中可以發(fā)現(xiàn)了更多可能性,該部分記錄了Panel對(duì)象的導(dǎo)入項(xiàng)。在draw方法中,它是一個(gè)簡(jiǎn)單(且非常開(kāi)放)的過(guò)程:
- 訪(fǎng)問(wèn)context 中的相關(guān)數(shù)據(jù)對(duì)象
- 為需要按鈕的任何Operator 調(diào)用創(chuàng)建layout.operator對(duì)象
- 為用戶(hù)可寫(xiě)數(shù)據(jù)屬性創(chuàng)建layout.prop對(duì)象
當(dāng)然,在該模式以及更復(fù)雜的數(shù)據(jù)類(lèi)型中,還有很多擴(kuò)展和變化的空間。但在基礎(chǔ)上,這是另一個(gè)由 API 處理繁重工作的地方,并遵循bpy.props和bpy.types.PropertyGroup實(shí)例的內(nèi)置使用模式。由于這種簡(jiǎn)單性,這是另一個(gè)模塊,它足夠簡(jiǎn)單:
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的類(lèi)定義,它與 bpy.types.Panel一起被StandoffPanel繼承。因?yàn)檫@不是DemoRack插件中的唯一Panel,它們都將位于 View 3D 側(cè)抽屜中的單個(gè)選項(xiàng)卡下,消除 3 行重復(fù)是一件簡(jiǎn)單的事情。layout.prop 的模式和相關(guān)函數(shù)中是將 Data 對(duì)象作為第一個(gè)參數(shù),并將該對(duì)象內(nèi)屬性的字符串標(biāo)識(shí)符作為第二個(gè)參數(shù)。
剩下要做的就是從DemoRack/src/目錄編譯DemoRack.zip文件,然后像其他任何文件一樣在編輯 > 首選項(xiàng) > 附加組件中安裝該本地文件。
原文鏈接:Build a Blender Add-on Ready to Scale
以上就是python可擴(kuò)展的Blender 3D插件開(kāi)發(fā)匯總的詳細(xì)內(nèi)容,更多關(guān)于python Blender 3D插件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python 實(shí)現(xiàn)仿微信聊天時(shí)間格式化顯示的代碼
這篇文章主要介紹了python 實(shí)現(xiàn)仿微信聊天時(shí)間格式化顯示,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-04-04numpy中np.dstack()、np.hstack()、np.vstack()用法
numpy里dstack, hstack, vstack, 都有拼接的作用,本文詳細(xì)的介紹了np.dstack()、np.hstack()、np.vstack()用法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03一文帶你了解CNN(卷積神經(jīng)網(wǎng)絡(luò))
CNN是神經(jīng)網(wǎng)絡(luò)中的一種,它的權(quán)值共享網(wǎng)絡(luò)結(jié)構(gòu)使之更類(lèi)似于生物神經(jīng)網(wǎng)絡(luò),降低了網(wǎng)絡(luò)模型的復(fù)雜度,減少了權(quán)值的數(shù)量。本文主要講解了CNN(卷積神經(jīng)網(wǎng)絡(luò))的基礎(chǔ)內(nèi)容,想了解更多的小伙伴可以看一看這篇文章2021-09-09Python?print函數(shù):如何將對(duì)象打印輸出
這篇文章主要介紹了Python?print函數(shù):如何將對(duì)象打印輸出,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Python實(shí)現(xiàn)電視里的5毛特效實(shí)例代碼詳解
這篇文章主要介紹了Python實(shí)現(xiàn)了電視里的5毛特效,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05pyecharts繪制中國(guó)2020肺炎疫情地圖的實(shí)例代碼
在本篇文章里小編給大家整理的是關(guān)于pyecharts繪制中國(guó)2020肺炎疫情地圖的實(shí)例代碼內(nèi)容,有興趣的朋友們可以測(cè)試下。2020-02-02Python實(shí)戰(zhàn)之大魚(yú)吃小魚(yú)游戲的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Python制作一個(gè)經(jīng)典游戲之大魚(yú)吃小魚(yú),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Python有一定幫助,需要的可以參考一下2022-04-04