写在前面
Hot Node 现已上线 Blender 插件官网 —> Blender Extensions - Hot Node
项目 Github 仓库 —> Github - Hot Node
Bilibili 演示视频 —> Blender 超级节点插件 Hot Node
也可以直接在 Blender 软件内 Preferences(首选项 )
> Get Extension(获取插件)
界面搜索安装!
总结和随笔
这个插件从5月3号开始写,一直搞到现在……一开始是为了输出能生成节点的python语句,后来越写越大。随着对bpy的不断深入了解,最后决定重构,并做成了现在的样子,想想还是蛮厉害的。
这篇日志主要从重构插件开始记录,初衷是为了不忘记曾经它是用来傻傻地输出python语句的。。。现在也用来记录一些blender api的bug。bpy的坑实在太多,多到我不想写blender插件开发的相关文章。所以,遇到的开发问题,也统一暂存在这里,之后如果真要写开发教程,看看这里还能想起之前遇到的坑。
不过,写这个插件也确实是很宝贵的项目经历!无论是从代码还是到宣发、提交审核、维护运营、版本更新等等……
插件偶尔会登上首页轮播推荐,还是挺开心的……
收到好评的那一刻非常开心。看到了一句我认为的至高评价:
Blender community needs contributors like you.
看到这句话,内心真是内牛满面!冲啊,FORZA BLENDER COMMUNITY!!!
早期开发阶段
2024-06-23 NodeGroup创建
昨天和今天设法把NodeGroup的创建逻辑做得差不多了,唯一缺少的只有Image了。
NodeGroup的创建有一些坑。
首先,NodeGroup内部包含的是NodeTree,但在NG节点本级,NG表现为一个Node。这个Node需要有Input Socket和Output Socket。由于是自定义的节点,我们需要先声明它。实现:通过node_tree.interface.new_socket方法声明IO插槽。
还有NodeFrame的父级设置问题,通过node.parent解决了。
文档里写的NodeFrame,实际上用node.type输出的是FRAME。不过,type属性文档里说已经deprecated了,还是用bl_idname好。
目前,生成函数名是通过添加正则表达式剔除用户预设名称/NG名称的空格和符号。这有潜在隐患:用户有仅以符号和空格做区分的NG时,以用户设置的名称作为函数名并不可靠。但并不打算改,一是用这个插件的应该不会有这种混乱的命名方式,二是可以通过强制禁止如此命名来避开风险。
现在距离第一个release版本发布还差如下功能:
1、NG内Image节点预设的保存。
2、使插件不用基于根材质面板,成为一个可以在任意子级材质面板应用预设的插件。可以在子2级面板应用子1级的预设。这个功能应该比较好实现,需要考虑的只有Group Input和Output这种特殊节点的存在与否。有两种情况:根NT和NG内NT。如果是NG内NT,部分节点的预设还好,那种包含input和output的要具体考虑怎么做。
3、支持用户框选部分节点,生成预设。这和第2点遥相呼应,使插件更具有灵活性。
4、完善NG在json中的增删查改,因为现在的逻辑是根据需要,就地新建生成NG的语句,没完善删除NG的逻辑。设想是增加一个变量,统计预设内包含的NG,每次修改预设时,扫描这个预设包,剔除不需要的NG。同时,完善用户保存、使用预设时,如果已经存在NG,该怎么办(唯一名称、覆盖、使用现有)
5、★ 为了之后的统一性,需要测试json文件包含多个预设时的性能,看是否需要改成一个预设一个json,一个文件夹一个预设包的形式(这也有利于解决单个预设包的预设数上限问题、生成代码效率问题、NG重复问题,可以说,第4个问题就迎刃而解了)。现在的逻辑是,修改一次预设,就重新生成所有预设包内的代码。显然有点不妥。如果修改这一点,CRUD系统就要大改……在做这件事之前一定要备份一个原来逻辑的插件版本。这件事还得趁早实现……
没错,经过综合考虑,第5个需求,也就是修改数据存储方式要趁早实现。先实现5,再看4的需求,再实现23,再实现1.
这样一来,插件应该就能比较好地应对材质面板操作的所有情况了。CRUD十分重要。考虑到以后可能拓展到几何节点、合成节点……需要在CRUD部分留下充足的修改空间,降低NT种类与CRUD系统的耦合度。
2024-06-24 计划重构数据结构、预设执行方式
为什么不用存储节点数据+分析执行器的方式,而是要手动去生成这些语句???
不是,哥们?
实在是太笨了。立刻立刻重构码!!!!!!!!!!!!!!!!!!!!!
一开始生成语句是为了方便复制进脚本直接运行,生成代码,方便放进其他需要生成固定节点预设的插件也是一个考量点。
但随着代码越来越复杂,这样搞肯定不行。何不改成写执行器+开放api给其他插件调用?
这样一来,其他插件借用本插件功能的难题也能解决了:在Coder里增设一个保存预设至插件的功能,会复制我的执行器+其它人创建的预设到他的插件。他只需在插件里调用固定的API,即可敏捷修改他的插件材质预设。
完美,只要如此重构,就可以直接支持几何节点了!
体会到了需求分析和市场调研的重要性。
修改的数据结构如下(太占地方,删了)
第一个版本的UI已经变得难以适配新需求了。今天先重构GUI,向更合理、专业的UI看齐。先做UI是因为可以在过程中整理用户需求,并且事先思考需要哪些scene变量。
0.0.9版本UI
当初的UI是面板+枚举列表+弹窗口,十分不妨便。为了适配多个Image,把UI和operator、Image数据类耦合度搞得巨高。。。因为插件功能是实时生成代码,没保存节点数据,用完就扔,需要再创建。
这两天深刻体会到了需求分析、可行方案探索的重要性。
一个小坑,导致无法注册:
for cls in classes:
bpy.utils.register_class(cls)
我的classes声明如下:
classes = (
myclass
)
这会报错,提示不可迭代,因为括号里只有一个对象!解决办法也很简单,加一个逗号,让classes被判定成元组就行了。。
UIList无法显示.找了半天的bug,最后发现是没有注册类。。。因为原来的框架中,注册统一在init中进行。。并且,EnumProperty定义中不能缺少description,否则UIList无法显示。。。这可能是FakeBPY和现有版本不一致。靠经验解决的。
经过修改后的UI、变量框架更符合规范了。
对于一些变量,直接访问bpy.context.scene是不允许的。要设法从blender api中支持context的回调函数,传入context参数
从中获得scene。但对于一些变量,直接访问又是允许的。。。
终于找到可以不用重新打开blender就应用脚本更改的办法了!之前每次都得重开。。。我怎么早没想到放进register里呢?
register中importlib.reload()。现在太爽了!终于不用重开了!
一些UI交互的小细节:删除后,光标是上移还是下移。应该如此:下方还有,就下移;没有才上移。我用于参考UI代码的VR插件这点做的不好。blender的材质插槽界面是好的。
col.separator()要看官方文档。fake bpy不对
新preset界面
赏心悦目有木有!
2024-06-26
b prop回调函数不触发,重开一下bl就好了
2024-06-27 重构UI
已知bug:重开插件,当前面板名称和实际名称不对应(因为重开插件装载packs时,packs重新排序了。)
这个留作以后解决。
终于把UI重构完了!按钮少了很多!!
接下来做数据结构和解析器。
新代码看着太爽了!
注意,使用\t向文件中写入的tab是tab格式,而vscode中配置的是4空格代替tab。混用会出错.复制代码缩进也会从空格变成tab,得注意。
2024-06-28 重构 Node Parser
重构分析节点逻辑有序进行中,很顺利,写了很多代码,都没什么bug。越来越熟练了。
从之前重构UI就有个bug一直没解决:重新注册插件时,当前pack name显示和实际选择的pack不一致。这是因为不调用ops,无法访问context,无法修改注册在context.scene中的的变量。而之前注册的变量不知为何还在。
重新梳理一下blender的数据结构,之前是模仿其他插件,把数据存在bpy.types.Scene里。数据是跟随scene的,切换Scene就会重新创建一份。而跟随整个blender文件的数据级别是types.ID。
可以看看blender material的ui面板:
row.template_ID(ob, “active_material”, new=“material.new”)
ob是context.object,属于ID下的数据块
还是不行,修改ID层面数据不太好。
最后,竟然通过去掉reload解决了!(之前为了不退出blender就能更新代码,在register中reload了所有模块。我推测这样会丢失对已经注册的blender变量的引用。去掉对properties的引用以后,重新加载插件,面板值就能和当前选项对应了!而且能继承之前的选项!
又排除了一个bug: 读取预设失败。存储在blender里的presets是一个collectionProps,它是一个类的列表,单独拿一个元素出来也是类,我直接赋值preset了,应该赋值preset.name。哎,blender没有api提示就是容易出错啊。
排除bug:重复存储时,数据文件里的内容越来越多。原因:把引用类型当值类型了,append以后没有删除。下次保存时继承了上次的内容。
发现json文件好多换行啊。。信息密度好低。而且才写一点就6kb了。把indent设置成1或者\t,变成了3kb。indent不要,只有2kb。很难取舍啊.blender规范是4个空格缩进。还是设置成1吧。
发现数据里所有node的attrs属性都一样。原来是今天老是用C#的习惯,在类内声明变量默认是静态的。实际上应该用__init__(self)配合http://self.xxx来声明实例。
发现如果当前面板有重名节点,links会失效,因为blender的重名保护逻辑自动重名了我们生成的节点。用列表idx来标记我们自己的节点,即使它的名字被改变,也能找到它。完美解决!考虑到效率问题,没用index()获取idx,而是每生成cnode时在字典中加一项:nodename:idx。
还有undo,redo的逻辑,之后新增吧。
2024-06-29 摆脱特殊类限制
有一个很危险的bug: 隔一段时间打开blender窗口,选择预设包会卡死!
今天摆脱了特殊类的限制!现在任何类都能由一个函数全部解析,不用设置uni_attrs了!原理是递归向下查找子属性。
blender不能debug,只能一点一点找bug,搞这种逻辑真是受罪,就像一个黑箱!
但是跑通的一刻很爽!唯一美中不足的就是还需要设置一个白名单,防止这个函数太通吃,把很多不必要的类都解析了。
**一个超级大坑!!cattrs[attr] = [{}] * length这样子会使每个元素都有同样的引用!!!!因为字典是引用类型!!!**我说怎么所有值一样,找了我半天时间!!!
本想今天搞完NG,但是摆脱特殊类限制就debug了一天。。。
2024-06-30
争取今天把功能开发完。
今天吃早餐突然想到昨天看api,里面提到,blender 为了性能考虑,很多回调函数做出的更改不会立即执行(好像扯到不同线程),直到有东西使用这些更改。我猜这就是为什么我很多时候复制节点会复制成先前复制的了。因为复制的节点存储方式是bpy.data.selected_nodes,而我们做出选择时修改的是node.select。
被node group 的 socket折磨了半天,它的构造函数创建出来以后,index还会自动变化。。。想了半天如何把它们纠正过来,后来猜突然想到,自己新建一个正确顺序的列表去引用它们就好了。
一个坑:如果有一个dic,for x in dic 得到的将是keys。
2024-07-01 解决节点属性的问题
一直会莫名其妙少属性,今天检查一下白名单的作用。
打印分析时分析的属性,再打印输出的结果属性,发现对不上!有属性没被输出。
破案了!!用来判断是否写结果的语句是if result,因为之前的逻辑中如果不是基础属性就会返回None,但是当result是浮点数0.0,int 0等,也会返回None。。。
对于defaultvalue,minvalue,maxvalue,有一个 BUG:we can change default_value here, but it go back to the default when out of this function!!! WHY WHY WHY???
打印结果显示,set_attrs成功赋值了这些内容,但它们变回原样了。是不是对其它属性的修改会影响这些属性?
破案了!!就是!"socket_type"的重新赋值会把defaultvalue,minvalue,maxvalue变回初始值!啊,真让我好找!
今天还解决了Group INput output在主面板的生成问题。完全就是逆向blender的一些特性嘛……
现在,ng逻辑终于解决!哎!竟然搞了这么久。。。
修复了: # BUG: if the two socket in one node group node have the same name, only one will be linked. for now, we hope the user dont have rename socket…
竟然查api一次就修复好了,开心!
好吧,出bug了,收回我的话。。。。。
2024-07-02 添加引用
对啊!可以用添加引用的方式!!!就是把生成时生成的对象引用,添加进我们的字典。这样从cnode可以直接获取到node,不用那么麻烦地去比较重名什么的了!!!
发现我给inputs, outputs等对象写的特殊对待逻辑越来越多,不太对劲。发现,自己写的万能解析器比我想象的更强大,可以解析它们!重构代码。。。
新设立了一个根据类查找黑名单的逻辑。很不错。
当直接从Group Input拖出link给某个插槽,subtype会变成"",需要手动变成NONE!
2024-07-03
如果color ramp节点,输出有color和alpha,但是我们存的只有一个!简单粗暴把outputs加入黑名单解决了。
解决了查找link idx的问题,直接用==比较tosocket和inputs[i]。这样,重名socket无法连接的问题也就解决了,因为比较的是index。
现在的代码很漂亮,让人一看就觉得:是可拓展的,是正确的,绿色的。
最后剩下一个自带节点panel开关无法设置。但这个不做也行啦。
终于!!NG逻辑全部优化完成,可以写imgae逻辑了。。优化一天,改bug+重构又搞了快一周。。。
image的大概说明:
在json文件中储存softkey,hardkey,并调用parse_attrs万能分析器,分析image的部分指定内容(给分析器加了一个功能,只分析白名单)。
用户存储完一次preset后,即可选中单个图像节点存储图像打开规则。这是通过保留最后一次存储的cobj数据,在用户存储image规则时查找对应cimage实现的。
打算再加一个图像识别系统,可以自动查找需要的图像,如法线等。不知道这样快不快?
2024-07-04
还是不搞图像识别了,因为考虑用户环境,得打包成exe,实在是有点小题大做。
2024-07-05
写了一下存取图像的各个gui,逻辑等。决定将两类key:soft,hard变为只有softkey。这样给人感觉可操作性更大,给人更安心,因为是soft的。用户操作总是想要从感性出发。
2024-07-09
这几天完成了图像存取的全部逻辑。现在完成CRUD系统的改进。
一个bug:在无节点树界面新增预设,ui面板上会诞生一个预设。这是不允许的。
写这个插件遇到的最难的问题就是blender的context可访问性,和增删查改途中缺少对blender数据结构认识而犯的错。第一种清空,blender约束了我们设置在ui中的环境变量只能在ops、ui等特定类中被访问…所以初始化的时候,用户不点击UI或者ops,我们就无法初始化用户第一眼看到的数据。看来需要用一些ugly tricks。。。
好吧,有人问过相同的问题。答案是:You Cant。
https://blender.stackexchange.com/questions/8445/collectionproperty-initialization
但是,下面有人评论说可以用一种hack方法:
It is possible using a hack: a scene update handler that removes itself and adds the required items, although there can be issues if you load another .blend file.
但是我没太懂啊,Application Handlers我知道,具体使用哪个呢。
还有一个问题,UI好丑啊,信息量好大看之不舒服。
bug:重新打开ui,预设变多一个 - 0710已解决
bug:Control G 自动连接单独一个gamma节点,subtype是“”,会报错。只有gamma节点会这样,我觉得是blender的bug。
今天测试了几何节点,发现group input output无法正常连线,simulation zone 无法支持。
测试了合成节点,完美兼容!(可能还有问题没发现
2024-07-10
bug:label readonly。已解决,socket的label不能修改
一个逻辑问题:如果当前相同的NG实际是.001,存的是本体。那么只要本体不一样就会新建,略过了比较.001
CRUD问题彻底解决、UI布局已优化!
接下来解决几何节点问题。
目前,将其他NodeGroupINput用在不属于他们的界面还是会报错。Geometry节点GroupInput无法连线,Group会报错。解决这些这个插件几乎就完成了!
2024-07-11
唉,bpy不能debug,有时候用print不打印不一定是没进入这个逻辑,而是隐式报错了,这个callback函数提前退出了,所以还是要仔细看控制台。
修复bug:选择了node但未选择它parent,apply时报错
优化对NodeGroupInput/output结点的支持。
现在看来,我在generator中调用parser进行比较简直是天才之举!
小bug:有NodeFrame时,生成的节点位置有点问题
不过呢,一些吸取场景数据(object,collection)的socket是无法赋值的。这是这个插件的缺陷
bug:新建的pack中,新建的preset直接向上移动,会导致第一个preset在metadata中记录的名字为""
该bug已修复。认为是blender 对prop collection使用.keys()的bug,改为遍历获取names组成列表就好了。
bug:subtype为""的问题。也认为是blender在Ctrl+G时的bug,使用了额外的if判断来防止其发生,有点ugly。
2024-07-12
发现一个问题,blender有时候撤回得按好几次的原因是很多不合规范的operator使用了’UNDO’选项,这会占用一个撤回位置。所以要尽量少用UNDO以保证正常功能撤回的敏捷性。
今天做了导入导出功能,并使用Blender4.2对插件进行了测试。
修改逻辑:比较NG时,会略过比较节点位置。
码一下blender官方的嵌入module讲解,之后新功能嵌入模块可能会用到:https://docs.blender.org/manual/en/4.2/extensions/python_wheels.html
今天给插件新增了GPL3许可证,写了Readme。
2024-07-13
今天把插件修改成blender官方规范extension格式,再把它传到github,再交给blender审核。终于要发布beta了!
解决了不overwrite情况下,geo group input 连线报错的情况。原因:使用name判断是否是group input了,应该用bl_idname
悲!忘记simulation zone了。。。还有个bug要解决。
PS: 知乎粘贴md格式真恶心
2024-07-14
本来今天要提交,但是在录制介绍视频的时候又出bug了:NG内部父级无法设置。
这是个小bug,于是给他改掉了。关于simulation zone,这也是一个需要设置输入输出socket的东西。由于疏忽没找到api还去blender stackexchange问了一下,今天收到回答解决了。热心网友还帮我修改了一下提问文案,不得不说我的英语相比起来还是比较蹩脚。
今天做了插件宣传图片,见本文封面。打算再把blender的官方示例工程下载下来测试一下插件,就录制发布!真别再拖了!
0.X 版本阶段
2024-07-15 to 07-24 插件首次发布
这10天的总结:15号,插件正式被提交,16号下午收到了工作人员的答复:readme太长,还有注册的scene变量太多,污染了developer extra的custom attributes界面。建议我放进一个PropCollection内。好建议!我修改之后重新提交了。
之后去旅游了。19号还是20号,审核通过!终于上线了!获得了几条五星好评,十分地振奋人心啊!
第一条评论说我的插件很有用,但需要一个电梯演讲。的确,这个插件功能太贴近blender原生内容,基础面太宽了,以至于不好描述特点!更何况,他的创新点在代码层面,实时解析的特点要怎么描述给仅仅对Node系统略知一二的小白呢。。
到25号,插件已经登上Rating排行的第二页!
2024-07-25 to 07-26 更新插件
在评论区的指点下,决定添加Shift A界面。
NodeFrame乱窜的问题终于解决了!
大坑:拖动NodeFrame设置的位置是相对的,不会改变内部节点的location,而是改动NodeFrame。所以,对于NodeFrame,不能应用offset,因为内部的节点已经应用过一遍了!
以及,在应用预设后自动选中NodeFrame的状态下,使用move ops,frame会乱窜,只要在调用ops之前取消选中,再在之后重新选中即可!天才般的trick!
更新了0.2.0版本,增加了Shift + A面板,操作逻辑更加贴近blender添加节点的逻辑。解决了NodeFrame问题。
这样,就真的很像blender原生的操作逻辑了!!太·帅·了!
一天就把version control 和shift A界面干完,我也是挺牛的。。
所以,0.2.0发布了!同时,宣发视频也加了中英双语字幕。无奈我的麦不太好,用AI配音太麻烦了!
2024-07-30 发布0.3.0版本,增加autosave,更新插件不再丢数据
昨天发布了0.2.1版本,fix:控制台输出error信息。
这两天增加了autosave和recover逻辑,把自动保存的文件放在系统temp文件夹。
增加了更新插件时自动重新导入autosave文件的逻辑。现在用户终于不用每次更新都自己导入导出pack文件了。
这类读写过程和跨文件配合多了以后,要十分注意各变量改变的顺序,今天搞了一个bug:由于packs在关闭插件时不会更新,而测试时又要把pack根文件夹总体删除,导致导出了空pack。之前一直再找import逻辑的问题,后来才发现pack是空的,定位到export逻辑。
发现0.2.1 release版的bug:export all时,导出空pack,这也是变量改变顺序带来的坑。
可是0.2.1版本还不支持自动重新导入packs,这导致我不太敢频繁推送更新,因为不知情的用户很可能更新然后丢掉自己保存的数据!所以我决定尽快把0.3.0版本更新好再推送,这样以后更新就少了后顾之忧,不用担心不知情的用户在更新时丢数据了。
不能重新导入packs+export all会导出空pack这两个问题组合起来真是要命,万一用户导出了空包,又更新了,就完蛋了。他会给我差评的!
运营维护实在需要瞻前顾后……
明天再测试一下0.3.0版本。希望能多测试出来一点bug。我可能需要一个内测群,但我没有经历去运营……
好了,又爆肝了一天,把所有逻辑完善并上线了。今天还感冒发烧。。
不过,总算把更新时丢packs的问题解决了。后顾之忧已除,以后可以尽情推送更新了!
2024-08-01
有b友提出了blender oc渲染器使用插件的问题。我查了下,oc定义了很多新的node,显然无法适配……暂时没有精力去做,以后可以研究!作为让万能解析器更加万能的一个好例子。
收到了一个bug,比较严重,在install时会发生:autosave文件夹不存在。这是因为install时走的逻辑漏掉了检测文件夹存在。
这些初始化内容越来越多和分散,决定0.4.0全部放到__init__.py中去。
正在做sync机制,blende开始的sync已经做好。现在在做跨文件sync,方法是检测本地文件修改时间。现在bug多多。
2024-08-02 完成自动更新逻辑
今天完成了自动刷新逻辑。
突然发现,一般这种涉及增删查改的软件,一般都是有“全部”类别的,也就是动态分类。而我的文件是基于文件夹的。在未来版本需要改成单个文件夹作为“全部”,同时把文件夹放进preset的数据里,用来分类,而不是真的用文件夹来分类。
在0.4.0 版本更新后,终于不用来回点击刷新了。
经过Blender StackExchange 上老哥的点拨,终于找到了不用用户点击按钮就能初始化blender注册的变量的方法:
使用bpy.app.timer.register()延迟执行ops。
我在插件注册时这么做了一次,让用户刚进blender就能自动sync。
同时,我在gui的draw函数里放了一个time计时,如果两次gui刷新间隔大于1s,就进行sync检测(检查记录在案的node预设数据修改时间是否和真正的修改时间相同)。因为blender的gui通常会在用户鼠标划过对应界面时执行draw函数,用户时隔1s没把鼠标放在draw上,就有切换文件的可能。这么做感觉有点消耗,而且不能应对极端情况(比如神金用户在1s之间来回切换.blend文件来使用我的插件。。但应该没人这么做,只有我会如此测试)。这已经是目前最好的办法了。
2024-08-03 发布0.4.0,修改Shift A界面,增加Auto Sync
给shift A界面加了寻找所有预设的功能。本想在全部预设界面动态剔除当前pack以节省空间,因为当前pack默认是显示在最前的。但考虑到用户会更加追求固定不变的界面,靠以往的记忆寻找内容。所以还是不搞这个动态界面了。
blender 内置ui,通常context.menu是右键界面,NODE界面下的右键菜单就是NODE_MT_context_menu
2024-08-04 Compositor Color Balance BUG
之前发现了b站up“异次元学者”做了一个和我逻辑几乎一模一样的插件。在他的经验提醒之下(感谢他),发现了Compositor下Color Balance节点颜色赋值错误的bug。它的色彩轮盘似乎有点bug,不按用户选择的颜色进行显示。。。得找个时间修复。近期一直在润色插件的交互,没有动核心逻辑。
2024-08-07 修复曲线节点问题
Gtihub收到issue,曲线节点无法正确生成
测试发现,曲线倒数第二个点赋值会错误,变成1,1.
但是解决办法却不是修改赋值逻辑,而是…把黑名单中的location去掉。因为为了节省效率,之前把location加入了全局黑名单,然而曲线点也有叫做location的属性。这么做实在有失妥当。
这也暴露出我核心逻辑的风险:不断地递归调用让bug很难找,同时全局黑名单只基于属性名。
issue还指出,metadata储存的key无法找到,导致每次刷新都会报错key error。
可能是之前的逻辑漏洞导致metadata里内容出问题了,而又缺少对metadata内容的检测机制。
糟糕啊,万一其他用户都有这个问题怎么办!测试真的很有必要啊!
但是blender插件系统对测试的包容很不友好,新版本上线只能发布,没有测试功能……
我急需一批测试用户!是否需要拉一个QQ群…
2024-08-08
正在修改shift a交互逻辑,目标是将用户的pack罗列在shift a菜单上,并可选择是否放进二级菜单。这个建议是由VictoryLuode提出的,同时异次元学者的插件也更新了此功能,我也觉得这个交互方式更合理。
实现原理是将一个draw函数扩展到shift a菜单,并根据pack数量确定draw函数绘制menu的次数(一个menu代表一个pack)。由于pack数量不定,采用类似对象池的逻辑创建元类,在一个blender进程中注册用户所需的最大pack数量的menu,并动态修改它们的属性来使它们链接到正确的menu库,最后关闭blender时再一并注销。python的动态性还是强大啊。
因为状态参数越定义越多,决定单开一个states脚本,但问题又来了,bpy限制十分的多,使用状态参数就必须要检测状态并做出修改,而非立即修改。
使用状态参数,就要将添加元类这一步推迟到draw中,于是报错:
register_class(...): can't run in readonly state 'HOTNODE_MT_pack_menu_0'
那么由谁来监控状态好呢…
2024-08-12 修改Pack数据结构
最近在考研集训,都没时间写代码哦……好烦,好想写代码,只能晚上挤一点时间出来。
改进了Pack,现在Pack是一个类,packs是一个字典。这样比单纯用字符串列表方便很多,因为有任何属性都可以直接加在类里并获取到,而不用声明一个相同顺序的列表去存储他。
修改了同步检测逻辑。现在任何改动都会更新root_meta的文件更新时间,以此来检测是否同步。现在同步系统和shift a终于不冲突了!
这几天修改了文件结构,把全局变量单独放在了一个文件中,和注册在bl scene中的变量分开,缓解了代码结构混乱的现象。
2024-08-14 debug 动态菜单
今天把pack动态菜单全部debug完了。改pack数据类型真的好麻烦,到处都要改。
这个插件的框架也开始略显不足了,可能等到blender 5.0也就是明年下半年,就要再重构一次了吧。
现在的架构多少有点零碎,完全不符合面向对象的原则,基本就是在文件里各种def+全局变量。
不过,好在4.3alpha能够完美适配。
今天把undo redo开了个头,又是不小的工程量。。。
还有preset选择显示和记录用户configs,也在0.5版本加上吧。
2024-08-17 自定义 Undo Redo 回调如何实现
正在写undo,redo逻辑,研究了一个在undo时回调我们自定义的undo函数的办法。之前搜索没得出方法,现在研究出来以后,刚好也把那位兄弟六年前的问题回答了:
There is a way.
- Register a BoolProperty to the scene, here we call it checker.
In your operator, access checker via context and run checker = not checker. - Define two global bool property: checker_cache and skip_checker_update.
- In the update function of checker, if skip_checker_update is False, sync the checker and the checker_cache.
- Define your own post undo function my_undo_post(scene, ) and a pre undo function my_undo_pre(, _).
- In your pre undo/redo function, set skip_checker_update as True so the blender’s undo/redo logic will change back the checker because it is registed, but the checker_cache will keep unchanged.
- In your post undo/redo function, check if the checker not equals checker_cache, if is, sync them and run your undo logic. Then turn skip_checker_update back to False
- Append your own pre/post undo/redo functions to blender’s. E.g. run bpy.app.handlers.undo_post.append(my_undo_post).
It’s a little complex but basically works.
还发现一个大坑,就算我的OPS没有UNDO选项,以CollectionProperty存在的preset也会被undo,导致报错。。。这意味着只要用户的ctrl z波及了插件,就会报错。。得赶紧修复啊!修复的办法就是增加undo逻辑。。。
2024-08-18 Undo Redo 的回调
undo redo逻辑基本写完了!
用户undo redo由于波及到重载用户的预设文件,用户选择的预设包、预设会回归默认。因此,为了使用户undo redo后重新选择回上一个选择的内容,需要在undo redo中加入特定的callback来选择相应内容。
这里采用了策略模式,在每一步操作生成的step实例中,添加预先定义的选择pack或选择preset的回调函数。
这样子避免了针对每一种操作都定义一种回调函数,也避免了针对每一种操作生成对应的特殊step类,解耦合了step和operators。
这个逻辑实在是太美了!文字力量实在有限,只有亲眼看到我的代码才能理解它有多美。但会看我代码的人也寥寥,所以干脆起兴(赋比兴的兴):
如果要用一句话来概括,那就是——
这就是面向对象!!!!
得赶紧做完新版本。。昨天收到了一个一星评价,说在mac silicon上有问题。。。心情很不好
2024-08-23 多进程历史记录同步的大难题
完美地实现了单文件的undo redo逻辑之后,发现多个blender进程中undo redo会冲突,因为我的插件逻辑是共用一套数据库,实时同步修改。那么在进程A中修改了数据之后,进程B没有这些修改记录,undo会直接撤销修改前的数据,之前的数据已经被修改了,于是就会冲突。
非常非常棘手的问题,感觉根本无从下手。
因为blender 压历史记录的条件是:用户显式调用operator,或显式修改某个属性值。
现在有两个解决方案:
- 将所有操作改为operator实现,再将历史记录存在本地,和blender本身的undo系统分开。这样子就相当于插件的undo系统被独立了出来。然而,要使插件有着好看的ui,就必须用blender的ui模板,就必须注册一些bl属性。那么用户修改这些属性就必定会压记录。。。
- 在进程A修改数据后,如果在进程B撤销内容,会先撤回进程A的修改,再撤回进程B的内容。这么做的前提是能够在进程B实现将A的历史记录数据复制到B,可行,但难度非常非常非常之大!
头快秃了,怎么解决好呢。。。
早上起来,找到了一个夹缝中的解决办法!这个方法跳脱出了必须同步props的限制!!
给每一步step实例加一个discarded状态,检测到文件同步操作就设置之前的step为True。undo时检测到true就跳过我们自己的文件复制逻辑,然后刷新插件。
这个方法的妙处在于,我们不用担心blender undo会自动修改已注册的props,因为我们检测到这个步骤被废弃以后,就会直接刷新插件。而刷新插件意味着我们从文件夹读取了数据并重新设置了所有已注册的props,因此undo被废弃的步骤不会给用户数据带来任何破坏!用户可以undo完被废弃的步骤后,再undo之前其他地方的内容,而不需要被限制undo,从而实现了多开blender时undo redo的进程安全
2024-08-24
0.5.0终于写好了!
又加了VL提到的剔除没有该类preset的空包的功能,原理是加了一个pack_type的数据在metadata里。
加了在UI面板提示info的功能。因为用户撤销没有显式调用ops,不能弹信息。。
还加了个细节:存单个节点,预设名会直接变成该节点的名称。
现在这个插件接近完美了!
接下来主要做一些适配工作,维护稳定性,再补一些python八股,重构一下代码以应对可能到来的5.0 blender版本。八股还是很重要的,代表着系统学习。比如生成器,用在现在的插件代码里肯定能显著提高效率。而我写的时候还不知道有这种东西,全部用的是迭代器…
2024-08-26
今天接到 issue,repeat zone应用报错。原因类似simulation zone,得单独考虑zone output的items。解决办法是增加特殊逻辑,生成时去掉自带的geometry插槽,和解析时去掉只读的color属性。于是把simulation zone的逻辑几乎照搬了一遍就解决了。
这种节点的特殊逻辑还是要避免。目前是为这类节点单独写了特殊逻辑,先凑合着用。以后要并到通用逻辑去。
我的天,刚刚推送了0.5.1版本,立刻发现更新后原来的包无法自动导入!然后锁定问题在metadata的检查上,0.5版本的metadata多了一项pack_types,而这个不在代码的导入检查中,所以就被禁止导入了。赶紧推送了0.5.2版本,但还是有一个倒霉蛋下载了0.5.1。。。
这个事故警示我:得建立一个上线测试的标准流程。其实早该建立了,只是我懒。。
2024-08-29
今天找到了color balance节点颜色设置不正确的解决办法。
问题:发现Lift/Gamma/Gain模式下,Gain的R和G数值被应用成了Offset/Power/Slope模式的Slope的RG数值。
直接原因:设置另一个模式中的颜色数值会牵连修改本模式。
测试得到的办法:只记录一个模式的数据,以确保当下模式的准确。目前措施是只记录LGG模式下的三个颜色数值,这样能确保LGG模式的绝对准确,但OPS模式依然有问题。同样的,只记录OPS模式,LGG模式会有问题。
于是在之前设置好的特殊解析器里,新增了一个解析逻辑,根据模式,删除另一个模式的values。这样能确保用户设置的模式下,rgb数值是准确的。然而,倘若用户同时在两个模式下都修改了一些数值,应用预设时只能保证当前模式是准确的,另一个模式将变得不可预测。这可能是blender的bug。
另一个问题:加载文件时,如果这个文件之前存过presets数据,那么选择的包下会呈现以前的数据,必须手动刷新。
解决办法:注册一个bpy.app.handlers.load_post.append(load_handler)。在load_handler中添加刷新逻辑,以实现每次打开blend文件都刷新一次。
2024-08-30
新坑:旧的blender文件打开,undo redo没效果
推测原因:旧文件没有注册pre undo等函数
解决办法:在load_handler中尝试取消注册这些函数,再注册
2024-09-05
最近把中文翻译做好了,新增了i18n内容。
接到issue,带有Menu Switcher节点报错。这是因为Blender为了Menu的可访问性,增加了可由用户自定义的enum作为menu。
很容易就解决了,但有新问题:interface带menu时,无法设置default value。因为interface要连接到menu节点,才能知道具体有哪些enum,而default value又在连接节点的代码前,导致无法找到enum。目前解决办法比较丑陋,是在setattr时检查是否为SocketMenu,如果是,就跳过设置default value,并现场定义一个函数,作为委托,放进委托队列里,在节点连接完以后处理。
如果直接使用闭包函数,因为闭包找到的是同一地址中父级函数中对应变量最终的值,所以十分不便。
所以,这里采用和history相近的办法,同时定义函数和他需要的params,存进数组中,以便之后调用。
于是成功解决了这个问题。
这个插件为了适配各种节点,乱七八糟的逻辑越来越多,此后面对新issue每次都要加新内容,运行速度会越来越慢。
估计到blender 4.4左右,就要再一次重构核心逻辑,把未来可能发生的情况纳入考虑,构建一个更加长远、可编辑的框架。
现在还是太像早期的敏捷开发了,哪里需要补哪里。
2024-09-07
总是有一些漏网之鱼的node socket,明明是color,解析出来default_value却是float. 通过在setter中加一个except分支,将float扩展成color(list[4])解决了。
现在为了向下兼容,尽量不改parser,否则用户此前的预设就不能跟上版本了。
VL提出file output节点bug,又是一个用户可自定义输入插槽的节点,需要特殊逻辑。。。
BL创建节点会自带一些插槽,有的自带一个,有的自带两个,就很烦!不过可以获取其数量,使通用的“自定义插槽节点”设置器成为可能。这个工作留到重构代码时进行,现在先力求稳定。
2024-09-09
更新了0.6版本并修复了一些bug:
- 跨文件undo失效,原因是import sync 在局部的函数中没有覆盖到所有sync,导致部分用了sync的分支没有导入sync。
- Shift A 会出现不该出现的pack,原因是用户可能用以前的插件,有逻辑漏洞,pack里还有其它类型的文件,但当下插件没有额外的检测逻辑,导致实际有文件,metadata却一直没更新。现在在插件启用时强行检查所有pack并更新metadata来解决该问题。有点消耗。
- 自定义node group panel order问题,原因是设置panel的子socket会导致panel排在顶上,而最后的panel又是最晚被设置子socket的,故而会被拍到最前。通过在最后重新设置panel position解决。
- 一个Bl细节,在选中一个预设并双击改名的界面,再双击另外一个预设,可以不更改选中预设,而改名另一个未选中的预设。原来的逻辑是通过选中idx索引获取当前改名的预设。现在修改为利用name update自带的一个self(preset),当检测到self不是当前选中的时候,检查文件并确认真正要修改的preset name。
- 如果bl文件有一个image,这个image路径缺失了,预设又刚好有同名image,会开启image比较,而缺失的image在比较逻辑里会触发file not found。在比较里加一个check以解决。
接下来实现了VL提出的modifier界面直接运用几何节点预设
VL提出有关快捷键的自定义需求,目前BL有这个功能,VL提出多了就难以管理。目前在需求洞察阶段,如有必要则考虑添加。
VL提出右键可以直接导航到相应菜单和预设,直接覆盖预设,以免每次都在sidebar修改。这个可以,是一个显而易见的需求,添加。
我想到,Shift A + A打开插件菜单,是不是更合理,两次同一快捷键有一种强调的,“Advanced" Node Add 的感觉,也和插件基调相匹配。可以探索一下能不能实现。
坑:pack动态菜单每次注册的类名不一样,导致bl自带的自定义快捷键设置每次会映射到不同的菜单
2024-09-13
更新了0.6.4版本,修复了一些问题:
- 几何节点重复区域、模拟区域的问题(他们的Input节点是需要在output节点设置好后,配对获得相应socket,再设置input的。然而用于延迟设置的列表append语句不知为何消失了。最近经常发现代码莫名其妙消失一句的问题,有点烦人,怎么回事?)
- NodeReroute节点包含在NodeFrame中,应用预设时会乱跑(因为当时为了节省空间,给NR节点单独设置了一个simple parser,解析不了父节点NF,所以NF的相对偏移无法应用。)
- 贴图预设在路径设置不正确时会报错而非默认置空(路径找不到问题)
- 快速保存预设丢失历史记录(没有正确设置undo_callback_param)
现在做新功能有些动力不足,因为这些涉及到非常细分的交互领域,而99%的用户并不会如此深度使用。
的确,好的设计会在细节上下足功夫,推演每一个用户可能做出的行动分支。这种设计就像苹果,能让你使用的时候立刻感觉到:这是上好的设计,但那需要不计成本地投入,也容易陷入钻牛角尖的境界。
所以,在目前达到95分的情况下,暂时休息一段时间,维护现有功能。
还发现NG复用逻辑存在漏洞:有时候会无限创建NG而非复用,原因有2:1、应用预设没有100%成功,某个value没设置好,导致再应用时,预设的value和已经应用出来,并拿去对比的value不一致,从而新建。如果本身有一个叫做NG的不同的ng,应用出来是NG.001,再应用时比较的是NG,而非NG.001,所以会无限创建。这个应该怎么办呢…
2024-09-15 右键管理任意pack预设,一般化访问pack菜单
今天统一了 Pack Menu的注册,现在任何需要 访问packs然后访问presets的菜单,都可以经由一个packs menu然后根据实际功能需求,来触发相应的菜单,如应用预设或修改预设。
经过这个统一,做好了右键添加预设/修改预设到任意pack的功能。
这个pack menu有点绕,这里捋一下防止忘记:
- draw 注册到菜单的 Packs 展开菜单(此时不同的 draw 函数设置了不同的全局draw mode用于选定功能)
- 公共 Packs 菜单,用于访问根据mode确定的 draw packs 函数
- 特定 draw packs 函数,draw 每个 Pack 的公共 preset 菜单
- 公共 preset 菜单,用于访问根据 mode 确定的 draw presets 函数
- 特定 draw presets 函数,draw 每个 Preset.
即使这样写也十分抽象,因此还得看源码。
2024-09-22 pack icon功能
加了给pack设置icon的功能
发现很需要一个框架。
当前功能实现,需要写一个把icon写入metadata的逻辑,同时为了ui适配,需要统计含icon的pack数量,这需要在有修改时状态变量,以避免每次都deep读一次文件。
icon属性倒是在pack类中新增一个就好了,但文件和状态变量就面临上述问题。
因此,在下一次重构中,需要有属性的crud模块,状态变量的模块,而不是每次有需要再单独写