Blender 节点插件开发全流程 您所在的位置:网站首页 houdini和blender几何节点 Blender 节点插件开发全流程

Blender 节点插件开发全流程

2023-11-30 20:52| 来源: 网络整理| 查看: 265

文章目录 写在前面: blender节点插件的结构NodeTreeNodepropertyNodeSocket 准备工作实例创建NodeTree创建组件NodeSocket创建Node创建节点目录并注册上述类 其他说明控制台出现乱码插件代码修改并重新安装后没有变化查找Blender中的图标节点 刷新/输出滞后 问题 参考最终实现效果

写在前面:

由于网上关于blender自定义节点的文章极少(这也是我特意写本文的原因),本文均为自己摸索和部分参考Blender文档得到,可能会有不严谨和不正确的地方,请观者酌情采纳

目的:借用blender的节点实现自己的可视化节点编程,初步完成基本的输入输出节点

Blender Version: 2.92完成时间:2021.06.04会使用到的bpy模块: import bpyfrom bpy.types import NodeTreefrom bpy.types import NodeSocketfrom bpy.types import Nodefrom bpy.utils import register_classfrom bpy.utils import unregister_class blender节点插件的结构

  在正式进入Blender节点插件编写之前,先弄清楚开发流程和包含关系,如下图

img-kO8EhnFj-1622820261019

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

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

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/107151652Blender中的模板文件:Text Editor -> TemplatesGithub 最终实现效果

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

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

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有