文章目錄
-
-
- 寫在前面:
-
- blender節點插件的結構
-
- NodeTree
- Node
- property
- NodeSocket
- 準備工作
- 執行個體
-
- 建立NodeTree
- 建立元件NodeSocket
- 建立Node
- 建立節點目錄并注冊上述類
- 其他說明
-
- 控制台出現亂碼
- 插件代碼修改并重新安裝後沒有變化
- 查找Blender中的圖示
- 節點 重新整理/輸出滞後 問題
- 參考
- 最終實作效果
寫在前面:
由于網上關于blender自定義節點的文章極少(這也是我特意寫本文的原因),本文均為自己摸索和部分參考Blender文檔得到,可能會有不嚴謹和不正确的地方,請觀者酌情采納
目的:借用blender的節點實作自己的可視化節點程式設計,初步完成基本的輸入輸出節點
- Blender Version: 2.92
- 完成時間:2021.06.04
- 會使用到的bpy子產品:
- import bpy
- from bpy.types import NodeTree
- from bpy.types import NodeSocket
- from bpy.types import Node
- from bpy.utils import register_class
- from bpy.utils import unregister_class
blender節點插件的結構
在正式進入Blender節點插件編寫之前,先弄清楚開發流程和包含關系,如下圖

白色和藍色方框中的内容是重點,黑色方框是本次執行個體要完成的内容,即建立一個和幾何節點面闆一樣的自定義節點面闆,并編寫幾個簡單的節點
不願意看介紹可以直接看執行個體
NodeTree
如果你用過Blender,Blender中自帶了幾何節點和材質節點,他們都有單獨的面闆使用他們,同樣的,我們建立的自定義節點也可以擁有自己的獨立面闆,我們編寫的節點程式除了擁有python提供的能力外,同時可以使用Blender提供的API。
Node
如圖所示:一個節點可見的部分有5個部分
- 标簽
- 圖示【可以沒有】
- 輸出元件(NodeSocket)
- 節點屬性(property)
- 輸入元件(NodeSocket)
如果把節點分為UI和内部方法兩個部分,我們的UI工作也就是這些
property
這個部分來自bpy.props,props全稱是property,直譯為屬性。blender中很多子產品都使用到了屬性,屬性中常用的方法有get()和set(),在需要讀取和寫入屬性值時,會自動調用他們。
這個部分我看了很久,至今也沒太明白,主要是建立一個執行個體後,我怎麼通過多種方法通路和修改到它的屬性,以及了解他的工作方式。
NodeSocket
直譯為節點套接字,我覺得這個不好了解,下文都稱為節點元件/NodeSocket。
節點元件是節點的基本組成之一,節點元件内一般都會定義一個property,blender也自帶了種類豐富的元件。
準備工作
- 我的電腦中blender的python環境和VScode是分開的,兩邊的庫并不連通。
- 下面的做法并不是必須的,但能極大的幫助插件的開發工作
為了友善開發,需要安裝一個python庫:使用 pip install fake-bpy-module-2.92 或 進入 https://pypi.org/project/fake-bpy-module-2.92/ 下載下傳,安裝在VScode環境中。該庫的作用如其名,假的bpy庫,隻提供代碼自動補齊,使用時同樣是import bpy。
我推薦用VScode編寫,裡面有一個Blender Development插件,可以極大的友善插件的編寫和調試工作。
執行個體
所有代碼:連結:https://pan.baidu.com/s/1xtUTL4gf46DT5geIUnN9ig
提取碼:ldz8
建立NodeTree
NodeTree部分的工作量不大,隻是注冊一個面闆,填入必要的資訊即可
### NOTE:第一步:建立節點樹 ###
from bpy.types import NodeTree
# 自定義節點編輯面闆的資訊
class myCustomTree(NodeTree):
"""一個節點樹類型,它将展示在編輯器類型清單中"""
bl_idname = 'CustomTreeType' # 面闆的id,唯一
bl_label = "My NodeTree" # 面闆的标簽,用于展示
bl_icon = 'RNA' # 面闆的圖示,使用Blender自帶的圖示
# 定義節點時需要這個類
class MyCustomTreeNode:
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'CustomTreeType'
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem
# 建立節點目錄時需要這個類
class MyNodeCategory(NodeCategory):
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'CustomTreeType'
建立元件NodeSocket
# Created by Jiacong Zhao @CSDN-奇偕
# XXX The last modification at:2021.06.04
# XXX Topic: 自定義單選元件示例
# XXX Discription: 元件是構成節點的基礎
# blender本身自帶了NodeSocketInt、NodeSocketFloat、NodeSocketString等元件
import bpy
from bpy.types import NodeSocket
class Socket_Enum_List(NodeSocket):
bl_idname = '_enum_list'
bl_label = "enum_list"
# 選項清單
my_items = (
# [(identifier, name, description, icon, number), ...]
('C://', "C://", ""),
('D://', "D://", ""),
('E://', "E://", ""),
('F://', "F://", ""),
)
# 元件基本資訊
myenum: bpy.props.EnumProperty(
name="Direction",# 名稱
description="一個例子",# 描述
items=my_items,# 可選擇的項目
default='C://',# 預設選擇
)
def val(self):
return self.myenum
# 用于繪制在節點框裡顯示的文本【可選】
def draw(self, context, layout, node, text):
if self.is_linked:
# 如果被連接配接,隻顯示的文本
# text += bpy.props.
layout.label(text=text)
else:
# 沒有被連接配接時,顯示文本和選項
layout.prop(self, "myenum", text=text)
# 元件顔色【那個小點點】
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
建立Node
'''
in info s
'''
# Created by Jiacong Zhao @CSDN-奇偕
# XXX The last modification at:2021.06.04
# XXX Topic: 列印節點
# XXX Discription: 将得到的任何資料轉為字元串并列印,實時重新整理
import time
import bpy
from bpy.types import Node
from ..MyCustomTree import *
from ..MyCustomSocket import *
class PrintNode(Node, MyCustomTreeNode):
bl_idname = 'PrintNode'
bl_label = "列印"
bl_icon = 'CONSOLE'
def init(self, context):
self.inputs.new('NodeSocketString', "info")
def parent_socket(self):
return self.inputs[0].links[0].from_socket
def draw_buttons(self, context, layout):
if self.inputs[0].is_linked:
layout.label(text=str(self.parent_socket().default_value))
else:
layout.label(text='')
def draw_buttons_ext(self, context, layout):
...
def draw_label(self):
return "列印"
# 拓撲圖更新時調用
def update(self):
print(time.ctime(),end='>>> ')
print(str(self.parent_socket().default_value))
# 複制時調用
def copy(self, node):
print("Copying from node ", node)
# 釋放時調用
def free(self):
print("Removing node ", self, ", Goodbye!")
建立節點目錄并注冊上述類
# 插件資訊
bl_info = {
"name" : "MyCustomAddon",
"author" : "QiXie",
"description" : "",
"blender" : (2, 92, 0),
"version" : (0, 0, 1),
"location" : "",
"warning" : "",
"category" : "Generic"
}
import bpy
# View3d面闆
from .TestHello import *
from .TestMyPanel import *
''' 自定義節點面闆 '''
# 節點樹--節點元件--節點--節點目錄
# from .myNodes import *
from .MyCustomTree import *
from .MyCustomSocket import Socket_classes
from .MyCustomNodes import Node_classes
classes = (
Test_OT_Hello,
Test_PT_HelloPanel,
myCustomTree,
)
### NOTE:建立節點目錄 ###
node_categories = [
# identifier, label, items list
# -->辨別符、标簽(展示的名稱)、項目清單
MyNodeCategory('INPUT', "輸入節點", items=[
NodeItem("TextNode"),
NodeItem("FileNode"),
]),
MyNodeCategory('OUTPUT', "輸出節點", items=[
NodeItem("PrintNode"),
]),
MyNodeCategory('OTHERNODES', "測試節點", items=[
# --> 節點項可以有額外的設定,可以應用于新的節點
# --> 注意:設定值存儲為字元串表達式,是以應該使用repr()将其轉換為字元串。
# 由一個節點設定不同的值派生出新節點
# 一個節點有四個屬性
# nodetype【類型|必填】
# label【名稱】
# settings【設定】:settings[0]【名稱】settings[1]【值】
# poll
NodeItem("TestNode", label="Node A", settings={
"my_string_prop": repr("文本"),
}),
NodeItem("TestNode", label="Node B", settings={
"my_string_prop": repr("文本"),
}),
]),
]
# 類的注冊【方法一】
# register, unregister = bpy.utils.register_classes_factory(classes)
# 類的注冊【方法二】
def register():
from bpy.utils import register_class
# 注冊
for cls in classes:
register_class(cls)
# 元件注冊
for cls in Socket_classes:
register_class(cls)
# 節點注冊
for cls in Node_classes:
register_class(cls)
# 節點目錄注冊
nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories)
def unregister():
nodeitems_utils.unregister_node_categories('CUSTOM_NODES')
from bpy.utils import unregister_class
# 節點
for cls in Node_classes:
unregister_class(cls)
# 元件
for cls in Socket_classes:
unregister_class(cls)
#
for cls in reversed(classes):
unregister_class(cls)
# if __name__ == "__main__":
# register()
其他說明
控制台出現亂碼
- 路徑中包含中文,應更改路徑
- 設定了界面語言為中文,應改為English
- 實際上,我建議最好全用英文,即使是下劃線,放在最前面有時候也會報錯
插件代碼修改并重新安裝後沒有變化
- 舊的插件并沒有徹底删除,跳轉至Blender放置插件的位置将過期代碼删除,并重新嘗試安裝
- 建議:開發全程使用VS Code的Blender Development插件,調用Blender: Start指令調試插件
查找Blender中的圖示
-
打開自帶插件icon Viewer,在文本編輯視窗的側邊欄Dev選項中即可檢視全部圖示,點選即可複制圖示名稱。
參考文章
節點 重新整理/輸出滞後 問題
- 當節點自身拓撲結構發生變化時,Blender會自動調用節點的update方法,是以我們将輸入與輸出的邏輯關系放在這裡
- 當我們改變某節點的父結點的參數時,此時update方法不會被調用,也就是說變化的傳遞會即刻終止,這在大多數情況下不符合需求,我的解決辦法如下:
- 編寫自定義NodeSocket,在property of NodeSocket的set方法中,調用NodeSocket父節點的update方法,這樣自身參數改變時,輸出也會跟着重新整理
- 在原有的update方法中運用遞歸的思想,自身輸出重新整理後調用子節點的update方法,如下所示
for item in self.outputs:
for cell in item.links:
cell.to_socket.node.update()
# 我必須要說明的是,這是一種簡單粗暴的方法,當整個節點樹比較複雜時,update方法的調用次數是成倍上升的,好的算法應該讓每個節點按照一定順序逐個執行一遍
# 如果你需要掐斷這種傳遞,那麼添加一個不含這類代碼的節點即可
參考
遇到困難的時候不妨求助以下路徑
- 點選打開Blender官方文檔 直接看很頭痛
- 【Blender Python小白向入門-哔哩哔哩】https://b23.tv/c0UwtY 入門推薦
- 【dog日常:開發blender插件#1-哔哩哔哩】https://b23.tv/zCLcR9 快速了解插件開發
- Blender中的圖示 https://blog.csdn.net/qq_42110882/article/details/107151652
- Blender中的模闆檔案:Text Editor -> Templates
- Github
最終實作效果
目前隻是簡單做了幾個節點,當然,會做幾個基本的節點後,後面會快很多。
- Text節點為字元串節點
- File Str節點是打開FilePath下的檔案并以指定編碼方式輸出
- 列印節點顧名思義是将得到的資訊列印出來
最終的想法是做一個能處理資料和解決數學問題的節點插件,我認為這種方式去做這些會更為直覺