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节点插件编写之前,先弄清楚开发流程和包含关系,如下图

img-kO8EhnFj-1622820261019

白色和蓝色方框中的内容是重点,黑色方框是本次实例要完成的内容,即创建一个和几何节点面板一样的自定义节点面板,并编写几个简单的节点

不愿意看介绍可以直接看实例

NodeTree

  如果你用过Blender,Blender中自带了几何节点和材质节点,他们都有单独的面板使用他们,同样的,我们创建的自定义节点也可以拥有自己的独立面板,我们编写的节点程序除了拥有python提供的能力外,同时可以使用Blender提供的API。


Node

如图所示:一个节点可见的部分有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

最终实现效果

目前只是简单做了几个节点,当然,会做几个基本的节点后,后面会快很多。

  • Text节点为字符串节点
  • File Str节点是打开FilePath下的文件并以指定编码方式输出
  • 打印节点顾名思义是将得到的信息打印出来

最终的想法是做一个能处理数据和解决数学问题的节点插件,我认为这种方式去做这些会更为直观

  • 17
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
非常好的问题!Blender是一个功能强大的开源3D建模和渲染软件,它允许用户通过Python编程语言创建自定义插件插件开发使您能够扩展Blender的功能,添加新的工具、功能和工作流程。 要开始进行Blender Python插件开发,您可以按照以下步骤进行操作: 1. 学习Python语言:如果您还不熟悉Python编程语言,建议您首先学习Python的基础知识。这将帮助您理解和编写Blender插件所需的代码。 2. 了解BlenderPython API:Blender提供了一个强大的Python API,用于访问和操作Blender的各种功能。您需要熟悉这个API,以便了解可用的函数、类和方法。 3. 编写插件代码:使用Python编写插件代码。插件可以是用于添加新工具、修改现有工具或自动化特定任务的脚本。您可以使用BlenderPython控制台进行实时测试和调试。 4. 注册插件:为了使Blender能够识别和加载您的插件,您需要在Blender进行注册。这通常涉及在插件代码添加一些元数据,例如名称、描述和版本。 5. 测试和调试:在开发过程,测试和调试是非常重要的一步。您可以使用Blender的调试工具和Python控制台来检查代码,并确保插件按预期工作。 6. 分发插件:一旦您的插件开发完成并且经过测试,您可以选择将其分发给其他人使用。您可以将插件打包为.zip文件,并在Blender进行安装。 希望这些步骤能够帮助您入门Blender Python插件开发。祝您成功!如果您有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值