天天看點

Blender 節點插件開發全流程blender節點插件的結構準備工作執行個體其他說明參考最終實作效果

文章目錄

      • 寫在前面:
  • 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節點插件編寫之前,先弄清楚開發流程和包含關系,如下圖

Blender 節點插件開發全流程blender節點插件的結構準備工作執行個體其他說明參考最終實作效果
白色和藍色方框中的内容是重點,黑色方框是本次執行個體要完成的内容,即建立一個和幾何節點面闆一樣的自定義節點面闆,并編寫幾個簡單的節點

不願意看介紹可以直接看執行個體

NodeTree

Blender 節點插件開發全流程blender節點插件的結構準備工作執行個體其他說明參考最終實作效果

  如果你用過Blender,Blender中自帶了幾何節點和材質節點,他們都有單獨的面闆使用他們,同樣的,我們建立的自定義節點也可以擁有自己的獨立面闆,我們編寫的節點程式除了擁有python提供的能力外,同時可以使用Blender提供的API。

Node

Blender 節點插件開發全流程blender節點插件的結構準備工作執行個體其他說明參考最終實作效果

如圖所示:一個節點可見的部分有5個部分

  1. 标簽
  2. 圖示【可以沒有】
  3. 輸出元件(NodeSocket)
  4. 節點屬性(property)
  5. 輸入元件(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()

           

其他說明

控制台出現亂碼

  1. 路徑中包含中文,應更改路徑
  2. 設定了界面語言為中文,應改為English
  3. 實際上,我建議最好全用英文,即使是下劃線,放在最前面有時候也會報錯

插件代碼修改并重新安裝後沒有變化

  • 舊的插件并沒有徹底删除,跳轉至Blender放置插件的位置将過期代碼删除,并重新嘗試安裝
  • 建議:開發全程使用VS Code的Blender Development插件,調用Blender: Start指令調試插件

查找Blender中的圖示

  1. 打開自帶插件icon Viewer,在文本編輯視窗的側邊欄Dev選項中即可檢視全部圖示,點選即可複制圖示名稱。

    參考文章

節點 重新整理/輸出滞後 問題

  1. 當節點自身拓撲結構發生變化時,Blender會自動調用節點的update方法,是以我們将輸入與輸出的邏輯關系放在這裡
  2. 當我們改變某節點的父結點的參數時,此時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

最終實作效果

Blender 節點插件開發全流程blender節點插件的結構準備工作執行個體其他說明參考最終實作效果

目前隻是簡單做了幾個節點,當然,會做幾個基本的節點後,後面會快很多。

  • Text節點為字元串節點
  • File Str節點是打開FilePath下的檔案并以指定編碼方式輸出
  • 列印節點顧名思義是将得到的資訊列印出來

最終的想法是做一個能處理資料和解決數學問題的節點插件,我認為這種方式去做這些會更為直覺