├── node_align ├── 未用到的图片 │ ├── 上对齐.png │ ├── 下对齐.png │ ├── 右对齐.png │ ├── 左对齐.png │ ├── 直线-瞄准.png │ ├── 上下居中对齐.png │ ├── 上对齐-渐变.png │ ├── 上对齐-蓝色.png │ ├── 下对齐-蓝色.png │ ├── 右对齐-蓝色.png │ ├── 垂直等距分布1.png │ ├── 垂直等距分布2.png │ ├── 左右居中对齐.png │ ├── 左对齐-蓝色.png │ ├── 水平等距分布1.png │ ├── 水平等距分布2.png │ ├── 直线-蓝色箭头.png │ ├── 直线-蓝色虚线.png │ ├── 上下居中对齐-蓝色.png │ ├── 垂直等距分布-蓝色.png │ ├── 垂直等距分布-蓝黑.png │ ├── 垂直等距分布-青色.png │ ├── 左右居中对齐-蓝色.png │ ├── 水平等距分布-蓝色.png │ ├── 水平等距分布-蓝黑.png │ ├── 水平等距分布-青色.png │ └── 直线-橙色虚线-原图.png ├── icons │ ├── 网格-绿方块.png │ ├── 网格-蓝方块.png │ ├── 网格-蓝线框.png │ ├── 上对齐-蓝双色.png │ ├── 下对齐-蓝双色.png │ ├── 右对齐-蓝双色.png │ ├── 垂直等距分布-蓝橙.png │ ├── 左对齐-蓝双色.png │ ├── 水平等距分布-蓝橙.png │ ├── 直线-橙色虚线.png │ ├── 上下居中对齐-蓝双色.png │ ├── 垂直等距分布-蓝双色.png │ ├── 左右居中对齐-蓝双色.png │ └── 水平等距分布-蓝双色.png ├── blender_manifest.toml ├── README.md ├── translator.py └── __init__.py ├── group_input_helper ├── icons │ ├── 空.png │ ├── 图像.png │ ├── 字符串.png │ ├── 布尔.png │ ├── 整数.png │ ├── 旋转.png │ ├── 材质.png │ ├── 浮点.png │ ├── 物体.png │ ├── 着色器.png │ ├── 矢量.png │ ├── 矩阵.png │ ├── 纹理.png │ ├── 菜单.png │ ├── 集合.png │ ├── 颜色.png │ └── 几何数据.png ├── blender_manifest.toml └── translator.py ├── named_attribute_list ├── icons │ ├── 域-布尔.png │ ├── 域-整数.png │ ├── 域-旋转.png │ ├── 域-浮点.png │ ├── 域-矢量.png │ ├── 域-矩阵.png │ └── 域-颜色.png ├── blender_manifest.toml ├── my_dataclass.py └── translator.py ├── node_socket_location ├── blender_manifest.toml ├── test_bpy.py ├── __init__.py ├── 获取接口位置-无注释.py └── 获取接口位置_精简版本.py ├── VoronoiLinker ├── blender_manifest.toml ├── 无用 │ ├── 我做的.py │ └── 长注释.py ├── utils_color.py ├── utils_translate.py ├── v_Dummy_tool.py ├── common_forward_func.py ├── v_CallNodePie.py ├── v_ResetNode_tool.py ├── utils_ui.py ├── v_LinksTransfer_tool.py ├── v_PreviewAnchor_tool.py ├── rot_or_mat_convert.py ├── v_LinkRepeating_tool.py ├── v_Warper_tool.py ├── utils_solder.py ├── v_MassLinker_tool.py ├── v_QuickDimensions_tool.py ├── v_Swapper_tool.py ├── C_Structure.py ├── v_QuickConstant.py ├── v_RantoTool.py └── v_Ranto_tool.py ├── node_utilities ├── show_node_editor_center_lines │ ├── blender_manifest.toml │ └── translator.py ├── node_group_path_navigator │ ├── blender_manifest.toml │ └── translator.py ├── show_gn_total_execution_time_in_header_and_editor │ ├── blender_manifest.toml │ └── translator.py ├── 小王-节点属性快速获取.py ├── 小王-复制节点和驱动器.py └── 小王-节点叠加层预览属性.py ├── .md ├── README.md └── 小王-切换N面板 └── __init__.py /node_align/未用到的图片/上对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/上对齐.png -------------------------------------------------------------------------------- /node_align/未用到的图片/下对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/下对齐.png -------------------------------------------------------------------------------- /node_align/未用到的图片/右对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/右对齐.png -------------------------------------------------------------------------------- /node_align/未用到的图片/左对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/左对齐.png -------------------------------------------------------------------------------- /node_align/icons/网格-绿方块.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/网格-绿方块.png -------------------------------------------------------------------------------- /node_align/icons/网格-蓝方块.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/网格-蓝方块.png -------------------------------------------------------------------------------- /node_align/icons/网格-蓝线框.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/网格-蓝线框.png -------------------------------------------------------------------------------- /node_align/未用到的图片/直线-瞄准.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/直线-瞄准.png -------------------------------------------------------------------------------- /group_input_helper/icons/空.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/空.png -------------------------------------------------------------------------------- /node_align/icons/上对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/上对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/下对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/下对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/右对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/右对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/垂直等距分布-蓝橙.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/垂直等距分布-蓝橙.png -------------------------------------------------------------------------------- /node_align/icons/左对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/左对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/水平等距分布-蓝橙.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/水平等距分布-蓝橙.png -------------------------------------------------------------------------------- /node_align/icons/直线-橙色虚线.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/直线-橙色虚线.png -------------------------------------------------------------------------------- /node_align/未用到的图片/上下居中对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/上下居中对齐.png -------------------------------------------------------------------------------- /node_align/未用到的图片/上对齐-渐变.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/上对齐-渐变.png -------------------------------------------------------------------------------- /node_align/未用到的图片/上对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/上对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/下对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/下对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/右对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/右对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/垂直等距分布1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/垂直等距分布1.png -------------------------------------------------------------------------------- /node_align/未用到的图片/垂直等距分布2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/垂直等距分布2.png -------------------------------------------------------------------------------- /node_align/未用到的图片/左右居中对齐.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/左右居中对齐.png -------------------------------------------------------------------------------- /node_align/未用到的图片/左对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/左对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/水平等距分布1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/水平等距分布1.png -------------------------------------------------------------------------------- /node_align/未用到的图片/水平等距分布2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/水平等距分布2.png -------------------------------------------------------------------------------- /node_align/未用到的图片/直线-蓝色箭头.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/直线-蓝色箭头.png -------------------------------------------------------------------------------- /node_align/未用到的图片/直线-蓝色虚线.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/直线-蓝色虚线.png -------------------------------------------------------------------------------- /group_input_helper/icons/图像.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/图像.png -------------------------------------------------------------------------------- /group_input_helper/icons/字符串.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/字符串.png -------------------------------------------------------------------------------- /group_input_helper/icons/布尔.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/布尔.png -------------------------------------------------------------------------------- /group_input_helper/icons/整数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/整数.png -------------------------------------------------------------------------------- /group_input_helper/icons/旋转.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/旋转.png -------------------------------------------------------------------------------- /group_input_helper/icons/材质.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/材质.png -------------------------------------------------------------------------------- /group_input_helper/icons/浮点.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/浮点.png -------------------------------------------------------------------------------- /group_input_helper/icons/物体.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/物体.png -------------------------------------------------------------------------------- /group_input_helper/icons/着色器.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/着色器.png -------------------------------------------------------------------------------- /group_input_helper/icons/矢量.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/矢量.png -------------------------------------------------------------------------------- /group_input_helper/icons/矩阵.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/矩阵.png -------------------------------------------------------------------------------- /group_input_helper/icons/纹理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/纹理.png -------------------------------------------------------------------------------- /group_input_helper/icons/菜单.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/菜单.png -------------------------------------------------------------------------------- /group_input_helper/icons/集合.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/集合.png -------------------------------------------------------------------------------- /group_input_helper/icons/颜色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/颜色.png -------------------------------------------------------------------------------- /node_align/icons/上下居中对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/上下居中对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/垂直等距分布-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/垂直等距分布-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/左右居中对齐-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/左右居中对齐-蓝双色.png -------------------------------------------------------------------------------- /node_align/icons/水平等距分布-蓝双色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/icons/水平等距分布-蓝双色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/上下居中对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/上下居中对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/垂直等距分布-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/垂直等距分布-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/垂直等距分布-蓝黑.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/垂直等距分布-蓝黑.png -------------------------------------------------------------------------------- /node_align/未用到的图片/垂直等距分布-青色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/垂直等距分布-青色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/左右居中对齐-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/左右居中对齐-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/水平等距分布-蓝色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/水平等距分布-蓝色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/水平等距分布-蓝黑.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/水平等距分布-蓝黑.png -------------------------------------------------------------------------------- /node_align/未用到的图片/水平等距分布-青色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/水平等距分布-青色.png -------------------------------------------------------------------------------- /node_align/未用到的图片/直线-橙色虚线-原图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/node_align/未用到的图片/直线-橙色虚线-原图.png -------------------------------------------------------------------------------- /group_input_helper/icons/几何数据.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/group_input_helper/icons/几何数据.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-布尔.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-布尔.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-整数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-整数.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-旋转.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-旋转.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-浮点.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-浮点.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-矢量.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-矢量.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-矩阵.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-矩阵.png -------------------------------------------------------------------------------- /named_attribute_list/icons/域-颜色.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunkezengren/Blender-addons-Node-Utilities/HEAD/named_attribute_list/icons/域-颜色.png -------------------------------------------------------------------------------- /node_socket_location/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "node_socket_location" 3 | version = "2.7.1" 4 | name = "node_socket_location" 5 | tagline = "node_socket_location" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /VoronoiLinker/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "voronoi_linker" 3 | version = "5.6.9" 4 | name = "Voronoi Linker" 5 | tagline = "无敌高效" 6 | maintainer = "ugorek;W_Cloud" 7 | type = "add-on" 8 | tags = ["Node"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://github.com/neliut/VoronoiLinker" 12 | # website = "https://space.bilibili.com/1109241880" -------------------------------------------------------------------------------- /node_align/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "node_align" 3 | version = "3.1.2" 4 | name = "Node Align(节点对齐)" 5 | tagline = "Shift+Q|Ctrl+Q Pie menu-Align Nodes in Multiple Ways(多种方式对齐节点)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /group_input_helper/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "group_input_helper" 3 | version = "2.9.1" 4 | name = "Group input helper(节点组输入助手)" 5 | tagline = "Qucik add/split/merge Group Input node/socket(快速添加拆分合并移动组输入节点接口)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node", "Geometry Nodes"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /named_attribute_list/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "named_attribute_list" 3 | version = "2.8.2" 4 | name = "Named Attribute List(命名属性列表)" 5 | tagline = "Quickly Add Attribute Node in GN and Shader(几何节点和材质里快速添加属性节点)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node", "Geometry Nodes"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /node_utilities/show_node_editor_center_lines/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "show_node_editor_center_lines" 3 | version = "1.3.0" 4 | name = "node_editor_center_lines(节点界面中心线)" 5 | tagline = "show_node_editor_center_lines(节点界面中心线)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node", "Geometry Nodes"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /node_utilities/node_group_path_navigator/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "node_group_path_navigator" 3 | version = "2.4.0" 4 | name = "node_group_path_navigator(组路径导航)" 5 | tagline = "Qucik add/split/merge Group Input node/socket(快速添加拆分合并移动组输入节点接口)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node", "Geometry Nodes"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /node_utilities/show_gn_total_execution_time_in_header_and_editor/blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "gn_total_execution_time" 3 | version = "1.3.0" 4 | name = "show_gn_total_execution_time_in_header_and_editor(几何节点耗时显示在标题栏和界面)" 5 | tagline = "Qucik add/split/merge Group Input node/socket(快速添加拆分合并移动组输入节点接口)" 6 | maintainer = "W_Cloud(一尘不染)" 7 | type = "add-on" 8 | tags = ["Node", "Geometry Nodes"] 9 | blender_version_min = "4.2.0" 10 | license = ["SPDX:GPL-3.0-or-later"] 11 | website = "https://space.bilibili.com/1109241880" 12 | -------------------------------------------------------------------------------- /VoronoiLinker/无用/我做的.py: -------------------------------------------------------------------------------- 1 | # 小王 transfer value 2 | # 小王 允许同时连接多个input 接口 3 | # 小王 唤起位置偏移 4 | # 小王 想让预览器自动激活 5 | # 小王 绘制颜色加深 6 | # 小王 解决 Ctrl Shift E / Ctrl E / Alt E 等显示太浅 7 | # 小王 这个更像影响全体 这里使得Ctrl Shift E / Ctrl E / Alt E 等显示太浅 8 | # 小王 这样更舒服,在输入或输出接口方面加强 9 | # 小王 这里也能更改颜色 10 | # 小王 额外绘制 11 | # 小王 饼菜单颜色条宽度 12 | # 小王-npr预览 13 | # 小王-优化-绘制节点组名字 14 | # 小王-只对有选项的节点绘制-导致节点组和bake之类的失效 15 | # 小王-Alt D 支持的接口 16 | # 小王-Alt D 旋转接口 17 | # 小王-Alt D 字符串接口 18 | # 小王-Alt D 矩阵接口 19 | # 小王-工具提示 20 | # 小王-折叠的节点同样绘制 21 | # 小王-支持交换接口 22 | # 小王-新建接口 23 | # 小王-新建接口-捕捉属性 烘焙 菜单切换 24 | # 小王-更改接口名称 25 | # 小王-模式名匹配 26 | # 小王-混合饼菜单对比较节点的额外支持 27 | # 小王-绘制工具提示 28 | # 小王-自动隐藏接口优化-inline 29 | # 小王-自动隐藏接口优化-旋转接口 30 | # 小王-判断节点是否有下拉列表 31 | # 小王-显示节点选项优化 32 | # 小王-显示节点选项优化-根据选项重命名节点-domain 33 | # 小王-隐藏接口值-节点组 34 | # 小王-新接口类型的Mix饼菜单 35 | # _ todo 补全工具名称提示 36 | # _ todo 接口1移到接口2上 FLIP模式,在两个接口绘制名后加上 接口1 接口2 37 | # _ 粘贴接口名,只支持那几个特定的 38 | # _ 浮点饼菜单 to 弧度 角度 39 | # _ 自动隐藏显示接口后,扩展接口不显示 40 | # _ TODO 交换接口 复制粘贴接口名 扩展接口 CTRl Shift A Alt Shift A 41 | 42 | # TODO 没面板的组输入和节点组,插入接口才符合顺序 43 | # TODO 快速数学运算,在偏好设置里加个选项,如果连满了两个接口,是否hide 44 | # TODO 整数运算饼菜单 45 | # TODO 旋转 快速切换饼菜单 46 | # _ TODO 矩阵 快速切换饼菜单 47 | # TODO 切换浮点整数矢量运算 -------------------------------------------------------------------------------- /node_socket_location/test_bpy.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from pprint import pprint 3 | 4 | def print_bpy(): 5 | modules = [ 6 | bpy, 7 | bpy.ops, 8 | # bpy.utils, 9 | bpy.types, 10 | bpy.props, 11 | bpy.path, 12 | bpy.msgbus, 13 | ] 14 | # for module in modules: 15 | # print(module) 16 | # print(f"{type(module)=}") 17 | # print(f"\ndir({module})") 18 | # print(dir(module)) 19 | # pprint(module.__dict__) 20 | 21 | datas = [ 22 | bpy.context, 23 | bpy.data, 24 | bpy.app, 25 | ] 26 | # for data in datas: 27 | # print(data) 28 | # print(f"{type(data)=}") 29 | # # print(f"\ndir({data})") 30 | # # print(dir(data)) 31 | # # pprint(data.__dict__) # 没 __dict__ 32 | # # pprint(type(data).__slot__) # 没, 虽然 '__slots__': (), 仍会采用 slotted 模式来创建实例,实例对象不会被分配 __dict__ 字典 33 | # pprint(type(data).__dict__) 34 | 35 | 36 | # bpy.data/app/context object has no attribute '__dict__',因为使用了__slot__ 类属性 37 | # 像 context 和 data 这样的核心对象在 Blender 运行期间是长期存在的,而且它们的结构是固定的,不需要用户在运行时动态添加属性。 38 | # 使用 __slots__ 可以减少内存占用并提高访问效率。 39 | # 类 __dict__ 和实例 __dict__ 是两个完全独立、用途也截然不同的字典。 40 | 41 | # print(bpy.utils.previews) 42 | 43 | -------------------------------------------------------------------------------- /node_align/README.md: -------------------------------------------------------------------------------- 1 | # Blender-align-node 2 | 节点多种方便对齐分布方式 3 | ## 安装方式 4 | ![image](https://github.com/yunkezengren/Blender-align-node/assets/98995559/5a2d8163-e825-4b93-8860-a02652a390da) 5 | ### 下载压缩包和正常插件安装方式一样 6 | ![image](https://github.com/yunkezengren/Blender-align-node/assets/98995559/4b5b6687-229a-494d-92f5-edb3ac06a810) 7 | #### 之前不能直接安装压缩包原因是我压缩方式的问题 8 | 根据https://github.com/3DSinghVFX/align_nodes 代码框架修改添加(十分感谢) 9 | 10 | 1.选中节点可以直接对齐(不需要特意选一个活动节点) 11 | 12 | 2.增加了一些对齐方式 13 | 14 | 3.对齐可以保持节点的顺序 15 | 16 | 4.拉直节点之间连线 17 | 18 | 两个饼菜单快捷键分别为Shift Q | Ctrl Q,具体可以在偏好设置-键位映射-按键绑定-搜索 Shift Q 然后修改 19 | 20 | ![对齐饼菜单](https://github.com/yunkezengren/Blender-align-node/assets/98995559/61279459-67f0-4141-a7da-447cdbd05a35) 21 | ![分布饼菜单](https://github.com/yunkezengren/Blender-align-node/assets/98995559/fed572e1-5956-432b-b789-22120c8b3a63) 22 | 23 | #### 拉直连线 24 | ![image](https://github.com/yunkezengren/Blender-align-node/assets/98995559/9b3de54d-5679-4a6a-b854-17ccb22726fa) 25 | 26 | #### 两种网格分布 27 | ![两种网格分布](https://github.com/yunkezengren/Blender-align-node/assets/98995559/868a4db2-27b0-4705-8028-ca6136025cd6) 28 | 29 | 30 | 巨大不足:涉及到节点frame框架,不好对齐,只能先去掉frame,排列好再加上 31 | 对别的一些情况效果不佳(比如挨得特别紧的,节点框架frame用的多的) 32 | 33 | ### 最新版已经解决节点在Frame里很难对齐的问题 34 | 35 | 主要是在新建节点树时让节点树始终整洁,而不是把一个杂乱节点树瞬间对好(这种可以尝试内置的对齐插件或选中部分比较有规律的杂乱节点分批对齐) 36 | -------------------------------------------------------------------------------- /VoronoiLinker/utils_color.py: -------------------------------------------------------------------------------- 1 | from mathutils import Vector as Color4 2 | import bpy 3 | from bpy.types import NodeSocket 4 | 5 | const_float4 = tuple[float, float, float, float] 6 | 7 | def power_color4(arr: const_float4, *, pw=1/2.2) -> const_float4: 8 | # return (arr[0]**pw, arr[1]**pw, arr[2]**pw, arr[3]**pw) || map(lambda a: a**pw, arr) 9 | return tuple(i**pw for i in arr) 10 | 11 | def opaque_color4(c, *, alpha=1.0) -> const_float4: 12 | return (c[0], c[1], c[2], alpha) 13 | 14 | def clamp_color4(c) -> const_float4: 15 | return (max(c[0], 0), max(c[1], 0), max(c[2], 0), max(c[3], 0)) 16 | 17 | def get_color_black_alpha(c: Color4, *, pw: float) -> float: 18 | # (R, G, B)最大值通常代表了它的亮度, 亮 转 暗 19 | # return ( 1.0 - max(max(c[0], c[1]), c[2]) )**pw 20 | return ( 1 - max(c[:3]) )**pw 21 | 22 | def get_sk_color(sk: NodeSocket): 23 | if sk.bl_idname=='NodeSocketUndefined': 24 | return (1.0, 0.2, 0.2, 1.0) 25 | elif hasattr(sk,'draw_color'): 26 | # 注意: 如果需要摆脱所有 `bpy.` 并实现所有 context 的正确路径, 那么首先要考虑这个问题. 27 | return sk.draw_color(bpy.context, sk.node) 28 | elif hasattr(sk,'draw_color_simple'): 29 | return sk.draw_color_simple() 30 | else: 31 | return (1, 0, 1, 1) 32 | 33 | def get_sk_color_safe(sk: NodeSocket) -> const_float4: # 不从插槽获取透明度; 并去掉插槽可能存在的负值. 34 | return opaque_color4(clamp_color4(get_sk_color(sk))) -------------------------------------------------------------------------------- /.md: -------------------------------------------------------------------------------- 1 | The conventional method is too slow: Adding named attribute node, selecting attributes or entering text in the name socket, and sometimes change option. 2 | For me, named attribute node take up too much space, and I always hide option and socket that are not needed. 3 | 4 | ## Named Attribute List(2/Shift+2) 5 | 6 | Used in Geometry Nodes and Shader Nodes. 7 | Try to list all available named attributes. 8 | Can also list Vertex Groups, UV Maps, Color Attributes, and Object Attributes. 9 | 10 | ## Quick Add Named Attribute Nodes(Ctrl+2) 11 | 12 | Used in Geometry Nodes. 13 | Select the Stored Named Attribute node and press the shortcut key to quickly add the corresponding Named Attribute node. 14 | 15 | ## ToDo List 16 | 17 | If there is feedback, it might get done sooner. 18 | 19 | - One-press quickly add the corresponding attributes for the selected Stored Named Attribute nodes. 20 | - Rename Attribute Name: Change the name socket value of the corresponding Stored Named Attribute nodes and Named Attribute nodes with a single click. 21 | 22 | --- 23 | 24 | 常规方法太慢: 添加已命名属性节点,在名称接口里选择属性或输入文本,有时还要更改选项. 25 | 对我来说,命名属性节点占空太大,我总是隐藏选项,隐藏用不到的接口. 26 | 27 | ## 命名属性列表(2/Shift+2) 28 | 29 | 可以在几何节点和材质节点里使用. 30 | 尽量列出可用命名属性. 31 | 还可以列出 顶点组 UV贴图 颜色属性 物体属性. 32 | 33 | ## 快速添加命名属性节点(Ctrl+2) 34 | 35 | 在几何节点里使用. 36 | 选中存储命名属性节点,按下快捷键快速添加相应命名属性节点 37 | 38 | ## 待办 39 | 40 | 如果有反馈的话可能会早点做 41 | 42 | - 一键快速添加选中的存储命名属性节点们对应的属性 43 | - 重命名属性名: 一键更改属性对应存储属性节点和命名属性节点的名称接口值 44 | -------------------------------------------------------------------------------- /VoronoiLinker/无用/长注释.py: -------------------------------------------------------------------------------- 1 | # 或许某天应该在工具属性里添加一个按键, 用来在工具运行时修改其行为, 比如 VQDT 的 Alt+D 操作中的 Alt 选项. 现在对 VWT 来说这个功能更重要了. 2 | # 在注释中, "редактор типа"(编辑器类型) 和 "тип дерева"(节点树类型) 是同义词; 指的是4种经典的内置编辑器, 也就是那几种节点树类型. 3 | 4 | # 对于某些工具, 存在一些彼此相同的常量, 但它们有自己的前缀; 这样做是为了方便, 以免"借用"其他工具的常量. 5 | 6 | # VL 当前需要但(可能)只能通过非公开API实现的需求: 7 | # 1. GeoViewer 是否处于活动状态 (通过标题判断) 和/或当前是否正在积极预览? (在底层判断, 而不是从电子表格读取) 8 | # 2. 在编辑器上下文中, 明确判断用户是通过哪个上层节点进入当前节点组的. 9 | # 3. 如何区分通用的类枚举(enum)和特定于某个节点的独有枚举? 10 | # 4. 更改几何节点 Viewer 预览的字段类型. 11 | # 5. 插槽(socket)布局的高度 (自从我添加了 Draw Socket Area 后, 我早就后悔了, 只有美学价值能让它免于被删除). 12 | # 6. 通过API新创建的接口现在必须遍历所有现有的节点树, 寻找它的"实例"来设置 `default_value`, 模拟传统的非API方式. 13 | # 7. 完全访问接口面板及其所有功能. 参见 |4|. 14 | 15 | # 插件在其他插件节点树中的(理论)用处表 (默认--有用): 16 | # VLT, VRT, VST, VHT, VMLT, VEST, VLRT, VLTT, VWT, VRNT 17 | # VPT, VPAT 部分有用 18 | # VMT, VQMT, VQDT, VLNST 没用 19 | # VICT 绝对没用! 20 | 21 | 22 | # 本插件代码中的命名约定: 23 | # sk -- 插槽(socket) 24 | # skf -- 插槽接口(socket-interface) 25 | # skin -- 输入插槽 (ski) 26 | # skout -- 输出插槽 (sko) 27 | # skfin -- 输入插槽接口 28 | # skfout -- 输出插槽接口 29 | # skfa -- 节点树的接口集合 (tree.interface.items_tree), 包括 simrep 30 | # skft -- 节点树的接口基础 (tree.interface) 31 | # nd -- 节点(node) 32 | # rr -- 重路由节点(reroute) 33 | ## 34 | # blid -- bl_idname 35 | # blab -- bl_label 36 | # dnf -- identifier 37 | ## 38 | # 未使用的变量名前加 "_下划线". 39 | 40 | # 在这里留下我的个人"愿望清单"的一小部分 (按集成时间顺序), 这些是从我其他的个人插件移植到 VL 的: 41 | # Hider, QuckMath 和 JustMathPie, Warper, RANTO 42 | 43 | # 在 bl_info 里放我的 GitHub 链接当然很酷, 但最好还是明确提供一些联系方式: 44 | # coaltangle@gmail.com 45 | # ^ 我的邮箱. 如果万一发生世界末日, 或者这个 VL-考古-发现能够解决一个非多项式问题, 就写信到那里. 46 | # 为了更实时的交流 (首选) 以及关于 VL 及其代码的问题, 请在我的 Discord 上找我 'ugorek#6434'. 47 | # 另外, 在 blenderartists.org 上也有一个帖子 blenderartists.org/t/voronoi-linker-addon-node-wrangler-killer 48 | -------------------------------------------------------------------------------- /VoronoiLinker/utils_translate.py: -------------------------------------------------------------------------------- 1 | from .globals import dict_vlHhTranslations 2 | from .globals import * 3 | from .common_forward_func import Prefs 4 | 5 | 6 | class TranClsItemsUtil(): 7 | def __init__(self, tup_items): 8 | if type(tup_items[0])==tuple: 9 | self.data = dict([(li[0], li[1:]) for li in tup_items]) 10 | else: 11 | self.data = tup_items 12 | def __getattr__(self, att): 13 | if type(self.data)==tuple: 14 | match att: 15 | case 'name': 16 | return self.data[0] 17 | case 'description': 18 | return self.data[1] 19 | assert False 20 | else: 21 | return TranClsItemsUtil(self.data[att]) #`toolProp.ENUM1.name` 22 | def __getitem__(self, key): 23 | return TranClsItemsUtil(self.data[key]) #`toolProp['ENUM1'].name` 24 | 25 | class TranAnnotFromCls(): 26 | def __init__(self, annot): 27 | self.annot = annot 28 | def __getattr__(self, att): 29 | result = self.annot.keywords[att] 30 | return result if att!='items' else TranClsItemsUtil(result) 31 | def GetAnnotFromCls(cls, key): # 原来它们藏在这里, 在注解(annotations)里. 我都快放弃希望了, 以为必须手动一个个写了. 😂 32 | return TranAnnotFromCls(cls.__annotations__[key]) 33 | 34 | 35 | class VlTrMapForKey(): 36 | def __init__(self, key: str, *, tc='a'): 37 | self.key = key 38 | self.data = {} 39 | self.tc = tc 40 | def __enter__(self): 41 | return self.data 42 | def __exit__(self, *_): 43 | for dk, dv in self.data.items(): 44 | dict_vlHhTranslations[dk]['trans'][self.tc][self.key] = dv 45 | 46 | 47 | def GetPrefsRnaProp(att, inx=-1): 48 | prefsTran = Prefs() 49 | prop = prefsTran.rna_type.properties[att] 50 | return prop if inx==-1 else getattr(prop,'enum_items')[inx] -------------------------------------------------------------------------------- /VoronoiLinker/v_Dummy_tool.py: -------------------------------------------------------------------------------- 1 | from .v_tool import * 2 | from .globals import * 3 | from .utils_ui import * 4 | from .utils_node import * 5 | from .utils_color import * 6 | from .utils_solder import * 7 | from .utils_drawing import * 8 | from .utils_translate import * 9 | from .common_forward_func import * 10 | from .common_forward_class import * 11 | from .v_tool import VoronoiToolSk 12 | 13 | 14 | class VoronoiDummyTool(VoronoiToolSk): # 快速便捷地添加新工具的模板 15 | bl_idname = 'node.voronoi_dummy' 16 | bl_label = "Voronoi Dummy" 17 | usefulnessForCustomTree = True 18 | isDummy: bpy.props.BoolProperty(name="Dummy", default=False) 19 | def CallbackDrawTool(self, drata): 20 | TemplateDrawSksToolHh(drata, self.fotagoSk) 21 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 22 | self.fotagoSk = None 23 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 24 | nd = ftgNd.tar 25 | if nd.type=='REROUTE': 26 | continue 27 | list_ftgSksIn, list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=0) 28 | ftgSkIn = list_ftgSksIn[0] if list_ftgSksIn else None 29 | ftgSkOut = list_ftgSksOut[0] if list_ftgSksOut else None 30 | self.fotagoSk = MinFromFtgs(ftgSkOut, ftgSkIn) 31 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk, flag=False) 32 | break 33 | #todo0NA Я придумал что делать с концепцией, когда имеются разные критерии от isFirstActivation'а, и второй находится сразу рядом после первого моментально. Явное (и насильное) сравнение на своего и отмена. 34 | def MatterPurposePoll(self): 35 | return not not self.fotagoSk 36 | def MatterPurposeTool(self, event, prefs, tree): 37 | sk = self.fotagoSk.tar 38 | sk.name = sk.name if (sk.name)and(sk.name[0]=="\"") else f'"{sk.name}"' 39 | sk.node.label = "Hi i am vdt. See source code" 40 | VlrtRememberLastSockets(sk if sk.is_output else None, None) 41 | def InitTool(self, event, prefs, tree): 42 | self.fotagoSk = None 43 | @staticmethod 44 | def LyDrawInAddonDiscl(col, prefs): 45 | LyAddNiceColorProp(col, prefs,'vdtDummy') 46 | @classmethod 47 | def BringTranslations(cls): 48 | pass 49 | -------------------------------------------------------------------------------- /named_attribute_list/my_dataclass.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional 2 | from dataclasses import dataclass, field 3 | 4 | # field(default_factory=list) 作用:为数据类的每个新实例/对象 创建一个全新的、独立的空列表作为默认值。 5 | # 创建一个 Attr_Info 对象时,如果没给 domain_info 传值,它都会得到一个属于自己的、新的空列表。 6 | # 默认值只在函数/类定义时创建一次;所有实例共享同一个默认值;修改一个,影响所有 7 | 8 | @dataclass 9 | class Attr_Info: 10 | # 必选参数 11 | data_type: str 12 | """ ## data_type提示 """ 13 | 14 | domain: list[str] 15 | 16 | domain_info: list[str] = field(default_factory=list) 17 | """ ### todo 应该可以删掉 domain_info,在需要的地方再 domain -> domain_info """ 18 | 19 | # 对于 list或dict这样的可变类型,必须这样, 直接写domain:list=[], 所有实例都会共享同一个列表 20 | group_name: Union[str, list[str]] = field(default_factory=list) 21 | 22 | group_node_name: list[str] = field(default_factory=list) 23 | 24 | group_name_parent: list[str] = field(default_factory=list) 25 | 26 | node_name: list[str] = field(default_factory=list) 27 | 28 | # "可能存在"的属性,Optional[bool] 是 Union[bool, None] 的简写 29 | if_instanced: Optional[bool] = None 30 | 31 | info: Optional[str] = None 32 | 33 | Attr_Dict = dict[str, Attr_Info] 34 | 35 | _example = { 36 | '-Value0': { 37 | 'data_type': 'FLOAT', 38 | 'domain': ['EDGE'], 39 | 'domain_info': ['边'], 40 | 'group_name': ['测试.001'], 41 | 'group_name_parent': ['顶层节点树无父级/wrapper'], 42 | 'group_node_name': ['当前group是顶层节点树/Group'], 43 | 'if_instanced': False, 44 | 'node_name': ['Store Named Attribute.007'] 45 | }, 46 | '1111111': { 47 | 'data_type': 'FLOAT', 48 | 'domain': ['POINT'], 49 | 'domain_info': ['点'], 50 | 'group_name': '不确定' 51 | }, 52 | 'Group': { 53 | 'data_type': 'FLOAT', 54 | 'domain': ['POINT'], 55 | 'domain_info': ['点'], 56 | 'group_name': '物体属性', 57 | 'info': '顶点组' 58 | }, 59 | '查找': { 60 | 'data_type': 'INT', 61 | 'domain': ['POINT', 'POINT'], 62 | 'domain_info': ['点', '点'], 63 | 'group_name': ['测试.001', '测试.001'], 64 | 'group_name_parent': ['顶层节点树无父级/wrapper', '顶层节点树无父级/wrapper'], 65 | 'group_node_name': ['当前group是顶层节点树/Group', '当前group是顶层节点树/Group'], 66 | 'if_instanced': False, 67 | 'node_name': ['Store Named Attribute.008', 'Store Named Attribute.011'] 68 | }, 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > 3 | > # 描述过时懒得改 4 | 5 | > [!IMPORTANT] 6 | > 7 | > **VoronoiLinker修改的[neliut/VoronoiLinker](https://github.com/neliut/VoronoiLinker),支持Blender最新版本** 8 | 9 | > [!TIP] 10 | 11 | > [!WARNING] 12 | 13 | > [!CAUTION] 14 | 15 | * [X] 交换接口-除了ForEach 16 | * [ ] 交换面板-对节点组时 17 | * [X] 新建接口-除了ForEach 18 | * [X] 粘贴接口名-除了ForEach 19 | * [X] 插入接口-模仿模拟重复区域节点 20 | * [ ] 插入到节点组面板内 21 | * [ ] Ctrl E 切换选项里加上自定义选项 22 | * [ ] 加个选项-已经连了线的转接点能否被不同转接点连接 23 | * [X] 旋转节口连接限制在旋转或矢量或矩阵 24 | * [X] 矩阵节口连 接限制在旋转或矩阵 25 | * [X] 新建矩阵接口 26 | 27 | # Group-input-helper 28 | 29 | ## 快速添加拆分合并移动组输入节点-快速添加输入输出接口 30 | 31 | ## Quick Add Split Merge Move Group Input Nodes - Quick Add Input and Output Socket 32 | 33 | ## 添加组输入节点 34 | 35 | 组输入的接口数量多了后,可以方便添加一个只剩目标接口没被隐藏的组输入节点 36 | 37 | ## Quick Add Group Input Node 38 | 39 | When the number of group input socket increases, it becomes convenient to add a new group input node that only exposes the target socket while hiding the others. 40 | 41 | ![image](https://github.com/user-attachments/assets/6dad262e-7874-4c84-b322-67005b2e8097) 42 | 43 | ## 拆分合并移动组输入节点 44 | 45 | 对于很多节点的情况,却只有一个组输入节点,连线特别多,不方便.该功能方面组织节点. 46 | 选中一些组输入节点,该功能一键拆分组输入节点,并尽量移动到合适位置. 47 | 节点多了才好显示出效果. 48 | 49 | ## Split Merge Move Group Input Nodes 50 | 51 | In scenarios with numerous nodes but only one group input node, resulting in an excessive number of connections which can be inconvenient. This feature facilitates organizing the nodes. By selecting several group input nodes, this function can split the group input nodes with a single click and move them to appropriate positions, making the organization clearer especially when dealing with a large number of nodes. 52 | 53 | ![image](https://github.com/user-attachments/assets/f51635da-f58b-4506-889e-da21809d63f4) 54 | 55 | ![image](https://github.com/user-attachments/assets/6215a476-1500-4255-9535-48979fa40ad0) 56 | 57 | ## 快速添加输入输出接口 58 | 59 | 当选中节点组时,直接为该节点组添加节口,免去进入节点组,打开N面板,退出节点组(有点用但不多,但有朋友喜欢) 60 | 61 | ## Quick Add Input and Output Socket 62 | 63 | When a node group is selected, this function directly adds socket to that node group, bypassing the need to enter the node group, open the N panel, and then exit the node group (this feature is somewhat useful but not frequently used; however, some users prefer it). 64 | ![image](https://github.com/user-attachments/assets/c5103b40-818f-4dfd-bfff-948dc88f700e) 65 | -------------------------------------------------------------------------------- /VoronoiLinker/common_forward_func.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from pprint import pprint 3 | from mathutils import Vector as Vec2 4 | from bpy.types import Node, NodeSocket 5 | 6 | # 从 globals.py 中明确导入 7 | from .globals import dict_typeSkToBlid 8 | 9 | try: 10 | from rich.console import Console 11 | print = Console(width=165, log_time=False).log # 带有 时间戳 源文件路径 行号 12 | except ImportError: 13 | pass 14 | 15 | def Prefs(): # 很多局部变量也是prefs 还是改大写好点 16 | return bpy.context.preferences.addons[__package__].preferences # type: ignore 17 | 18 | def user_node_keymaps(): 19 | return bpy.context.window_manager.keyconfigs.user.keymaps['Node Editor'] 20 | 21 | def GetFirstUpperLetters(txt): 22 | txtUppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #"".join([chr(cyc) for cyc in range(65, 91)]) 23 | list_result = [] 24 | for ch1, ch2 in zip(" "+txt, txt): 25 | if (ch1 not in txtUppers)and(ch2 in txtUppers): #/(?<=[^A-Z])[A-Z]/ 26 | list_result.append(ch2) 27 | return "".join(list_result) 28 | 29 | def DisplayMessage(title: str, text, icon='NONE'): 30 | def PopupMessage(self, _context): 31 | self.layout.label(text=text, icon=icon, translate=False) 32 | bpy.context.window_manager.popup_menu(PopupMessage, title=title, icon='NONE') 33 | 34 | def format_tool_set(cls: bpy.types.Operator): 35 | return cls.bl_label + " tool settings" 36 | 37 | # ======================放在这避免循环导入 38 | # 关于节点的函数 和 common_class 都用到了 39 | def sk_label_or_name(sk: NodeSocket): 40 | return sk.label if sk.label else sk.name 41 | 42 | def sk_type_to_idname(sk: NodeSocket): 43 | """ 接口.label 是'',有特例吗? """ 44 | return dict_typeSkToBlid.get(sk.type, "Vl_Unknow") 45 | 46 | def is_builtin_tree_idname(blid): 47 | set_quartetClassicTreeBlids = {'ShaderNodeTree','GeometryNodeTree','CompositorNodeTree','TextureNodeTree'} 48 | return blid in set_quartetClassicTreeBlids 49 | 50 | def add_item_for_index_switch(node: Node): 51 | nodes = node.id_data.nodes 52 | old_active = nodes.active 53 | nodes.active = node 54 | bpy.ops.node.index_switch_item_add() 55 | nodes.active = old_active 56 | return node.inputs[-2] 57 | # old_active = nodes.active 58 | # nodes.active = index_switch_node 59 | # bpy.ops.node.index_switch_item_add() 60 | # nodes.active = old_active 61 | # return index_switch_node.inputs[-2] 62 | 63 | # ======================================== 64 | 65 | def SetPieData(self, toolData, prefs, col): 66 | def GetPiePref(name): 67 | return getattr(prefs, self.vlTripleName.lower()+name) 68 | toolData.isSpeedPie = GetPiePref("PieType")=='SPEED' 69 | # todo1v6: 已经有 toolData.prefs 了, 所以可以干掉这个; 并且把这一切都做得更优雅些. 还有 SolderClsToolNames() 里的注释. 70 | toolData.pieScale = GetPiePref("PieScale") 71 | toolData.pieDisplaySocketTypeInfo = GetPiePref("PieSocketDisplayType") 72 | toolData.pieDisplaySocketColor = GetPiePref("PieDisplaySocketColor") 73 | toolData.pieAlignment = GetPiePref("PieAlignment") 74 | toolData.uiScale = self.uiScale 75 | toolData.prefs = prefs 76 | prefs.vaDecorColSkBack = col # 这句在 vaDecorColSk 之前很重要; 参见 VaUpdateDecorColSk(). 77 | prefs.vaDecorColSk = col -------------------------------------------------------------------------------- /VoronoiLinker/v_CallNodePie.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .v_tool import * 3 | from .globals import * 4 | from .utils_ui import * 5 | from .utils_node import * 6 | from .utils_color import * 7 | from .utils_solder import * 8 | from .utils_drawing import * 9 | from .utils_translate import * 10 | from .common_forward_func import * 11 | from .common_forward_class import * 12 | from .v_tool import VoronoiToolAny 13 | 14 | 15 | class VoronoiCallNodePie(VoronoiToolAny): 16 | """ Voronoi 联动 Node Pie """ 17 | bl_idname = 'node.voronoi_call_node_pie' 18 | bl_label = "Voronoi联动节点饼菜单插件" 19 | # toolMode: bpy.props.EnumProperty(name="Mode", default='SOCKET', items=fitVhtModeItems) 20 | isTriggerOnCollapsedNodes: bpy.props.BoolProperty(name="Trigger on collapsed nodes", default=True) 21 | 22 | def CallbackDrawTool(self, drata): 23 | self.TemplateDrawAny(drata, self.fotagoAny, cond=False, tool_name="节点饼菜单") 24 | # TemplateDrawSksToolHh(drata, self.fotagoSkMain, self.fotagoSkRosw, tool_name="节点饼菜单") 25 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 26 | # pprint(self.__dict__) 27 | self.fotagoAny: Fotago = None 28 | ftg_nodes = self.ToolGetNearestNodes() # ->list[Fotago] 这里 .tar 是 Node 29 | # pprint(ftg_nodes[0].__dict__) 30 | node_count = 5 if len(ftg_nodes) >= 5 else len(ftg_nodes) 31 | node_count = min(5, len(ftg_nodes)) 32 | ftg_sockets: list[Fotago] = [] 33 | # 优化了最近接口的获取 34 | for fotago in ftg_nodes[:node_count]: 35 | nd = fotago.tar 36 | if (not self.isTriggerOnCollapsedNodes)and(nd.hide): 37 | continue 38 | # self.fotagoAny = ftgNd 39 | # 这的最近节点的输入输出接口,有时候最近的是输出节口,但是没有离最近的节点的输入接口更近,所以把最近的几个节点接口列表合并 40 | ftg_sks_in, ftg_sks_out = self.ToolGetNearestSockets(nd) # ->([], []) 这里 .tar 是 Socket 41 | ftg_sockets.extend(ftg_sks_in) 42 | ftg_sockets.extend(ftg_sks_out) 43 | ftg_sockets.sort(key=lambda soc: soc.dist) 44 | near_ftg_soc = None 45 | for ftg_sk in ftg_sockets: 46 | if ftg_sk.blid != "NodeSocketVirtual": 47 | near_ftg_soc = ftg_sk 48 | break 49 | # pprint(ftg_sockets[0].__dict__) 50 | self.fotagoAny = near_ftg_soc 51 | if near_ftg_soc: 52 | CheckUncollapseNodeAndReNext(near_ftg_soc.tar.node, self, cond=self.fotagoAny) #Для режима сокетов тоже нужно перерисовывать, ибо нод у прицепившегося сокета может быть свёрнут. 53 | 54 | def MatterPurposeTool(self, event, prefs, tree): 55 | # print(tar) print(str(tar)) 56 | # pprint(tar) pprint(eval(repr(tar))) bpy.data.node_groups['Geometry Nodes'].nodes["Vector Math"].outputs[0] 57 | path = repr(self.fotagoAny.tar) # 有效解决几何和材质节点 节点数据路径不太一样的问题 58 | # print(self.fotagoAny.tar) 59 | # pprint(self.fotagoAny.tar) 60 | # print(repr(self.fotagoAny.tar)) 61 | bpy.ops.node_pie.call_node_pie("INVOKE_DEFAULT", reset_args=False, voronoi_call=True, socket_path=path) 62 | 63 | def InitTool(self, event, prefs, tree): 64 | self.firstResult = None # 从第一个节点获取操作“折叠”或“展开”,然后将其传输到所有其他节点。 65 | -------------------------------------------------------------------------------- /小王-切换N面板/__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "小王-切换几何节点N面板", 3 | "author": "Your Name", 4 | "version": (1, 0), 5 | "blender": (3, 6, 0), # 最低支持的 Blender 版本 6 | "location": "Node Editor > Press N", 7 | "description": "Quickly switch N-Panel categories using a pie menu.", 8 | "warning": "", 9 | "doc_url": "", 10 | "category": "Node", 11 | } 12 | 13 | 14 | import bpy 15 | from bpy.types import Menu, Operator 16 | 17 | addon_keymaps = [] 18 | 19 | # --- 核心: 饼菜单类 --- 20 | class NODE_MT_n_panel_pie(Menu): 21 | bl_label = "Switch N-Panel" 22 | bl_idname = "NODE_MT_n_panel_pie" 23 | 24 | # draw 方法负责绘制菜单内容 25 | def draw(self, context): 26 | layout = self.layout 27 | pie = layout.menu_pie() 28 | 29 | pie.separator() # LEFT 30 | # RIGHT 31 | op = pie.operator("pme.switch_n_panel_category", text="N 面板", icon='MENU_PANEL') 32 | op.category_name = "切换N面板" 33 | 34 | pie.separator() # BOTTOM 35 | pie.separator() # TOP 36 | pie.separator() # TOP_LEFT 37 | 38 | # TOP_RIGHT (右上) 39 | op = pie.operator("pme.switch_n_panel_category", text="Group", icon='NODETREE') 40 | op.category_name = "Group" 41 | 42 | # BOTTOM_LEFT (左下) 43 | pie.separator() 44 | 45 | # BOTTOM_RIGHT (右下) 46 | op = pie.operator("pme.switch_n_panel_category", text="Node", icon='NODE') 47 | op.category_name = "Node" 48 | 49 | 50 | # --- 切换分类的操作符 --- 51 | class PME_OT_switch_n_panel_category(Operator): 52 | """操作符: 切换节点编辑器 N 面板的分类""" 53 | bl_idname = "pme.switch_n_panel_category" 54 | bl_label = "Switch N-Panel Category" 55 | 56 | category_name: bpy.props.StringProperty() 57 | 58 | @classmethod 59 | def poll(cls, context): 60 | return context.area and context.area.type == 'NODE_EDITOR' 61 | 62 | def execute(self, context): 63 | area = context.area 64 | if self.category_name == "切换N面板": 65 | if context.space_data.show_region_ui == True: 66 | context.space_data.show_region_ui = False 67 | else: 68 | context.space_data.show_region_ui = True 69 | return {'FINISHED'} 70 | 71 | if context.space_data.show_region_ui == False: 72 | context.space_data.show_region_ui = True 73 | ui_region = next((region for region in area.regions if region.type == 'UI'), None) 74 | ui_region.active_panel_category = self.category_name 75 | context.area.tag_redraw() 76 | 77 | return {'FINISHED'} 78 | 79 | 80 | classes = ( 81 | NODE_MT_n_panel_pie, 82 | PME_OT_switch_n_panel_category, 83 | ) 84 | 85 | def register(): 86 | for cls in classes: 87 | bpy.utils.register_class(cls) 88 | 89 | # --- 添加快捷键 --- 90 | wm = bpy.context.window_manager 91 | kc = wm.keyconfigs.addon 92 | if kc: 93 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 94 | kmi = km.keymap_items.new('wm.call_menu_pie', type='TAB', value="CLICK_DRAG") 95 | kmi.properties.name = NODE_MT_n_panel_pie.bl_idname 96 | addon_keymaps.append((km, kmi)) 97 | 98 | def unregister(): 99 | for km, kmi in addon_keymaps: 100 | km.keymap_items.remove(kmi) 101 | addon_keymaps.clear() 102 | 103 | for cls in reversed(classes): 104 | bpy.utils.unregister_class(cls) 105 | -------------------------------------------------------------------------------- /node_socket_location/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy, ctypes 2 | from bpy.types import Operator, NodeSocket, Header 3 | 4 | # from .获取接口位置 import sk_loc2 5 | from .test_bpy import print_bpy 6 | from .获取接口位置_精简版本 import sk_support_types 7 | # from mathutils import Vector 8 | 9 | 10 | def sk_loc2(socket: NodeSocket): 11 | # return Vec2((ctypes.c_float * 2).from_address(ctypes.c_void_p.from_address(socket.as_pointer() + 520).value + 24)) 12 | arr = (ctypes.c_float * 2).from_address(ctypes.c_void_p.from_address(socket.as_pointer() + 520).value + 24) 13 | return f" Vector({arr[0]:5}, {arr[1]:5})" 14 | # return f" Vector({arr[0]:*5.0f}, {arr[1]:*5.0f})" 15 | 16 | 17 | class NODE_OT_print_socket_location(Operator): 18 | bl_idname = "node.print_socket_location" 19 | bl_label = "输出信息" 20 | bl_options = {'REGISTER', 'UNDO'} 21 | 22 | def execute(self, context): 23 | # print_bpy() 24 | a_node = context.active_node 25 | if not a_node: 26 | self.report({'INFO'}, "No active node selected.") 27 | return {'CANCELLED'} 28 | print(f"\n--- Node: '{a_node.name}' ---") 29 | 30 | if a_node.inputs: 31 | print("Inputs:") 32 | for socket in a_node.inputs: 33 | if socket.type != "GEOMETRY": continue 34 | # print(socket.type) 35 | print(f" - {socket.name:20} {sk_support_types(socket)}") 36 | # print(f" - {socket.name:20}") 37 | # sk_support_types(socket) 38 | else: 39 | print("Inputs: None") 40 | 41 | return {'FINISHED'} 42 | 43 | class NODE_OT_print_tree_path(Operator): 44 | bl_idname = "node.print_tree_path" 45 | bl_label = "输出节点树路径" 46 | bl_options = {'REGISTER', 'UNDO'} 47 | 48 | def execute(self, context): 49 | # print_bpy() 50 | space = context.space_data 51 | assert(type(space) is bpy.types.SpaceNodeEditor) 52 | print("="*50) 53 | path = space.path 54 | print(path.to_string) 55 | print(path) 56 | p = path[-1] 57 | print(p.node_tree) 58 | # path.pop() 59 | 60 | a_node = context.active_node 61 | assert(not isinstance(a_node, bpy.types.NodeGroup)) 62 | # path.append(a_node.node_tree) 63 | # path.append(a_node.node_tree, node=a_node) 64 | 65 | 66 | return {'FINISHED'} 67 | 68 | class NODE_PT_socket_printer_panel(bpy.types.Panel): 69 | bl_label = "Socket Printer" 70 | bl_idname = "NODE_PT_socket_printer" 71 | bl_space_type = 'NODE_EDITOR' 72 | bl_region_type = 'UI' 73 | bl_category = "Socket Printer" 74 | 75 | def draw(self, context): 76 | layout = self.layout 77 | col = layout.column() 78 | col.operator(NODE_OT_print_socket_location.bl_idname) 79 | col.operator(NODE_OT_print_tree_path.bl_idname) 80 | 81 | def draw_button_in_header(self: Header, context): 82 | self.layout.operator(NODE_OT_print_socket_location.bl_idname, text="输出信息", icon='CONSOLE') 83 | 84 | classes = ( 85 | NODE_OT_print_socket_location, 86 | NODE_OT_print_tree_path, 87 | NODE_PT_socket_printer_panel, 88 | ) 89 | 90 | addon_keymaps = [] 91 | 92 | def register(): 93 | for cls in classes: 94 | bpy.utils.register_class(cls) 95 | bpy.types.NODE_HT_header.prepend(draw_button_in_header) 96 | wm = bpy.context.window_manager 97 | kc = wm.keyconfigs.addon 98 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 99 | kmi = km.keymap_items.new(NODE_OT_print_socket_location.bl_idname, 'P', 'PRESS', ctrl=True, alt=True, shift=False) 100 | addon_keymaps.append((km, kmi)) 101 | 102 | def unregister(): 103 | # 注销快捷键 104 | for km, kmi in addon_keymaps: 105 | km.keymap_items.remove(kmi) 106 | addon_keymaps.clear() 107 | 108 | # 以相反的顺序注销所有类 109 | for cls in reversed(classes): 110 | bpy.utils.unregister_class(cls) 111 | bpy.types.NODE_HT_header.remove(draw_button_in_header) 112 | -------------------------------------------------------------------------------- /VoronoiLinker/v_ResetNode_tool.py: -------------------------------------------------------------------------------- 1 | from .common_forward_class import TryAndPass 2 | from .utils_solder import SolderSkLinks 3 | from .utils_drawing import TemplateDrawNodeFull 4 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 5 | from .v_tool import * 6 | from .globals import * 7 | from .utils_ui import * 8 | from .utils_node import * 9 | from .utils_color import * 10 | from .utils_solder import * 11 | from .utils_drawing import * 12 | from .utils_translate import * 13 | from .common_forward_func import * 14 | from .common_forward_class import * 15 | from .v_tool import VoronoiToolNd 16 | 17 | class VoronoiResetNodeTool(VoronoiToolNd): 18 | bl_idname = 'node.voronoi_reset_node' 19 | bl_label = "Voronoi Reset Node" 20 | usefulnessForCustomTree = True 21 | canDrawInAddonDiscl = False 22 | isResetEnums: bpy.props.BoolProperty(name="Reset enums", default=False) 23 | isResetOnDrag: bpy.props.BoolProperty(name="Reset on grag (not recommended)", default=False) 24 | isSelectResetedNode: bpy.props.BoolProperty(name="Select reseted node", default=True) 25 | def CallbackDrawTool(self, drata): # 小王-工具提示 26 | if self.isResetEnums: 27 | mode = "完全重置节点" 28 | else: 29 | mode = "重置节点" 30 | TemplateDrawNodeFull(drata, self.fotagoNd, tool_name=mode) 31 | # self.TemplateDrawAny(drata, self.fotagoAny, cond=self.toolMode=='NODE', tool_name=name) 32 | def VrntDoResetNode(self, ndTar, tree): 33 | ndNew = tree.nodes.new(ndTar.bl_idname) 34 | ndNew.location = ndTar.location 35 | with TryAndPass(): #SimRep的. 36 | for cyc, sk in enumerate(ndTar.outputs): 37 | for lk in sk.vl_sold_links_final: 38 | tree.links.new(ndNew.outputs[cyc], lk.to_socket) 39 | for cyc, sk in enumerate(ndTar.inputs): 40 | for lk in sk.vl_sold_links_final: 41 | tree.links.new(lk.from_socket, ndNew.inputs[cyc]) 42 | if ndNew.type=='GROUP': 43 | ndNew.node_tree = ndTar.node_tree 44 | if not self.isResetEnums: #如果不重置枚举,则将它们转移到新节点上. 45 | for li in ndNew.rna_type.properties.items(): 46 | if (not li[1].is_readonly)and(getattr(li[1],'enum_items', None)): 47 | setattr(ndNew, li[0], getattr(ndTar, li[0])) 48 | tree.nodes.remove(ndTar) 49 | tree.nodes.active = ndNew 50 | ndNew.select = self.isSelectResetedNode 51 | return ndNew 52 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 53 | SolderSkLinks(tree) 54 | self.fotagoNd = None 55 | for ftgNd in self.ToolGetNearestNodes(includePoorNodes=True, cur_x_off=0): 56 | nd = ftgNd.tar 57 | if nd.type=='REROUTE': #"你确定要重新创建转向节点吗?". 58 | continue 59 | self.fotagoNd = ftgNd 60 | if (self.isResetOnDrag)and(nd not in self.set_done): 61 | self.set_done.add(self.VrntDoResetNode(self.fotagoNd.tar, tree)) 62 | self.NextAssignmentTool(isFirstActivation, prefs, tree) 63 | #总的来说'isResetOnDrag'有点问题 -- 需要为新创建的节点重绘以获取其高度;或者我没什么好主意. 64 | #并且点会吸附到节点角落一帧. 65 | break 66 | def MatterPurposePoll(self): 67 | return (not self.isResetOnDrag)and(self.fotagoNd) 68 | def MatterPurposeTool(self, event, prefs, tree): 69 | self.VrntDoResetNode(self.fotagoNd.tar, tree) 70 | def InitTool(self, event, prefs, tree): 71 | self.set_done = set() #没有这个会有非常“可怕”的行为,如果过度操作,很可能会崩溃. 72 | @classmethod 73 | def BringTranslations(cls): 74 | with VlTrMapForKey(GetAnnotFromCls(cls,'isResetEnums').name) as dm: 75 | dm["ru_RU"] = "Восстанавливать свойства перечисления" 76 | dm["zh_CN"] = "恢复下拉列表里的选择" 77 | with VlTrMapForKey(GetAnnotFromCls(cls,'isResetOnDrag').name) as dm: 78 | dm["ru_RU"] = "Восстанавливать при ведении курсора (не рекомендуется)" 79 | dm["zh_CN"] = "悬停时恢复(不推荐)" 80 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectResetedNode').name) as dm: 81 | dm["ru_RU"] = "Выделять восстановленный нод" 82 | dm["zh_CN"] = "选择重置的节点" -------------------------------------------------------------------------------- /VoronoiLinker/utils_ui.py: -------------------------------------------------------------------------------- 1 | from bpy.types import UILayout 2 | from bpy.app.translations import pgettext_iface as TranslateIface 3 | 4 | class LyAddQuickInactiveCol(): 5 | def __init__(self, layout: UILayout, att='row', align=True, active=False): 6 | self.ly = getattr(layout, att)(align=align) 7 | self.ly.active = active 8 | def __enter__(self): 9 | return self.ly 10 | def __exit__(self, *_): 11 | pass 12 | 13 | def LyAddLeftProp(layout: UILayout, who, att, active=True): 14 | #layout.prop(who, att); return 15 | row = layout.row() 16 | row.alignment = 'LEFT' 17 | row.prop(who, att) 18 | row.active = active 19 | 20 | def LyAddDisclosureProp(layout: UILayout, who, att, *, txt=None, active=True, isWide=False): # 注意: 如果 layout 是 row, 它不能占满整个宽度. 21 | tgl = getattr(who, att) 22 | rowMain = layout.row(align=True) 23 | rowProp = rowMain.row(align=True) 24 | rowProp.alignment = 'LEFT' 25 | txt = txt if txt else None #+":"*tgl 26 | rowProp.prop(who, att, text=txt, icon='DISCLOSURE_TRI_DOWN' if tgl else 'DISCLOSURE_TRI_RIGHT', emboss=True) 27 | rowProp.active = active 28 | if isWide: 29 | rowPad = rowMain.row(align=True) 30 | rowPad.prop(who, att, text=" ", emboss=False) 31 | return tgl 32 | 33 | def LyAddNoneBox(layout: UILayout): 34 | box = layout.box() 35 | box.label() 36 | box.scale_y = 0.5 37 | 38 | def LyAddHandSplitProp(layout: UILayout, who, att, *, text=None, active=True, returnAsLy=False, forceBoolean=0): 39 | spl = layout.row().split(factor=0.42, align=True) 40 | spl.active = active 41 | row = spl.row(align=True) 42 | row.alignment = 'RIGHT' 43 | pr = who.rna_type.properties[att] 44 | isNotBool = pr.type!='BOOLEAN' 45 | isForceBoolean = not not forceBoolean 46 | row.label(text=pr.name*(isNotBool^isForceBoolean) if not text else text) 47 | if (not active)and(pr.type=='FLOAT')and(pr.subtype=='COLOR'): 48 | LyAddNoneBox(spl) 49 | else: 50 | if not returnAsLy: 51 | txt = "" if forceBoolean!=2 else ("True" if getattr(who, att) else "False") 52 | spl.prop(who, att, text=txt if isNotBool^isForceBoolean else None) 53 | else: 54 | return spl 55 | 56 | def LyAddNiceColorProp(layout: UILayout, who, att, align=False, txt="", ico='NONE', decor=3): 57 | rowCol = layout.row(align=align) 58 | rowLabel = rowCol.row() 59 | rowLabel.alignment = 'LEFT' 60 | rowLabel.label(text=txt if txt else TranslateIface(who.rna_type.properties[att].name)+":") 61 | rowLabel.active = decor%2 62 | rowProp = rowCol.row() 63 | rowProp.alignment = 'EXPAND' 64 | rowProp.prop(who, att, text="", icon=ico) 65 | rowProp.active = decor//2%2 66 | 67 | def LyAddKeyTxtProp(layout: UILayout, prefs, att): 68 | rowProp = layout.row(align=True) 69 | LyAddNiceColorProp(rowProp, prefs, att) 70 | # Todo0: 我还是没搞懂你们的 prop event 怎么用, 太吓人了. 需要外部帮助. 71 | with LyAddQuickInactiveCol(rowProp) as row: 72 | row.operator('wm.url_open', text="", icon='URL').url="https://docs.blender.org/api/current/bpy_types_enum_items/event_type_items.html#:~:text="+getattr(prefs, att) 73 | 74 | def LyAddLabeledBoxCol(layout: UILayout, *, text="", active=True, scale=1.0, align=True): 75 | colMain = layout.column(align=True) 76 | box = colMain.box() 77 | box.scale_y = 0.5 78 | row = box.row(align=True) 79 | row.alignment = 'CENTER' 80 | row.label(text=" ▶ ▶ ▶ " + text) 81 | row.active = active 82 | box = colMain.box() 83 | box.scale_y = scale 84 | return box.column(align=align) 85 | 86 | def LyAddTxtAsEtb(layout: UILayout, txt: str): 87 | row = layout.row(align=True) 88 | row.label(icon='ERROR') 89 | col = row.column(align=True) 90 | for li in txt.split("\n")[:-1]: 91 | col.label(text=li, translate=False) 92 | 93 | def LyAddEtb(layout: UILayout): # "你们修复bug吗? 不, 我们只发现bug." 94 | import traceback 95 | LyAddTxtAsEtb(layout, traceback.format_exc()) 96 | 97 | def LyAddThinSep(layout: UILayout, scaleY): 98 | row = layout.row(align=True) 99 | row.separator() 100 | row.scale_y = scaleY 101 | -------------------------------------------------------------------------------- /VoronoiLinker/v_LinksTransfer_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_solder import SolderSkLinks 2 | from .common_forward_func import sk_label_or_name 3 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 4 | from .v_tool import * 5 | from .globals import * 6 | from .utils_ui import * 7 | from .utils_node import * 8 | from .utils_color import * 9 | from .utils_solder import * 10 | from .utils_drawing import * 11 | from .utils_translate import * 12 | from .common_forward_func import * 13 | from .common_forward_class import * 14 | 15 | 16 | class VoronoiLinksTransferTool(VoronoiToolPairNd): #Todo2v6 与 VST 合并并变成 "PairAny" 的候选者. 17 | bl_idname = 'node.voronoi_links_transfer' 18 | bl_label = "Voronoi Links Transfer" 19 | usefulnessForCustomTree = True 20 | canDrawInAddonDiscl = False 21 | isByIndexes: bpy.props.BoolProperty(name="Transfer by indexes", default=False) 22 | def CallbackDrawTool(self, drata): 23 | # VLT 模式 24 | if not self.fotagoNd0: 25 | TemplateDrawSksToolHh(drata, None, tool_name="Links Transfer") 26 | elif (self.fotagoNd0)and(not self.fotagoNd1): 27 | TemplateDrawNodeFull(drata, self.fotagoNd0, side=-1, tool_name="Transfer") 28 | TemplateDrawSksToolHh(drata, None, tool_name="Links Transfer") 29 | else: 30 | TemplateDrawNodeFull(drata, self.fotagoNd0, side=-1, tool_name="Transfer") 31 | TemplateDrawNodeFull(drata, self.fotagoNd1, side=1, tool_name="Transfer") 32 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 33 | if isFirstActivation: 34 | self.fotagoNd0 = None 35 | self.fotagoNd1 = None 36 | for ftgNd in self.ToolGetNearestNodes(includePoorNodes=False, cur_x_off=0): 37 | nd = ftgNd.tar 38 | if nd.type=='REROUTE': 39 | continue 40 | if isFirstActivation: 41 | self.fotagoNd0 = ftgNd 42 | self.fotagoNd1 = ftgNd 43 | if self.fotagoNd0.tar==self.fotagoNd1.tar: 44 | self.fotagoNd1 = None 45 | # 成了. 现在 VL 有两个节点了. 46 | # 突然发现, 节点的“命中”位置简直是粘在它上面, 这在整个都是关于套接字的聚会中观察到相当不寻常. 47 | # 它应该滑动而不是粘住吗?. 大概不应该, 否则不可避免地会有轴向投影, 在视觉上“抹去”信息. 48 | # 而且它们都会随着光标移动而改变, 导致无法直观地知道谁是第一个, 谁是第二个, 49 | # 与粘住不同, 粘住时可以清楚地知道“这个是第一个”; 这对于这个工具尤其重要, 因为哪个节点被首先选择很重要. 50 | if prefs.dsIsSlideOnNodes: # 虽然不急, 但还是留着吧. 51 | if self.fotagoNd0: 52 | self.fotagoNd0.pos = GenFtgFromNd(self.fotagoNd0.tar, self.cursorLoc, self.uiScale).pos 53 | break 54 | def MatterPurposeTool(self, event, prefs, tree): 55 | ndFrom = self.fotagoNd0.tar 56 | ndTo = self.fotagoNd1.tar 57 | def NewLink(sk, lk): 58 | if sk.is_output: 59 | tree.links.new(sk, lk.to_socket) 60 | if lk.to_socket.is_multi_input: 61 | tree.links.remove(lk) 62 | else: 63 | tree.links.new(lk.from_socket, sk) 64 | tree.links.remove(lk) 65 | def GetOnlyVisualSks(puts): 66 | return [sk for sk in puts if sk.enabled and not sk.hide] 67 | SolderSkLinks(tree) # 否则在 vl_sold_links_final 上会是 '... has been removed'; 但也可以用普通的 'sk.links'. 68 | if not self.isByIndexes: 69 | for putsFrom, putsTo in [(ndFrom.inputs, ndTo.inputs), (ndFrom.outputs, ndTo.outputs)]: 70 | for sk in putsFrom: 71 | for lk in sk.vl_sold_links_final: 72 | if not lk.is_muted: 73 | skTar = putsTo.get(sk_label_or_name(sk)) 74 | if skTar: 75 | NewLink(skTar, lk) 76 | else: 77 | for putsFrom, putsTo in [(ndFrom.inputs, ndTo.inputs), (ndFrom.outputs, ndTo.outputs)]: 78 | for zp in zip(GetOnlyVisualSks(putsFrom), GetOnlyVisualSks(putsTo)): 79 | for lk in zp[0].vl_sold_links_final: 80 | if not lk.is_muted: 81 | NewLink(zp[1], lk) 82 | @classmethod 83 | def BringTranslations(cls): 84 | with VlTrMapForKey(GetAnnotFromCls(VoronoiLinksTransferTool,'isByIndexes').name) as dm: 85 | dm["ru_RU"] = "Переносить по индексам" 86 | dm["zh_CN"] = "按顺序传输" -------------------------------------------------------------------------------- /node_utilities/小王-节点属性快速获取.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name" : "小王-节点属性快速获取", 3 | "author" : "一尘不染", 4 | "description" : "节点N面板-节点-属性-(数据路径 bl_idname type)", 5 | "blender" : (3, 0, 0), 6 | "version" : (1, 0, 0), 7 | "location" : "", 8 | "warning" : "", 9 | "doc_url": "", 10 | "tracker_url": "", 11 | "category" : "Node" 12 | } 13 | 14 | 15 | import bpy 16 | 17 | class SNA_PT_panel_AEA7A(bpy.types.Panel): 18 | bl_label = 'API属性' 19 | bl_idname = 'SNA_PT_panel_AEA7A' 20 | bl_space_type = 'NODE_EDITOR' 21 | bl_region_type = 'UI' 22 | bl_options = {'DEFAULT_CLOSED'} 23 | bl_context = '' 24 | bl_order = 0 25 | bl_parent_id = 'NODE_PT_active_node_generic' 26 | bl_ui_units_x=0 27 | 28 | @classmethod 29 | def poll(cls, context): 30 | return not (False) 31 | 32 | def draw(self, context): 33 | node = context.active_node 34 | layout = self.layout 35 | 36 | op = layout.operator("wm.context_set_string", text="数据路径", icon="FILE_SCRIPT") 37 | op.data_path = "window_manager.clipboard" 38 | op.value = ".".join([repr(node.id_data), node.path_from_id()]) 39 | op.value = repr(node.id_data) + "." + node.path_from_id() 40 | 41 | op = layout.operator("wm.context_set_string", text="bl_idname: "+node.bl_idname) 42 | op.data_path = "window_manager.clipboard" 43 | # op.value = repr(node.bl_idname).replace("'", "") 44 | # op.value = node.bl_idname 45 | op.value = '"' + node.bl_idname + '"' 46 | # op.value = '"' + node.bl_idname + '"' 47 | # op.value = f'"{node.bl_idname}"' 48 | 49 | op = layout.operator("wm.context_set_string", text="type: "+node.type) 50 | op.data_path = "window_manager.clipboard" 51 | # op.value = repr(node.type).replace("'", "") 52 | op.value = node.type 53 | 54 | # # todo 突然不想搞了 55 | # if node.bl_idname == "GeometryNodeGroup" and bpy.app.version >= (4, 2, 0): 56 | # # bpy.data.node_groups["NodeGroup.001"].color_tag 57 | # op = layout.operator("wm.context_set_string", text="type: "+node.type) 58 | # op.data_path = "window_manager.clipboard" 59 | # # op.value = repr(node.type).replace("'", "") 60 | # op.value = node.type 61 | 62 | def add_color_tag_to_active_node_panel(self, context): 63 | layout = self.layout 64 | node = context.active_node 65 | if node.bl_idname == "GeometryNodeGroup": 66 | layout.prop(node.node_tree, 'color_tag', text='Color Tag') 67 | 68 | def add_bl_idname_to_active_node_panel(self, context): 69 | layout = self.layout 70 | nodes = context.selected_nodes 71 | active_node_idname = context.active_node.bl_idname 72 | text = "" 73 | # print(nodes) 74 | # print(len(nodes)) 75 | if len(nodes) == 1: 76 | text = active_node_idname 77 | # text = '"' + active_node_idname + '"' 78 | else: 79 | for node in nodes: 80 | text += '"' + node.bl_idname + '"' + ", \n" 81 | 82 | op = layout.operator("wm.context_set_string", text=active_node_idname, icon="FILE_SCRIPT") 83 | op.data_path = "window_manager.clipboard" 84 | op.value = text 85 | # op.value = text.replace("'", "") 86 | 87 | 88 | def register(): 89 | 90 | # bpy.types.Scene.color_tag = bpy.props.EnumProperty(name='列表排序方式', description='属性列表多种排序方式', 91 | # items=[ ('按类型排序1', '按类型排序1', '布尔-浮点-整数-矢量-颜色-旋转', 0, 0), 92 | # ('按类型排序1-反转', '按类型排序1-反转', '旋转-颜色-矢量-整数-浮点-布尔', 0, 1), 93 | # ('按类型排序2', '按类型排序2', '整数-布尔-浮点-矢量-颜色-旋转', 0, 2), 94 | # ('完全按字符串排序', '完全按字符串排序', '首字-数字英文中文', 0, 3)]) 95 | 96 | bpy.utils.register_class(SNA_PT_panel_AEA7A) 97 | bpy.types.NODE_PT_active_node_generic.append(add_color_tag_to_active_node_panel) 98 | bpy.types.NODE_PT_active_node_generic.append(add_bl_idname_to_active_node_panel) 99 | 100 | 101 | def unregister(): 102 | 103 | bpy.types.NODE_PT_active_node_generic.remove(add_color_tag_to_active_node_panel) 104 | bpy.types.NODE_PT_active_node_generic.remove(add_bl_idname_to_active_node_panel) 105 | bpy.utils.unregister_class(SNA_PT_panel_AEA7A) 106 | -------------------------------------------------------------------------------- /node_utilities/小王-复制节点和驱动器.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name" : "小王-复制节点带驱动器和关键帧", 3 | "author" : "小王", 4 | "description" : "", 5 | "blender" : (3, 0, 0), 6 | "version" : (1, 1, 0), 7 | "location" : "", 8 | "warning" : "", 9 | "doc_url": "", 10 | "tracker_url": "", 11 | "category" : "Node" 12 | } 13 | 14 | 15 | import bpy 16 | import bpy.utils.previews 17 | 18 | addon_keymaps = {} 19 | 20 | def find_user_keyconfig(key): 21 | km, kmi = addon_keymaps[key] 22 | for item in bpy.context.window_manager.keyconfigs.user.keymaps[km.name].keymap_items: 23 | found_item = False 24 | if kmi.idname == item.idname: 25 | found_item = True 26 | for name in dir(kmi.properties): 27 | if not name in ["bl_rna", "rna_type"] and not name[0] == "_": 28 | if name in kmi.properties and name in item.properties and not kmi.properties[name] == item.properties[name]: 29 | found_item = False 30 | if found_item: 31 | return item 32 | print(f"Couldn't find keymap item for {key}, using addon keymap instead. This won't be saved across sessions!") 33 | return kmi 34 | 35 | 36 | class SNA_OT_My_Generic_Operator_Dcb80(bpy.types.Operator): 37 | bl_idname = "sna.my_generic_operator_dcb80" 38 | bl_label = "小王-复制节点带驱动器和关键帧" 39 | bl_description = "复制节点带驱动器和关键帧" 40 | bl_options = {"REGISTER", "UNDO"} 41 | 42 | @classmethod 43 | def poll(cls, context): 44 | if bpy.app.version >= (3, 0, 0): 45 | cls.poll_message_set('') 46 | return True 47 | 48 | def execute(self, context): 49 | if context.selected_nodes: 50 | o_node = bpy.ops.node 51 | nodes = context.space_data.edit_tree.nodes 52 | tem_location = context.space_data.cursor_location 53 | o_node.group_make('INVOKE_DEFAULT') 54 | o_node.tree_path_parent('INVOKE_DEFAULT') 55 | context.active_node.node_tree.name = ".辅助复制节点驱动器组" 56 | context.active_node.name = ".辅助复制节点驱动器组节点" 57 | o_node.clipboard_copy('INVOKE_DEFAULT') 58 | o_node.group_ungroup('INVOKE_DEFAULT') 59 | if context.active_node: 60 | context.active_node.select = False 61 | o_node.clipboard_paste() # 粘贴后待在原地不动 62 | # o_node.clipboard_paste('INVOKE_DEFAULT') # INVOKE_DEFAULT 正常应该粘贴到鼠标位置,却有位置加上鼠标位置的偏移量 63 | nodes.active = nodes[".辅助复制节点驱动器组节点"] 64 | nodes.active.location = context.space_data.cursor_location 65 | o_node.group_ungroup('INVOKE_DEFAULT') 66 | bpy.data.node_groups.remove(bpy.data.node_groups[".辅助复制节点驱动器组"]) 67 | bpy.ops.transform.translate('INVOKE_DEFAULT') 68 | return {"FINISHED"} 69 | 70 | def invoke(self, context, event): 71 | return self.execute(context) 72 | 73 | 74 | def sna_add_to_node_mt_node_E9FCE(self, context): 75 | if not (False): 76 | layout = self.layout 77 | op = layout.operator('sna.my_generic_operator_dcb80', text='复制节点带驱动器和关键帧', icon_value=519, emboss=True, depress=False) 78 | layout.separator(factor=1.0) 79 | 80 | 81 | class SNA_AddonPreferences_7AF95(bpy.types.AddonPreferences): 82 | bl_idname = __name__ 83 | 84 | def draw(self, context): 85 | if not (False): 86 | layout = self.layout 87 | split = layout.split(factor=0.5, align=False) 88 | split.label(text='复制节点带驱动器和关键帧:', icon_value=519) 89 | split.prop(find_user_keyconfig('D6F5D'), 'type', text='', full_event=True) 90 | 91 | def register(): 92 | bpy.types.NODE_MT_select.prepend(sna_add_to_node_mt_node_E9FCE) 93 | bpy.utils.register_class(SNA_OT_My_Generic_Operator_Dcb80) 94 | bpy.utils.register_class(SNA_AddonPreferences_7AF95) 95 | kc = bpy.context.window_manager.keyconfigs.addon 96 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 97 | kmi = km.keymap_items.new('sna.my_generic_operator_dcb80', 'SIX', 'PRESS', ctrl=False, alt=False, shift=False, repeat=False) 98 | addon_keymaps['D6F5D'] = (km, kmi) 99 | 100 | def unregister(): 101 | wm = bpy.context.window_manager 102 | kc = wm.keyconfigs.addon 103 | for km, kmi in addon_keymaps.values(): 104 | km.keymap_items.remove(kmi) 105 | addon_keymaps.clear() 106 | bpy.types.NODE_MT_select.remove(sna_add_to_node_mt_node_E9FCE) 107 | bpy.utils.unregister_class(SNA_OT_My_Generic_Operator_Dcb80) 108 | bpy.utils.unregister_class(SNA_AddonPreferences_7AF95) 109 | -------------------------------------------------------------------------------- /VoronoiLinker/v_PreviewAnchor_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 2 | 3 | from .v_tool import * 4 | from .globals import * 5 | from .utils_ui import * 6 | from .utils_node import * 7 | from .utils_color import * 8 | from .utils_solder import * 9 | from .utils_drawing import * 10 | from .utils_translate import * 11 | from .common_forward_func import * 12 | from .common_forward_class import * 13 | from .v_tool import * 14 | 15 | 16 | class VoronoiPreviewAnchorTool(VoronoiToolSk): #嗯, 现在这是一个完整的工具了; 甚至可能需要在布局中为其创建一个新的独立类别. 17 | bl_idname = 'node.voronoi_preview_anchor' 18 | bl_label = "Voronoi Preview Anchor" 19 | usefulnessForCustomTree = True 20 | canDrawInAddonDiscl = False 21 | anchorType: bpy.props.IntProperty(name="Anchor type", default=0, min=0, max=2) 22 | isActiveAnchor: bpy.props.BoolProperty(name="Active anchor", default=True) 23 | isSelectAnchor: bpy.props.BoolProperty(name="Select anchor", default=True) 24 | isDeleteNonCanonAnchors: bpy.props.IntProperty(name="Clear anchors", default=0, min=0, max=2) 25 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 26 | self.fotagoSk = None 27 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 28 | nd = ftgNd.tar 29 | list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=0)[0] 30 | for ftg in list_ftgSksOut: 31 | if ftg.blid!='NodeSocketVirtual': 32 | self.fotagoSk = ftg 33 | break 34 | if self.fotagoSk: 35 | break 36 | def MatterPurposeTool(self, event, prefs, tree): 37 | VptData.reprSkAnchor = repr(self.fotagoSk.tar) 38 | def InitTool(self, event, prefs, tree): 39 | if self.isDeleteNonCanonAnchors: 40 | for nd in tree.nodes: 41 | if (nd.type=='REROUTE')and(nd.name.startswith(voronoiAnchorDtName)): 42 | tree.nodes.remove(nd) 43 | if self.isDeleteNonCanonAnchors==2: 44 | if nd:=tree.nodes.get(voronoiAnchorCnName): 45 | tree.nodes.remove(nd) 46 | return {'FINISHED'} 47 | if self.anchorType: 48 | for nd in tree.nodes: 49 | nd.select = False 50 | match self.anchorType: 51 | case 1: 52 | rrAnch = tree.nodes.get(voronoiAnchorCnName) 53 | isFirstApr = not rrAnch #用于首次出现时处理的标记. 54 | rrAnch = rrAnch or tree.nodes.new('NodeReroute') 55 | rrAnch.name = voronoiAnchorCnName 56 | rrAnch.label = voronoiAnchorCnName 57 | case 2: 58 | sco = 0 59 | tgl = True 60 | while tgl: 61 | sco += 1 62 | name = voronoiAnchorDtName+str(sco) 63 | tgl = not not tree.nodes.get(name, None) 64 | isFirstApr = True 65 | rrAnch = tree.nodes.new('NodeReroute') 66 | rrAnch.name = name 67 | rrAnch.label = voronoiAnchorDtName 68 | if self.isActiveAnchor: 69 | tree.nodes.active = rrAnch 70 | rrAnch.location = self.cursorLoc 71 | rrAnch.select = self.isSelectAnchor 72 | if isFirstApr: 73 | #为什么不呢. 反正很漂亮. 74 | rrAnch.inputs[0].type = 'COLLECTION' if self.anchorType==2 else 'MATERIAL' #对于插件树, 因为在其中下面的“硬闯”方式不起作用. 75 | rrAnch.outputs[0].type = rrAnch.inputs[0].type #以便链接的输出颜色相同. 76 | if self.anchorType==1: 77 | #直接设置 `.type = 'CUSTOM'` 不起作用, 所以我们硬闯; 感谢 Blender 3.5 的更新: 78 | nd = tree.nodes.new('NodeGroupInput') 79 | tree.links.new(nd.outputs[-1], rrAnch.inputs[0]) 80 | tree.nodes.remove(nd) 81 | return {'FINISHED'} 82 | @classmethod 83 | def BringTranslations(cls): 84 | with VlTrMapForKey(GetAnnotFromCls(cls,'anchorType').name) as dm: 85 | dm["ru_RU"] = "Тип якоря" 86 | dm["zh_CN"] = "转接点的类型" 87 | with VlTrMapForKey(GetAnnotFromCls(cls,'isActiveAnchor').name) as dm: 88 | dm["ru_RU"] = "Делать якорь активным" 89 | dm["zh_CN"] = "转接点设置为活动项" 90 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectAnchor').name) as dm: 91 | dm["ru_RU"] = "Выделять якорь" 92 | dm["zh_CN"] = "转接点高亮显示" 93 | with VlTrMapForKey(GetAnnotFromCls(cls,'isDeleteNonCanonAnchors').name) as dm: 94 | dm["ru_RU"] = "Удалить имеющиеся якори" 95 | # dm["zh_CN"] = "" -------------------------------------------------------------------------------- /VoronoiLinker/rot_or_mat_convert.py: -------------------------------------------------------------------------------- 1 | from .v_tool import * 2 | from .globals import * 3 | from .utils_ui import * 4 | from .utils_node import * 5 | from .utils_color import * 6 | from .utils_solder import * 7 | from .utils_drawing import * 8 | from .utils_translate import * 9 | from .common_forward_func import * 10 | from .common_forward_class import * 11 | from .v_tool import VoronoiOpTool 12 | from .common_forward_class import VmtData 13 | 14 | import bpy 15 | from bpy.types import Context, NodeTree 16 | 17 | 18 | Convert_Data = VmtData() 19 | 20 | def Do_Rot_or_Mat_Convert(context: Context, isS: bool, isA: bool, node_type: str): 21 | tree: NodeTree = context.space_data.edit_tree 22 | if not tree: 23 | return 24 | bpy.ops.node.add_node('INVOKE_DEFAULT', type=node_type, use_transform=True) 25 | aNd = context.active_node 26 | sk0 = Convert_Data.sk0 27 | sk1 = Convert_Data.sk1 28 | sk2 = Convert_Data.sk2 29 | # if "ToRotation" in aNd.bl_idname: 30 | if not sk0.is_output: 31 | # print("." * 70) 32 | # print(f"{Convert_Data.__dict__ = }") 33 | # pprint(Convert_Data.__dict__) 34 | skIn = aNd.outputs[0] 35 | tree.links.new(skIn, sk0) 36 | if sk1: 37 | if sk1.type == sk0.type: # 解决矩阵和旋转接口共用 Convert_Data 问题 38 | tree.links.new(skIn, sk1) 39 | if sk2: 40 | tree.links.new(skIn, sk2) 41 | if not hasattr(sk0, "default_value"): 42 | return 43 | value = sk0.default_value 44 | if aNd.bl_idname == "FunctionNodeEulerToRotation": 45 | aNd.inputs[0].default_value = value # 旋转值传递 46 | if aNd.bl_idname == "ShaderNodeCombineXYZ": 47 | aNd.inputs[0].default_value = value[0] 48 | aNd.inputs[1].default_value = value[1] 49 | aNd.inputs[2].default_value = value[2] 50 | # if isA: 51 | # for sk in aNd.inputs: 52 | # sk.hide = True 53 | if sk0.is_output: 54 | # if "RotationTo" in aNd.bl_idname: 55 | skOut = aNd.inputs[0] 56 | tree.links.new(sk0, skOut) 57 | # if Convert_Data.sk1: tree.links.new(Convert_Data.sk1, skOut) # 只有sk0,旋转节口不会触发更多 58 | 59 | # Rot_or_Mat_Convert 只被快速维度和常量使用 60 | class Rot_or_Mat_Convert(VoronoiOpTool): 61 | bl_idname = "node.rot_or_mat_convert" 62 | bl_label = "Mixer Mixer" 63 | node_type: bpy.props.StringProperty() 64 | def invoke(self, context, event): 65 | Do_Rot_or_Mat_Convert(context, event.shift, event.alt, self.node_type) 66 | return {'FINISHED'} 67 | 68 | class PIE_MT_Combine_Matrix(bpy.types.Menu): 69 | bl_idname = "PIE_MT_Combine_Matrix" 70 | bl_label = "" 71 | 72 | def draw(self, context): 73 | pie = self.layout.menu_pie() 74 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Combine Transform') 75 | op.node_type = 'FunctionNodeCombineTransform' 76 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Combine Matrix') 77 | op.node_type = 'FunctionNodeCombineMatrix' 78 | 79 | class PIE_MT_Separate_Matrix(bpy.types.Menu): 80 | bl_idname = "PIE_MT_Separate_Matrix" 81 | bl_label = "" 82 | 83 | def draw(self, context): 84 | pie = self.layout.menu_pie() 85 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Separate Matrix') 86 | op.node_type = 'FunctionNodeSeparateMatrix' 87 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Separate Transform') 88 | op.node_type = 'FunctionNodeSeparateTransform' 89 | 90 | class PIE_MT_Convert_Rotation_To(bpy.types.Menu): 91 | bl_idname = "PIE_MT_Convert_Rotation_To" 92 | bl_label = "" 93 | 94 | def draw(self, context): 95 | pie = self.layout.menu_pie() 96 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Rotation > Euler') 97 | op.node_type = 'FunctionNodeRotationToEuler' 98 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Rotation > Axis Angle') 99 | op.node_type = 'FunctionNodeRotationToAxisAngle' 100 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Rotation > Quaternion') 101 | op.node_type = 'FunctionNodeRotationToQuaternion' 102 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Rotation > Separate XYZ') 103 | op.node_type = 'ShaderNodeSeparateXYZ' 104 | 105 | class PIE_MT_Convert_To_Rotation(bpy.types.Menu): 106 | bl_idname = "PIE_MT_Convert_To_Rotation" 107 | bl_label = "" 108 | 109 | def draw(self, context): 110 | pie = self.layout.menu_pie() 111 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Euler > Rotation') 112 | op.node_type = 'FunctionNodeEulerToRotation' 113 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Axis Angle > Rotation') 114 | op.node_type = 'FunctionNodeAxisAngleToRotation' 115 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Quaternion > Rotation') 116 | op.node_type = 'FunctionNodeQuaternionToRotation' 117 | op = pie.operator(Rot_or_Mat_Convert.bl_idname, text='Combine XYZ > Rotation') 118 | op.node_type = 'ShaderNodeCombineXYZ' 119 | -------------------------------------------------------------------------------- /node_align/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "行": "Row", 7 | "列": "Column", 8 | "更改行数": "Change rows of selected nodes", 9 | "更改列数": "Change columns of selected nodes", 10 | "左对齐" : "Align left", 11 | "右对齐" : "Align right", 12 | "底对齐" : "Align bottom", 13 | "顶对齐" : "Align top", 14 | "节点对齐" : "Node align", 15 | "节点分布" : "Node distribute", 16 | "对齐高度" : "Align height", 17 | "对齐宽度" : "Align width", 18 | "拉直连线" : "Straighten links", 19 | "对齐方式" : "Alignment method", 20 | "行或列数" : "Number of rows or columns", 21 | "x方向间隔": "X direction spacing", 22 | "y方向间隔": "Y direction spacing", 23 | "改变行列数" : "Change rows or columns", 24 | "垂直等距分布": "Distribute vertical", 25 | "水平等距分布": "Distribute horizontal", 26 | "水平垂直等距": "Distribute horizontal vertical", 27 | "等距对齐高度": "Evenly align height", 28 | "相对网格分布": "Relative grid distribute", 29 | "等距对齐宽度": "Evenly align width", 30 | "绝对网格分布": "Absolute grid distribute", 31 | "对齐节点高度": "Align node height", 32 | "等距对齐高度": "Evenly align height", 33 | "等距对齐宽度": "Evenly align width", 34 | "对齐节点宽度": "Align node width", 35 | "自定义分布间距": "Custom space", 36 | "对齐节点到顶部": "Align nodes to the top", 37 | "对齐节点到底部": "Align nodes to the bottom", 38 | "普通对齐饼菜单": "Basic align pie menu", 39 | "高级对齐饼菜单": "Pro align pie menu", 40 | "对齐-拉直连线" : "Align - Straighten links", 41 | "对齐-改变行列数" : "Align - Change the number of rows or columns", 42 | "对齐节点到最左侧" : "Align nodes to the left", 43 | "对齐节点到最右侧" : "Align nodes to the right", 44 | "对齐-水平等距分布": "Align - Distribute horizontally with evenly distribute", 45 | "对齐-垂直等距分布": "Align - Distribute vertically with evenly distribute", 46 | "对齐-水平垂直等距": "Align - Equal spacing in both horizontal and vertical directions", 47 | "对齐-相对网格分布": "Align - Relative grid distribute", 48 | "对齐-绝对网格分布": "Align - Absolute grid distribute", 49 | "水平+垂直等距分布": "Equal distribute in both horizontal and vertical directions", 50 | "对齐选中节点到顶部" : "Align selected nodes to the top", 51 | "对齐选中节点到底部" : "Align selected nodes to the bottom", 52 | "对齐节选中点到最左侧" : "Align selected nodes to the left", 53 | "对齐节选中点到最右侧" : "Align selected nodes to the right", 54 | "对齐高度+垂直等距分布": "Align height + Distribute vertically evenly", 55 | "对齐宽度+水平等距分布": "Align width + Distribute horizontally evenly", 56 | "x方向两个节点之间间隔": "Spacing between two nodes in the X direction", 57 | "y方向两个节点之间间隔": "Spacing between two nodes in the Y direction", 58 | "水平方向节点之间间距一致": "Same space between nodes in the horizontal direction", 59 | "垂直方向节点之间间距一致": "Same space between nodes in the vertical direction", 60 | "把选中节点改成多少行或列": "Change the selected nodes to a specified number of rows or columns", 61 | "等距及栅格分布启用自定义间距" : "Enable custom spacing for evenly and grid distribute", 62 | "等距及栅格分布时的节点x方向间距" : "Node spacing in the X direction for evenly and grid distribute", 63 | "等距及栅格分布时的节点y方向间距" : "Node spacing in the Y direction for evenly and grid distribute", 64 | "栅格分布时判断是否在一列的宽度" : "Determine if nodes are within the width of a column during grid distribute", 65 | "栅格分布时判断是否在一列的宽度" : "Determine if nodes are within the width of a column during grid distribute", 66 | "等距及栅格分布时的节点x方向间距" : "Node spacing in the X direction for evenly and grid distribute", 67 | "等距及栅格分布时的节点y方向间距" : "Node spacing in the Y direction for evenly and grid distribute", 68 | "需要选中活动节点和要对齐的节点" : "You need to select the active node and the nodes to align", 69 | "例:20行20列节点,改成10列就是40行" : "Example: 20 rows and 20 columns of nodes, changing to 10 columns results in 40 rows", 70 | "对齐选中节点的高度中心,即居中分布" : "Align the height center of selected nodes, i.e., center distribute", 71 | "对齐选中节点的宽度中心,即居中分布" : "Align the width center of selected nodes, i.e., center distribute", 72 | "是否为等距及栅格分布启用自定义间距": "Enable custom spacing for evenly and grid distribute", 73 | "列模式:count=0是1行;行模式:count=0是1列" : "Column mode: count=0 means 1 row; Row mode: count=0 means 1 column", 74 | "选中节点,以活动节点为中心,拉直输入输出接口之间的连线": "Select nodes, use the active node as the center, and straighten the links between input and output sockets", 75 | "每一列先垂直等距分布,各列之间水平等距分布,每列对齐宽度居中对齐": "First, distribute each column vertically evenly, then distribute columns horizontally evenly, and center-align the width of each column", 76 | "每一列先垂直等距分布,各列之间水平等距分布,每列对齐宽度居中对齐,每列最大最小高度一样": "First, distribute each column vertically evenly, then distribute columns horizontally evenly, center-align the width of each column, and ensure each column has the same maximum and minimum height", 77 | }, 78 | } 79 | 80 | 81 | def i18n(text: str) -> str: 82 | view = bpy.context.preferences.view 83 | language = view.language 84 | trans_interface = view.use_translate_interface 85 | 86 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 87 | return text 88 | else: 89 | if text in dictionary["en_US"]: 90 | return dictionary["en_US"][text] 91 | else: 92 | return text 93 | 94 | -------------------------------------------------------------------------------- /VoronoiLinker/v_LinkRepeating_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_solder import SolderSkLinks 2 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 3 | from .utils_node import DoLinkHh 4 | from .v_tool import * 5 | from .globals import * 6 | from .utils_ui import * 7 | from .utils_node import * 8 | from .utils_color import * 9 | from .utils_solder import * 10 | from .utils_drawing import * 11 | from .utils_translate import * 12 | from .common_forward_func import * 13 | from .common_forward_class import * 14 | from .v_tool import VoronoiToolAny 15 | 16 | 17 | 18 | fitVlrtModeItems = ( ('SOCKET', "For socket", "Using the last link created by some from the tools, create the same for the specified socket"), 19 | ('NODE', "For node", "Using name of the last socket, find and connect for a selected node") ) 20 | class VoronoiLinkRepeatingTool(VoronoiToolAny): # 分离成单独的工具, 以免用意大利面条代码玷污神圣的地方 (最初只用于 VLT). 21 | bl_idname = 'node.voronoi_link_repeating' 22 | bl_label = "Voronoi Link Repeating" 23 | usefulnessForCustomTree = True 24 | canDrawInAddonDiscl = False 25 | toolMode: bpy.props.EnumProperty(name="Mode", default='SOCKET', items=fitVlrtModeItems) 26 | def CallbackDrawTool(self, drata): 27 | self.TemplateDrawAny(drata, self.fotagoAny, cond=self.toolMode=='NODE') 28 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 29 | def IsSkBetweenFields(sk1, sk2): 30 | return (sk1.type in set_utilTypeSkFields)and( (sk2.type in set_utilTypeSkFields)or(sk1.type==sk2.type) ) 31 | skLastOut = self.skLastOut 32 | skLastIn = self.skLastIn 33 | if not skLastOut: 34 | return 35 | SolderSkLinks(tree) # 好像不重新焊接也能工作. 36 | self.fotagoAny = None 37 | cur_x_off_repeat = -Cursor_X_Offset if self.toolMode=='SOCKET' else 0 # 小王 这个有点特殊 38 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=cur_x_off_repeat): 39 | nd = ftgNd.tar 40 | if nd==skLastOut.node: # 排除自身节点. 41 | break #continue 42 | if self.toolMode=='SOCKET': 43 | list_ftgSksIn, list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=-Cursor_X_Offset) 44 | if skLastOut: 45 | for ftg in list_ftgSksIn: 46 | if (skLastOut.bl_idname==ftg.blid)or(IsSkBetweenFields(skLastOut, ftg.tar)): 47 | can = True 48 | for lk in ftg.tar.vl_sold_links_final: 49 | if lk.from_socket==skLastOut: # 识别已有的链接, 并且不选择这样的套接字. 50 | can = False 51 | if can: 52 | self.fotagoAny = ftg 53 | break 54 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoAny, flag=False) 55 | else: 56 | if skLastIn: 57 | if nd.inputs: 58 | self.fotagoAny = ftgNd 59 | for sk in nd.inputs: 60 | if CompareSkLabelName(sk, skLastIn): 61 | if (sk.enabled)and(not sk.hide): 62 | tree.links.new(skLastOut, sk) # 注意: 不是高级的; 为什么节点重复需要接口?. 63 | break 64 | def MatterPurposeTool(self, event, prefs, tree): 65 | if self.toolMode=='SOCKET': 66 | # 这里不需要检查套接字树是否相同, NextAssignmentTool() 中已经检查过了. 67 | # 同样不需要检查 skLastOut 是否存在, 参见其在 NextAssignmentTool() 中的拓扑. 68 | # 注意: VlrtRememberLastSockets() 中有 `.id_data` 的相同性检查. 69 | # 注意: 不需要检查树是否存在, 因为如果连接的套接字在这里存在, 那它就肯定在某个地方. 70 | DoLinkHh(self.skLastOut, self.fotagoAny.tar) 71 | VlrtRememberLastSockets(self.skLastOut, self.fotagoAny.tar) # 因为. 而且.. “自递归”?. 72 | def InitTool(self, event, prefs, tree): 73 | for txt in "Out", "In": 74 | txtAttSkLast = 'skLast'+txt 75 | txtAttReprLastSk = 'reprLastSk'+txt # 如果失败, 不记录任何东西. 76 | setattr(self, txtAttSkLast, None) # 为工具初始化并在下面赋值. 77 | if reprTxtSk:=getattr(VlrtData, txtAttReprLastSk): 78 | try: 79 | sk = eval(reprTxtSk) 80 | if sk.id_data==tree: 81 | setattr(self, txtAttSkLast, sk) 82 | else: 83 | setattr(VlrtData, txtAttReprLastSk, "") 84 | except: 85 | setattr(VlrtData, txtAttReprLastSk, "") 86 | # 注意: 原来, Ctrl-Z 会使(全局保存的) tree 链接变成 'ReferenceError: StructRNA of type ShaderNodeTree has been removed'. 87 | @classmethod 88 | def BringTranslations(cls): 89 | tran = GetAnnotFromCls(cls,'toolMode').items 90 | with VlTrMapForKey(tran.SOCKET.name) as dm: 91 | dm["ru_RU"] = "Для сокета" 92 | # dm["zh_CN"] = "" 93 | with VlTrMapForKey(tran.SOCKET.description) as dm: 94 | dm["ru_RU"] = "Используя последний линк, созданный каким-н. из инструментов, создать такой же для указанного сокета." 95 | # dm["zh_CN"] = "" 96 | with VlTrMapForKey(tran.NODE.name) as dm: 97 | dm["ru_RU"] = "Для нода" 98 | # dm["zh_CN"] = "" 99 | with VlTrMapForKey(tran.NODE.description) as dm: 100 | dm["ru_RU"] = "Используя имя последнего сокета, найти и соединить для выбранного нода." 101 | dm["zh_CN"] = "鼠标移动到节点旁自动恢复节点的连接" -------------------------------------------------------------------------------- /VoronoiLinker/v_Warper_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 2 | from .v_tool import * 3 | from .globals import * 4 | from .utils_ui import * 5 | from .utils_node import * 6 | from .utils_color import * 7 | from .utils_solder import * 8 | from .utils_drawing import * 9 | from .utils_translate import * 10 | from .common_forward_func import * 11 | from .common_forward_class import * 12 | from .v_tool import VoronoiToolSk 13 | 14 | 15 | def GetSetOfKeysFromEvent(event, isSide=False): 16 | set_keys = {event.type} 17 | if event.shift: 18 | set_keys.add('RIGHT_SHIFT' if isSide else 'LEFT_SHIFT') 19 | if event.ctrl: 20 | set_keys.add('RIGHT_CTRL' if isSide else 'LEFT_CTRL') 21 | if event.alt: 22 | set_keys.add('RIGHT_ALT' if isSide else 'LEFT_ALT') 23 | if event.oskey: 24 | set_keys.add('OSKEY' if isSide else 'OSKEY') 25 | return set_keys 26 | 27 | 28 | class VoronoiWarperTool(VoronoiToolSk): 29 | bl_idname = 'node.voronoi_warper' 30 | bl_label = "Voronoi Warper" 31 | usefulnessForCustomTree = True 32 | isZoomedTo: bpy.props.BoolProperty(name="Zoom to", default=True) 33 | isSelectReroutes: bpy.props.IntProperty(name="Select reroutes", default=1, min=-1, max=1, description="-1 – All deselect.\n 0 – Do nothing.\n 1 – Selecting linked reroutes") 34 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 35 | def FindAnySk(): 36 | ftgSkOut, ftgSkIn = None, None 37 | for ftg in list_ftgSksOut: 38 | if (ftg.tar.vl_sold_is_final_linked_cou)and(ftg.blid!='NodeSocketVirtual'): 39 | ftgSkOut = ftg 40 | break 41 | for ftg in list_ftgSksIn: 42 | if (ftg.tar.vl_sold_is_final_linked_cou)and(ftg.blid!='NodeSocketVirtual'): 43 | ftgSkIn = ftg 44 | break 45 | return MinFromFtgs(ftgSkOut, ftgSkIn) 46 | self.fotagoSk = None 47 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 48 | nd = ftgNd.tar 49 | list_ftgSksIn, list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=0) 50 | if nd.type=='REROUTE': #todo0NA 以及这个要加入到通用的通用部分中. 51 | self.fotagoSk = list_ftgSksIn[0] if self.cursorLoc.x0 70 | else: 71 | nd.select = self.dict_saveRestoreRerouteSelecting[nd] 72 | RecrRerouteWalkerSelecting(nd.outputs[0] if sk.is_output else nd.inputs[0]) 73 | else: 74 | nd.select = True 75 | RecrRerouteWalkerSelecting(skTar) 76 | #可以为选中的节点添加颜色,但我不知道之后如何清除这些颜色。为此设置一个快捷键会太不方便使用. 77 | #Todo0v6SF 或者可以在节点上方绘制明亮的矩形一帧。但这可能与平滑缩放到目标的功能不兼容. 78 | if self.isSelectTargetKey: 79 | skTar.node.select = True 80 | tree.nodes.active = skTar.node 81 | if self.isZoomedTo: 82 | bpy.ops.node.view_selected('INVOKE_DEFAULT') 83 | else: #这个分支没有被使用. 84 | skTar.node.select = True 85 | if self.isZoomedTo: 86 | bpy.ops.node.view_selected('INVOKE_DEFAULT') 87 | skTar.node.select = False #很棒的技巧. 88 | def InitTool(self, event, prefs, tree): 89 | self.isSelectTargetKey = prefs.vwtSelectTargetKey in GetSetOfKeysFromEvent(event) 90 | self.dict_saveRestoreRerouteSelecting = {} #见 `action='DESELECT'`. 91 | for nd in tree.nodes: 92 | if nd.type=='REROUTE': 93 | self.dict_saveRestoreRerouteSelecting[nd] = nd.select 94 | @staticmethod 95 | def LyDrawInAddonDiscl(col, prefs): 96 | LyAddKeyTxtProp(col, prefs,'vwtSelectTargetKey') 97 | @classmethod 98 | def BringTranslations(cls): 99 | with VlTrMapForKey(GetAnnotFromCls(cls,'isZoomedTo').name) as dm: 100 | dm["ru_RU"] = "Центрировать" 101 | dm["zh_CN"] = "自动最大化显示" 102 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectReroutes').name) as dm: 103 | dm["ru_RU"] = "Выделять рероуты" 104 | dm["zh_CN"] = "选择更改路线" 105 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectReroutes').description) as dm: 106 | dm["ru_RU"] = "-1 – Де-выделять всех.\n 0 – Ничего не делать.\n 1 – Выделять связанные рероуты" 107 | dm["zh_CN"] = "-1 – 取消全选。\n 0 – 不做任何事。\n 1 – 选择相连的转向节点" 108 | ## 109 | with VlTrMapForKey(GetPrefsRnaProp('vwtSelectTargetKey').name) as dm: 110 | dm["ru_RU"] = "Клавиша выделения цели" 111 | dm["zh_CN"] = "选择目标快捷键" -------------------------------------------------------------------------------- /VoronoiLinker/utils_solder.py: -------------------------------------------------------------------------------- 1 | from .C_Structure import BNode 2 | from .globals import * 3 | from .utils_color import Color4, opaque_color4, power_color4 4 | from .common_forward_func import GetFirstUpperLetters 5 | from mathutils import Vector as Vec 6 | import bpy 7 | 8 | dict_solderedSkLinksFinal = {} 9 | def SkGetSolderedLinksFinal(self): # .vl_sold_links_final 10 | return dict_solderedSkLinksFinal.get(self, []) 11 | 12 | dict_solderedSkIsFinalLinkedCount = {} 13 | def SkGetSolderedIsFinalLinkedCount(self): # .vl_sold_is_final_linked_cou 14 | return dict_solderedSkIsFinalLinkedCount.get(self, 0) 15 | 16 | def SolderSkLinks(tree): 17 | def Update(dict_data, lk): 18 | dict_data.setdefault(lk.from_socket, []).append(lk) 19 | dict_data.setdefault(lk.to_socket, []).append(lk) 20 | #dict_solderedSkLinksRaw.clear() 21 | dict_solderedSkLinksFinal.clear() 22 | dict_solderedSkIsFinalLinkedCount.clear() 23 | for lk in tree.links: 24 | #Update(dict_solderedSkLinksRaw, lk) 25 | if (lk.is_valid)and not(lk.is_muted or lk.is_hidden): 26 | Update(dict_solderedSkLinksFinal, lk) 27 | dict_solderedSkIsFinalLinkedCount.setdefault(lk.from_socket, 0) 28 | dict_solderedSkIsFinalLinkedCount[lk.from_socket] += 1 29 | dict_solderedSkIsFinalLinkedCount.setdefault(lk.to_socket, 0) 30 | dict_solderedSkIsFinalLinkedCount[lk.to_socket] += 1 31 | 32 | 33 | for key, value in dict_skTypeHandSolderingColor.items(): 34 | dict_skTypeHandSolderingColor[key] = power_color4(value, pw=2.2) 35 | 36 | class SoldThemeCols: 37 | dict_mapNcAtt = {0: 'input_node', 1: 'output_node', 3: 'color_node', 38 | 4: 'vector_node', 5: 'filter_node', 6: 'group_node', 39 | 8: 'converter_node', 9: 'matte_node', 10:'distor_node', 40 | 12:'pattern_node', 13: 'texture_node', 32:'script_node', 41 | 33:'group_socket_node', 40: 'shader_node', 41:'geometry_node', 42 | 42:'attribute_node', 100:'layout_node'} 43 | def SolderThemeCols(themeNe): 44 | def GetNiceColNone(col4): 45 | return Color4(col4) 46 | # return Color4(power_color4(col4, pw=1/1.75)) # 小王 这个更像影响全体 这里使得Ctrl Shift E / Ctrl E / Alt E 等显示太浅 47 | def MixThCol(col1, col2, fac=0.4): # \source\blender\editors\space_node\node_draw.cc : node_draw_basis() : "Header" 48 | return col1*(1-fac)+col2*fac 49 | SoldThemeCols.node_backdrop4 = Color4(themeNe.node_backdrop) 50 | SoldThemeCols.node_backdrop4pw = GetNiceColNone(SoldThemeCols.node_backdrop4) # 对于Ctrl-F: 它被使用了, 参见下面的 `+"4pw"`. 51 | 52 | # theme = C.preferences.themes[0].node_editor 53 | # getattr(theme, "attribute_node") 54 | # for pr in theme.bl_rna.properties: 55 | # dnf = pr.identifier 56 | # if dnf.endswith("_node"): 57 | # print(f"{dnf = }") 58 | # themeNe 是 context.preferences.themes[0].node_editor 59 | # print("." * 50) 60 | for pr in themeNe.bl_rna.properties: 61 | dnf = pr.identifier 62 | if dnf.endswith("_node"): 63 | # 和背景混合使得偏亮 64 | # col4 = MixThCol(SoldThemeCols.node_backdrop4, Color4(opaque_color4(getattr(themeNe, dnf)))) 65 | col4 = Color4(opaque_color4(getattr(themeNe, dnf))) # 小王 解决 Ctrl Shift E / Ctrl E / Alt E 等显示太浅 66 | # 5.0.2里这样写的 67 | # col4 = MixThCol(SoldThemeCols.node_backdrop4, Color4(opaque_color4(getattr(themeNe, dnf)))) 68 | setattr(SoldThemeCols, dnf+"4", col4) 69 | setattr(SoldThemeCols, dnf+"4pw", GetNiceColNone(col4)) 70 | setattr(SoldThemeCols, dnf+"3", Vec(col4[:3])) # 用于 vptRvEeIsSavePreviewResults. 71 | 72 | def node_tag_color(node: bpy.types.Node): 73 | if bpy.app.version >= (5, 0, 0): 74 | color_tag = node.color_tag.lower() + "_node" 75 | if color_tag == "interface": 76 | color_tag = "group_socket" 77 | color = getattr(bpy.context.preferences.themes[0].node_editor, color_tag).copy() 78 | color.s *= 1.5 79 | color.v *= 0.9 80 | return Vec((*color, 1)) 81 | else: 82 | if node.bl_idname=='ShaderNodeMix': 83 | match node.data_type: 84 | case 'RGBA': return SoldThemeCols.color_node4pw 85 | case 'VECTOR': return SoldThemeCols.vector_node4pw 86 | case _: return SoldThemeCols.converter_node4pw 87 | else: 88 | return getattr(SoldThemeCols, SoldThemeCols.dict_mapNcAtt.get(BNode.GetFields(node).typeinfo.contents.nclass, 'node_backdrop')+"4pw") 89 | 90 | def SolderClsToolNames(class_dict: dict): 91 | for cls in class_dict: 92 | cls.vlTripleName = GetFirstUpperLetters(cls.bl_label)+"T" # 最初创建是"因为好玩", 但现在需要了; 参见 SetPieData(). 93 | cls.disclBoxPropName = cls.vlTripleName[:-1].lower()+"BoxDiscl" 94 | cls.disclBoxPropNameInfo = cls.disclBoxPropName+"Info" 95 | 96 | def RegisterSolderings(): 97 | txtDoc = "Property from and only for VoronoiLinker addon." 98 | #bpy.types.NodeSocket.vl_sold_links_raw = property(SkGetSolderedLinksRaw) 99 | bpy.types.NodeSocket.vl_sold_links_final = property(SkGetSolderedLinksFinal) 100 | bpy.types.NodeSocket.vl_sold_is_final_linked_cou = property(SkGetSolderedIsFinalLinkedCount) 101 | #bpy.types.NodeSocket.vl_sold_links_raw.__doc__ = txtDoc 102 | bpy.types.NodeSocket.vl_sold_links_final.__doc__ = txtDoc 103 | bpy.types.NodeSocket.vl_sold_is_final_linked_cou.__doc__ = txtDoc 104 | 105 | def UnregisterSolderings(): 106 | #del bpy.types.NodeSocket.vl_sold_links_raw 107 | del bpy.types.NodeSocket.vl_sold_links_final 108 | del bpy.types.NodeSocket.vl_sold_is_final_linked_cou 109 | -------------------------------------------------------------------------------- /node_socket_location/获取接口位置-无注释.py: -------------------------------------------------------------------------------- 1 | import ctypes, bpy, platform 2 | from mathutils import Vector as Vec2 3 | from bpy.types import NodeSocket 4 | 5 | class StructBase(ctypes.Structure): 6 | _subclasses = [] 7 | # __annotations__ = {} 8 | def __init_subclass__(cls): 9 | cls._subclasses.append(cls) 10 | @staticmethod 11 | def _init_structs(): 12 | functype = type(lambda: None) 13 | for cls in StructBase._subclasses: 14 | fields = [] 15 | for field, value in cls.__annotations__.items(): 16 | if isinstance(value, functype): 17 | value = value() 18 | fields.append((field, value)) 19 | if fields: 20 | cls._fields_ = fields 21 | cls.__annotations__.clear() 22 | StructBase._subclasses.clear() 23 | @classmethod 24 | def get_struct_instance_from_bpy_object(cls, tar: NodeSocket): 25 | return cls.from_address(tar.as_pointer()) 26 | 27 | class BNodeSocketRuntimeHandle(StructBase): 28 | if platform.system() == 'Windows': 29 | _pad0 : ctypes.c_char * 8 30 | declaration : ctypes.c_void_p 31 | changed_flag: ctypes.c_uint32 32 | total_inputs: ctypes.c_short 33 | _pad1 : ctypes.c_char * 2 34 | location : ctypes.c_float * 2 35 | 36 | class BNodeStack(StructBase): 37 | vec : ctypes.c_float * 4 38 | min : ctypes.c_float 39 | max : ctypes.c_float 40 | data : ctypes.c_void_p 41 | hasinput : ctypes.c_short 42 | hasoutput : ctypes.c_short 43 | datatype : ctypes.c_short 44 | sockettype : ctypes.c_short 45 | is_copy : ctypes.c_short 46 | external : ctypes.c_short 47 | _pad : ctypes.c_char * 4 48 | 49 | class BNodeSocket(StructBase): 50 | next : ctypes.c_void_p # 其实是 BNodeSocket 结构体指针 51 | prev : ctypes.c_void_p # 其实是 BNodeSocket 结构体指针 52 | prop : ctypes.c_void_p 53 | identifier : ctypes.c_char*64 54 | name : ctypes.c_char*64 55 | storage : ctypes.c_void_p 56 | in_out : ctypes.c_short 57 | typeinfo : ctypes.c_void_p 58 | idname : ctypes.c_char*64 59 | default_value : ctypes.c_void_p 60 | _pad : ctypes.c_char*4 61 | label : ctypes.c_char*64 62 | description : ctypes.c_char*64 63 | if (bpy.app.version[0] >= 4) and (bpy.app.version_string != '4.0.0 Alpha'): 64 | short_label : ctypes.c_char*64 65 | default_attribute_name: ctypes.POINTER(ctypes.c_char) 66 | to_index : ctypes.c_int 67 | link : ctypes.c_void_p 68 | ns : BNodeStack 69 | runtime : ctypes.POINTER(BNodeSocketRuntimeHandle) 70 | 71 | StructBase._init_structs() 72 | 73 | def sk_loc(sk: NodeSocket): 74 | """ 如果接口已启用且未隐藏,则返回 Vec2(位置),否则返回 None """ 75 | if sk.enabled and (not sk.hide): 76 | return Vec2(BNodeSocket.get_struct_instance_from_bpy_object(sk).runtime.contents.location[:]) 77 | return None 78 | 79 | 80 | # import bpy 81 | # # fake_bpy_modules\bpy\__init__.pyi 82 | 83 | # # fake_bpy_modules\bpy\utils\__init__.pyi 84 | # # utils\__init__ 里 from . import previews as previews 85 | # # utils\__init__ 里 from . import units as units 86 | # # fake_bpy_modules\bpy\utils\previews\__init__.pyi 87 | 88 | # # fake_bpy_modules\bpy\ops\__init__.pyi 89 | # # ops\__init__ 里from . import node as node 90 | # # fake_bpy_modules\bpy\ops\node\__init__.pyi 91 | 92 | # import bpy.utils.previews 93 | # bpy.utils.previews.new() 94 | 95 | # bpy.ops.node.add_node() 96 | # bpy, utils, node, previews, node都是module 97 | # 为什么这一行 bpy.utils.previews.new() 要import bpy.utils.previews 才是正确用法 98 | # bpy.ops.node.add_node() 好像就不用 99 | 100 | 101 | import pprint 102 | 103 | def print_struct_layout(struct_class: type[StructBase]): 104 | print(f"--- 内存布局: {struct_class.__name__} ---") 105 | 106 | for field_name, field_type in struct_class._fields_: 107 | field_descriptor = getattr(struct_class, field_name) 108 | offset = field_descriptor.offset 109 | size = field_descriptor.size 110 | print(f" - 字段: {field_name[0:15]:<25} | 偏移量: {offset:>4} | 大小: {size:>3} 字节") 111 | 112 | total_size = ctypes.sizeof(struct_class) 113 | print(f"结构体总大小: {total_size} 字节\n") 114 | 115 | 116 | sk = bpy.data.node_groups["Geometry Nodes"].nodes["Math"].inputs[0] 117 | 118 | 119 | bpy.context.space_data.edit_tree 120 | 121 | editor: bpy.types.SpaceNodeEditor = bpy.context.space_data 122 | editor.edit_tree 123 | 124 | 125 | print_struct_layout(BNodeSocket) 126 | b_sk = BNodeSocket.get_struct_instance_from_bpy_object(sk) 127 | b_sk = BNodeSocket.default_attribute_name 128 | b_sk = BNodeSocket.description 129 | 130 | 131 | a = list(10) 132 | a.clear() 133 | a= tuple(10) 134 | 135 | a= dict(10) 136 | a.clear() 137 | 138 | b = len(a) 139 | c = type(a) 140 | print(a, b, c) 141 | pprint.pprint("aaaaaaaaaaaa") 142 | e = "aaaaaaaaaaaaaaaa" 143 | e.capitalize() 144 | 145 | 146 | 147 | def simple_sk_loc(socket: NodeSocket): 148 | 149 | Vec2( 150 | (ctypes.c_float * 2).from_address( 151 | ctypes.c_void_p.from_address( 152 | socket.as_pointer() + 520 153 | ).value + 24 154 | ) 155 | ) 156 | 157 | return Vec2((ctypes.c_float*2).from_address(ctypes.c_void_p.from_address(socket.as_pointer()+520).value+24)) 158 | # return Vec2((ctypes.c_float * 2).from_address(ctypes.c_void_p.from_address(socket.as_pointer() + 520).value + 24)) 159 | 160 | class Computer: 161 | 162 | def __init__(self): 163 | self.__maxprice = 900 164 | 165 | def sell(self): 166 | print(f'Selling Price: {self.__maxprice}') 167 | 168 | def setMaxPrice(self, price): 169 | self.__maxprice = price 170 | 171 | cccccc = Computer() 172 | c = Computer() 173 | c.sell() 174 | 175 | # change the price 176 | c.__maxprice = 1000 177 | c.sell() 178 | 179 | # using setter function 180 | c.setMaxPrice(1000) 181 | c.sell() 182 | 183 | simple_sk_loc() 184 | pprint(simple_sk_loc()) 185 | 186 | getattr(Vec2, "aaa") 187 | 188 | aaaaaaaa 189 | 190 | -------------------------------------------------------------------------------- /named_attribute_list/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "点": "Point", 7 | "边": "Edge", 8 | "面": "Face", 9 | "面拐": "Corner", 10 | "样条线": "Curve", 11 | "实例": "Instance", 12 | "层": "Layer", 13 | "UV": "UV", 14 | "顶点组": "Vertex Group", 15 | "UV贴图": "UV Map", 16 | "属性": "Attribute", 17 | "不确定": "Unknown", 18 | "颜色属性": "Color Attribute", 19 | "物体属性": "Object Attribute", 20 | "菜单快捷键: ": "Menu Shortcut: ", 21 | "面板快捷键: ": "Panel Shortcut: ", 22 | "命名属性菜单快捷键: ": "Named Attribute Menu Shortcut: ", 23 | "命名属性面板快捷键: ": "Named Attribute Panel Shortcut: ", 24 | 25 | "添加活动存储属性节点对应属性节点: ": "Add Corresponding Attribute Nodes for Selected Active Stored Attribute Node: ", 26 | "快速添加选中的活动存储属性节点相应的已命名属性节点: ": "Quickly Add Corresponding Named Attribute Node for Selected Active Stored Attribute Nodes: ", 27 | "已知限制: ": "Known Limitations: ", 28 | "非网格域存储属性节点,名称接口由别的接口连接的话,可能识别不到": "For non-mesh domain attribute nodes, if the name socket is connected by other socket, it may not be recognized.", 29 | "存储属性节点后经过了实例化或实现实例,在着色器添加属性,选项不一定正确": "After store attribute node, instanced or realizing instances, adding attributes in the shader may not necessarily result in correct option.", 30 | "对于存了多次的属性,查找节点目前只能定位到其中之一": "For attributes stored multiple times, find node currently can only locate one of them.", 31 | "隐藏节点选项": "Hide Node Option", 32 | "隐藏存在接口": "Hide Exists Socket", 33 | "隐藏名称接口": "Hide Name Socket", 34 | "隐藏选中项接口": "Hide Selection Socket", 35 | "重命名属性接口": "Rename Attribute Socket", 36 | "折叠节点": "Hide Node", 37 | "重命名节点标签": "Rename Node Label", 38 | "前缀": "Prefix", 39 | "重命名添加前缀: ": "If Rename, Add Prefix: ", 40 | "隐藏前缀": "Hide Prefix", 41 | "列表排序方式": "List Sorting Method", 42 | "属性列表里是否显示": "Show in Attribute List", 43 | "属性列表里是否隐藏": "Hide in Attribute List", 44 | "未使用": "Unused", 45 | "组内属性": "Attrs in Group", 46 | "额外属性": "Extra Attrs", 47 | "属性列表文本设置": "Attribute List Text Settings", 48 | "显示所在域": "Show Domain", 49 | "查找节点设置": "Node Search Settings", 50 | "适当缩放视图": "Zoom View Appropriately", 51 | "添加节点选项": "Add Node Options", 52 | "列表显示选项": "List Display Options", 53 | "添加已命名属性节点": "Add Named Attribute Node", 54 | "添加属性节点": "Add Attribute Node", 55 | "框选节点": "Box Select Nodes", 56 | "类型: ": "Type: ", 57 | "属性节点:": "Attribute Node:", 58 | "存储属性节点:": "Store Named Attribute Node:", 59 | "属性所在域: ": "Attribute Domain: ", 60 | "所在节点组: ": "In Which Groups: ", 61 | "属性隐藏选项": "Attribute Hide Options", 62 | "命名属性菜单": "Named Attribute List Menu", 63 | "命名属性面板": "Named Attribute List Panel", 64 | "几何节点命名属性列表": "Geometry Nodes Named Attribute List", 65 | "快速添加命名属性节点": "Quickly Add Named Attribute Node", 66 | "小王-命名属性菜单": "Named Attribute List Menu", 67 | "小王-命名属性面板": "Named Attribute List Panel", 68 | "小王-几何节点命名属性列表": "Geometry Nodes Named Attribute List", 69 | "小王-快速添加命名属性节点": "Quickly Add Named Attribute Node", 70 | "该属性所在域,例:面 | 实例": "The domain of this attribute, e.g., Face | Instance", 71 | "该属性是否转到了实例域上": "Whether this attribute has been moved to the Instance domain", 72 | "存储属性节点目标": "Store Attribute Node Target", 73 | "活动物体属性": "Active Object Attributes", 74 | "活动物体及节点树属性": "Active Object and Node Tree Attributes", 75 | "目标退到顶层": "Target Move to Top Level", 76 | "添加时是否隐藏选项": "Hide Option When Adding", 77 | "添加时是否隐藏输出存在接口": "Hide Output Exists Socket When Adding", 78 | "添加时是否隐藏输入名称接口": "Hide Input Name Socket When Adding", 79 | "添加时是否重命名输出属性接口(不完善)": "Rename Output Attribute Socket When Adding(Imperfect)", 80 | "添加时是否折叠节点": "Hide Node When Adding", 81 | "添加时是否重命名节点为属性名": "Rename Node to Attribute Name When Adding", 82 | "重命名节点时添加的前缀": "Prefix to Add When Renaming Node", 83 | "隐藏带有特定前缀的属性,以|分隔多种,例 .|_|-": "Hide Attributes with Specific Prefixes, separated by |, e.g., .|_|-", 84 | "是否隐藏带有特定前缀的属性": "Hide Attributes with Specific Prefixes", 85 | "显示设置": "Display Settings", 86 | "查找节点时适当缩放视图": "Zoom View Appropriately When Finding for Node", 87 | "todo-隐藏没连到组输出的存储属性节点的属性": "todo-Hide Attribute of Store Named Attribute Node of output Unlinked to Group Output", 88 | "隐藏节点组里的属性": "Hide Attributes in Node Groups", 89 | "是否显示属性所在域": "Show Attribute Domain", 90 | "显示在n面板上的插件当前状态描述": "Plugin Description Shown in N-Panel", 91 | "是否在属性列表里隐藏顶点组": "Hide Vertex Groups in Attribute List", 92 | "是否在属性列表里隐藏UV贴图": "Hide UV Maps in Attribute List", 93 | "是否在属性列表里隐藏颜色属性": "Hide Color Attributes in Attribute List", 94 | "属性列表多种排序方式": "Multiple Sorting Methods for Attribute List", 95 | "使用加速键": "use accelerator key", 96 | "按类型排序1": "Sort by Type 1", 97 | "布尔-浮点-整数-矢量-颜色-旋转-矩阵": "Boolean-Float-Integer-Vector-Color-Rotation-Matrix", 98 | "按类型排序1-反转": "Sort by Type 1 - Reversed", 99 | "矩阵-旋转-颜色-矢量-整数-浮点-布尔": "Matrix-Rotation-Color-Vector-Integer-Float-Boolean", 100 | "按类型排序2": "Sort by Type 2", 101 | "整数-布尔-浮点-矢量-颜色-旋转-矩阵": "Integer-Boolean-Float-Vector-Color-Rotation-Matrix", 102 | "完全按字符串排序": "Sort Completely by String", 103 | "首字-数字英文中文": "First Character - Numbers English Chinese", 104 | "查找命名属性节点": "Find Stored Named Attribute Node", 105 | "跳转到已命名属性节点位置": "Find Stored Named Attribute Node", 106 | "隐藏额外的属性:\n--属性编辑器-数据-属性\n--物体/集合信息节点带的(和别的几何数据合并几何才显示顶点组)\n--存储属性节点名称接口连了线的\n--活动修改器之上的GN修改器的属性": "Hide extra attributes:\n--Properties Editor-Data-Attributes\n--object/collection info nodes(only show vertex Groups when join geometry with other geometry)\n--Store Named Attribute node name socket linked\n--the attrs of the GN modifiers above the active modifier", 107 | }, 108 | } 109 | 110 | 111 | def i18n(text: str) -> str: 112 | view = bpy.context.preferences.view 113 | language = view.language 114 | trans_interface = view.use_translate_interface 115 | 116 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 117 | return text 118 | else: 119 | if text in dictionary["en_US"]: 120 | return dictionary["en_US"][text] 121 | else: 122 | return text 123 | -------------------------------------------------------------------------------- /node_utilities/node_group_path_navigator/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "几何数据": "Geometry", 7 | "布尔": "Boolean", 8 | "浮点": "Float", 9 | "整数": "Integer", 10 | "矢量": "Vector", 11 | "颜色": "Color", 12 | "旋转": "Rotation", 13 | "矩阵": "Matrix", 14 | "菜单": "Menu", 15 | "字符串": "String", 16 | "材质": "Material", 17 | "物体": "Object", 18 | "集合": "Collection", 19 | "图像": "Image", 20 | "纹理": "Texture", 21 | "添加输入输出接口": "Add input/output socket for current node tree", 22 | "给活动节点组添加接口": "Add socket for active group node", 23 | "w-节点组输入助手-添加拆分合并移动": "Group input helper-Add Split Merge Move", 24 | "快速添加组输入节点-拆分合并移动组输入节点-快速添加组输入输出接口": "Qucik add and split merge move Group Input node-Qucik add Group Input Output socket", 25 | "偏好设置-标题栏-N面板": "Preferences-Editor Bar-N panel", 26 | "组输入": "Group input", 27 | "w-组输入拆分": "Group input split", 28 | "w-添加组输入输出接口": "Add group input output interface", 29 | "显示面板名字": "Show Panel name", 30 | "添加组输入菜单里显示接口所属面板名字": "Show Panel name of socket in Group input add menu", 31 | "组输入隐藏节口": "Group input hide socket", 32 | "添加一个只剩目标接口没被隐藏的组输入节点": "add a group input node that only exposes the target socket while hiding the others.", 33 | "空": "None", 34 | "标题栏和N面板显示组输入拆分": "Title bar and N panel display group input split", 35 | "添加组输入": "Add group input", 36 | "w-添加组输入": "Add group input", 37 | "添加组输入-菜单": "Add group input - menu", 38 | "添加组输入-面板": "Add group input - panel", 39 | "添加组输入输出接口": "Add group input output socket", 40 | "添加输入输出接口": "Add input output socket", 41 | "输入接口": "Input socket", 42 | "输出接口": "Output socket", 43 | "组输入合并拆分移动": "Group input merge split move", 44 | "w-组输入拆分合并移动": "Group input split merge move", 45 | "合并组输入接口": "Merge group input socket", 46 | "合并组输入连线": "Merge group input links", 47 | "留下未隐藏的接口": "Split group input socket, leave the unhidden socket", 48 | "拆分组输入接口": "Split group input socket", 49 | "拆分组输入并移动": "Split group input and move", 50 | "拆分组输入并移动合并": "Split group input and move merge", 51 | "拆分组输入": "Split group input", 52 | "合并节点并拆分": "Merge group input and split", 53 | "合并组输入连线": "Merge group input links", 54 | "拆分组输入连线": "Split group input links", 55 | "完全拆分并移动": "Completely split and move", 56 | "拆分并移动合并": "Split and move merge", 57 | "完全拆分": "Completely split", 58 | "拆分组输入接口,只留下连线的接口": "Split group input socket, only leave the linked socket", 59 | "拆分组输入接口,留下未隐藏的接口,一连多也拆开": "Split group input socket, leave the unhidden socket, also split the one-to-many connections", 60 | "拆分组输入接口,并移动到连向接口的附近,留下未隐藏的接口,一连多也拆开": "Split group input socket, and move to the left of the connected to_socket, leave the unhidden socket, also split the one-to-many connections", 61 | "完全拆分组输入接口,并移动到连向的接口(to_socket),并合并连到一个节点上的和距离近的组输入节点": "Completely split group input socket, and move to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 62 | "先合并选中组输入节点,再完全拆分组输入接口,并移动节点们到连向的接口(to_socket),并合并 连到一个节点上的和距离近的组输入节点": "First merge the selected group input nodes, then completely split the group input socket, move the nodes to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 63 | "隐藏未使用组输入接口": "hide unliked group input sockets", 64 | "同时添加输入接口": "Add input socket simultaneously", 65 | "同时添加输出接口": "Add output socket simultaneously", 66 | "简化<组输入合并拆分移动>菜单": "Simplify the menu of Split group input", 67 | "隐藏所有组输入节点未使用的接口": "Hide unused sockets of all group input nodes", 68 | "拆分并移动组输入节点时删除转接点": "Delete reroute when splitting and moving group input nodes", 69 | "选中组输入节点,每一个接口拆分成一个节点": "Select group input nodes, split each socket into a separate node", 70 | "选中组输入节点,合并接口到一个组输入节点": "Select group input nodes, merge sockets into one group input node", 71 | "选中组输入节点,每一个接口/连线拆分成一个节点": "Select group input nodes, split each socket/link into a separate node", 72 | "选中组输入节点,隐藏未连线节口后,拆分组输入接口": "Select group input nodes, hide unlinked sockets, then split group input sockets", 73 | "选中组输入节点,合并连线到一个组输入节点,隐藏未连线节口": "Select group input nodes, merge links into one group input node, hide unlinked sockets", 74 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近": "Select group input nodes, split each link into a separate node, and move to the left of the connected to_socket", 75 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, split each link into a separate node, move to the left of the connected to_socket, and merge group input nodes connected to one node", 76 | "选中组输入节点,先合并成一个节点,再拆分接口,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, first merge into one node, then split sockets, move to the left of the connected to_socket, and merge group input nodes connected to one node", 77 | }, 78 | # "zh_CN": { 79 | # "Add-on Preferences View": "插件偏好设置", 80 | # } 81 | } 82 | 83 | 84 | def i18n(text: str) -> str: 85 | view = bpy.context.preferences.view 86 | language = view.language 87 | trans_interface = view.use_translate_interface 88 | 89 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 90 | return text 91 | else: 92 | if text in dictionary["en_US"]: 93 | return dictionary["en_US"][text] 94 | else: 95 | return text 96 | 97 | 98 | # # Get the language code when addon start up 99 | # __language_code__ = bpy.context.preferences.view.language 100 | 101 | # dictionary["zh_HANS"] = dictionary["zh_CN"] 102 | 103 | 104 | # def i18n(content: str) -> str: 105 | # return i18n_d(content) 106 | 107 | # def i18n_l(content: str) -> str: 108 | # global __language_code__ 109 | # if __language_code__ not in dictionary: 110 | # # return content 111 | # return dictionary["en_US"][content] 112 | 113 | # if content not in dictionary[__language_code__]: 114 | # return content 115 | 116 | # return dictionary[__language_code__][content] 117 | 118 | 119 | # # update the preferences language code and do the translation 120 | # def i18n_d(content: str) -> str: 121 | # global __language_code__ 122 | # __language_code__ = bpy.context.preferences.view.language 123 | # return i18n_l(content) 124 | -------------------------------------------------------------------------------- /node_utilities/show_node_editor_center_lines/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "几何数据": "Geometry", 7 | "布尔": "Boolean", 8 | "浮点": "Float", 9 | "整数": "Integer", 10 | "矢量": "Vector", 11 | "颜色": "Color", 12 | "旋转": "Rotation", 13 | "矩阵": "Matrix", 14 | "菜单": "Menu", 15 | "字符串": "String", 16 | "材质": "Material", 17 | "物体": "Object", 18 | "集合": "Collection", 19 | "图像": "Image", 20 | "纹理": "Texture", 21 | "添加输入输出接口": "Add input/output socket for current node tree", 22 | "给活动节点组添加接口": "Add socket for active group node", 23 | "w-节点组输入助手-添加拆分合并移动": "Group input helper-Add Split Merge Move", 24 | "快速添加组输入节点-拆分合并移动组输入节点-快速添加组输入输出接口": "Qucik add and split merge move Group Input node-Qucik add Group Input Output socket", 25 | "偏好设置-标题栏-N面板": "Preferences-Editor Bar-N panel", 26 | "组输入": "Group input", 27 | "w-组输入拆分": "Group input split", 28 | "w-添加组输入输出接口": "Add group input output interface", 29 | "显示面板名字": "Show Panel name", 30 | "添加组输入菜单里显示接口所属面板名字": "Show Panel name of socket in Group input add menu", 31 | "组输入隐藏节口": "Group input hide socket", 32 | "添加一个只剩目标接口没被隐藏的组输入节点": "add a group input node that only exposes the target socket while hiding the others.", 33 | "空": "None", 34 | "标题栏和N面板显示组输入拆分": "Title bar and N panel display group input split", 35 | "添加组输入": "Add group input", 36 | "w-添加组输入": "Add group input", 37 | "添加组输入-菜单": "Add group input - menu", 38 | "添加组输入-面板": "Add group input - panel", 39 | "添加组输入输出接口": "Add group input output socket", 40 | "添加输入输出接口": "Add input output socket", 41 | "输入接口": "Input socket", 42 | "输出接口": "Output socket", 43 | "组输入合并拆分移动": "Group input merge split move", 44 | "w-组输入拆分合并移动": "Group input split merge move", 45 | "合并组输入接口": "Merge group input socket", 46 | "合并组输入连线": "Merge group input links", 47 | "留下未隐藏的接口": "Split group input socket, leave the unhidden socket", 48 | "拆分组输入接口": "Split group input socket", 49 | "拆分组输入并移动": "Split group input and move", 50 | "拆分组输入并移动合并": "Split group input and move merge", 51 | "拆分组输入": "Split group input", 52 | "合并节点并拆分": "Merge group input and split", 53 | "合并组输入连线": "Merge group input links", 54 | "拆分组输入连线": "Split group input links", 55 | "完全拆分并移动": "Completely split and move", 56 | "拆分并移动合并": "Split and move merge", 57 | "完全拆分": "Completely split", 58 | "拆分组输入接口,只留下连线的接口": "Split group input socket, only leave the linked socket", 59 | "拆分组输入接口,留下未隐藏的接口,一连多也拆开": "Split group input socket, leave the unhidden socket, also split the one-to-many connections", 60 | "拆分组输入接口,并移动到连向接口的附近,留下未隐藏的接口,一连多也拆开": "Split group input socket, and move to the left of the connected to_socket, leave the unhidden socket, also split the one-to-many connections", 61 | "完全拆分组输入接口,并移动到连向的接口(to_socket),并合并连到一个节点上的和距离近的组输入节点": "Completely split group input socket, and move to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 62 | "先合并选中组输入节点,再完全拆分组输入接口,并移动节点们到连向的接口(to_socket),并合并 连到一个节点上的和距离近的组输入节点": "First merge the selected group input nodes, then completely split the group input socket, move the nodes to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 63 | "隐藏未使用组输入接口": "hide unliked group input sockets", 64 | "同时添加输入接口": "Add input socket simultaneously", 65 | "同时添加输出接口": "Add output socket simultaneously", 66 | "简化<组输入合并拆分移动>菜单": "Simplify the menu of Split group input", 67 | "隐藏所有组输入节点未使用的接口": "Hide unused sockets of all group input nodes", 68 | "拆分并移动组输入节点时删除转接点": "Delete reroute when splitting and moving group input nodes", 69 | "选中组输入节点,每一个接口拆分成一个节点": "Select group input nodes, split each socket into a separate node", 70 | "选中组输入节点,合并接口到一个组输入节点": "Select group input nodes, merge sockets into one group input node", 71 | "选中组输入节点,每一个接口/连线拆分成一个节点": "Select group input nodes, split each socket/link into a separate node", 72 | "选中组输入节点,隐藏未连线节口后,拆分组输入接口": "Select group input nodes, hide unlinked sockets, then split group input sockets", 73 | "选中组输入节点,合并连线到一个组输入节点,隐藏未连线节口": "Select group input nodes, merge links into one group input node, hide unlinked sockets", 74 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近": "Select group input nodes, split each link into a separate node, and move to the left of the connected to_socket", 75 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, split each link into a separate node, move to the left of the connected to_socket, and merge group input nodes connected to one node", 76 | "选中组输入节点,先合并成一个节点,再拆分接口,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, first merge into one node, then split sockets, move to the left of the connected to_socket, and merge group input nodes connected to one node", 77 | }, 78 | # "zh_CN": { 79 | # "Add-on Preferences View": "插件偏好设置", 80 | # } 81 | } 82 | 83 | 84 | def i18n(text: str) -> str: 85 | view = bpy.context.preferences.view 86 | language = view.language 87 | trans_interface = view.use_translate_interface 88 | 89 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 90 | return text 91 | else: 92 | if text in dictionary["en_US"]: 93 | return dictionary["en_US"][text] 94 | else: 95 | return text 96 | 97 | 98 | # # Get the language code when addon start up 99 | # __language_code__ = bpy.context.preferences.view.language 100 | 101 | # dictionary["zh_HANS"] = dictionary["zh_CN"] 102 | 103 | 104 | # def i18n(content: str) -> str: 105 | # return i18n_d(content) 106 | 107 | # def i18n_l(content: str) -> str: 108 | # global __language_code__ 109 | # if __language_code__ not in dictionary: 110 | # # return content 111 | # return dictionary["en_US"][content] 112 | 113 | # if content not in dictionary[__language_code__]: 114 | # return content 115 | 116 | # return dictionary[__language_code__][content] 117 | 118 | 119 | # # update the preferences language code and do the translation 120 | # def i18n_d(content: str) -> str: 121 | # global __language_code__ 122 | # __language_code__ = bpy.context.preferences.view.language 123 | # return i18n_l(content) 124 | -------------------------------------------------------------------------------- /node_utilities/show_gn_total_execution_time_in_header_and_editor/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "几何数据": "Geometry", 7 | "布尔": "Boolean", 8 | "浮点": "Float", 9 | "整数": "Integer", 10 | "矢量": "Vector", 11 | "颜色": "Color", 12 | "旋转": "Rotation", 13 | "矩阵": "Matrix", 14 | "菜单": "Menu", 15 | "字符串": "String", 16 | "材质": "Material", 17 | "物体": "Object", 18 | "集合": "Collection", 19 | "图像": "Image", 20 | "纹理": "Texture", 21 | "添加输入输出接口": "Add input/output socket for current node tree", 22 | "给活动节点组添加接口": "Add socket for active group node", 23 | "w-节点组输入助手-添加拆分合并移动": "Group input helper-Add Split Merge Move", 24 | "快速添加组输入节点-拆分合并移动组输入节点-快速添加组输入输出接口": "Qucik add and split merge move Group Input node-Qucik add Group Input Output socket", 25 | "偏好设置-标题栏-N面板": "Preferences-Editor Bar-N panel", 26 | "组输入": "Group input", 27 | "w-组输入拆分": "Group input split", 28 | "w-添加组输入输出接口": "Add group input output interface", 29 | "显示面板名字": "Show Panel name", 30 | "添加组输入菜单里显示接口所属面板名字": "Show Panel name of socket in Group input add menu", 31 | "组输入隐藏节口": "Group input hide socket", 32 | "添加一个只剩目标接口没被隐藏的组输入节点": "add a group input node that only exposes the target socket while hiding the others.", 33 | "空": "None", 34 | "标题栏和N面板显示组输入拆分": "Title bar and N panel display group input split", 35 | "添加组输入": "Add group input", 36 | "w-添加组输入": "Add group input", 37 | "添加组输入-菜单": "Add group input - menu", 38 | "添加组输入-面板": "Add group input - panel", 39 | "添加组输入输出接口": "Add group input output socket", 40 | "添加输入输出接口": "Add input output socket", 41 | "输入接口": "Input socket", 42 | "输出接口": "Output socket", 43 | "组输入合并拆分移动": "Group input merge split move", 44 | "w-组输入拆分合并移动": "Group input split merge move", 45 | "合并组输入接口": "Merge group input socket", 46 | "合并组输入连线": "Merge group input links", 47 | "留下未隐藏的接口": "Split group input socket, leave the unhidden socket", 48 | "拆分组输入接口": "Split group input socket", 49 | "拆分组输入并移动": "Split group input and move", 50 | "拆分组输入并移动合并": "Split group input and move merge", 51 | "拆分组输入": "Split group input", 52 | "合并节点并拆分": "Merge group input and split", 53 | "合并组输入连线": "Merge group input links", 54 | "拆分组输入连线": "Split group input links", 55 | "完全拆分并移动": "Completely split and move", 56 | "拆分并移动合并": "Split and move merge", 57 | "完全拆分": "Completely split", 58 | "拆分组输入接口,只留下连线的接口": "Split group input socket, only leave the linked socket", 59 | "拆分组输入接口,留下未隐藏的接口,一连多也拆开": "Split group input socket, leave the unhidden socket, also split the one-to-many connections", 60 | "拆分组输入接口,并移动到连向接口的附近,留下未隐藏的接口,一连多也拆开": "Split group input socket, and move to the left of the connected to_socket, leave the unhidden socket, also split the one-to-many connections", 61 | "完全拆分组输入接口,并移动到连向的接口(to_socket),并合并连到一个节点上的和距离近的组输入节点": "Completely split group input socket, and move to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 62 | "先合并选中组输入节点,再完全拆分组输入接口,并移动节点们到连向的接口(to_socket),并合并 连到一个节点上的和距离近的组输入节点": "First merge the selected group input nodes, then completely split the group input socket, move the nodes to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 63 | "隐藏未使用组输入接口": "hide unliked group input sockets", 64 | "同时添加输入接口": "Add input socket simultaneously", 65 | "同时添加输出接口": "Add output socket simultaneously", 66 | "简化<组输入合并拆分移动>菜单": "Simplify the menu of Split group input", 67 | "隐藏所有组输入节点未使用的接口": "Hide unused sockets of all group input nodes", 68 | "拆分并移动组输入节点时删除转接点": "Delete reroute when splitting and moving group input nodes", 69 | "选中组输入节点,每一个接口拆分成一个节点": "Select group input nodes, split each socket into a separate node", 70 | "选中组输入节点,合并接口到一个组输入节点": "Select group input nodes, merge sockets into one group input node", 71 | "选中组输入节点,每一个接口/连线拆分成一个节点": "Select group input nodes, split each socket/link into a separate node", 72 | "选中组输入节点,隐藏未连线节口后,拆分组输入接口": "Select group input nodes, hide unlinked sockets, then split group input sockets", 73 | "选中组输入节点,合并连线到一个组输入节点,隐藏未连线节口": "Select group input nodes, merge links into one group input node, hide unlinked sockets", 74 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近": "Select group input nodes, split each link into a separate node, and move to the left of the connected to_socket", 75 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, split each link into a separate node, move to the left of the connected to_socket, and merge group input nodes connected to one node", 76 | "选中组输入节点,先合并成一个节点,再拆分接口,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, first merge into one node, then split sockets, move to the left of the connected to_socket, and merge group input nodes connected to one node", 77 | }, 78 | # "zh_CN": { 79 | # "Add-on Preferences View": "插件偏好设置", 80 | # } 81 | } 82 | 83 | 84 | def i18n(text: str) -> str: 85 | view = bpy.context.preferences.view 86 | language = view.language 87 | trans_interface = view.use_translate_interface 88 | 89 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 90 | return text 91 | else: 92 | if text in dictionary["en_US"]: 93 | return dictionary["en_US"][text] 94 | else: 95 | return text 96 | 97 | 98 | # # Get the language code when addon start up 99 | # __language_code__ = bpy.context.preferences.view.language 100 | 101 | # dictionary["zh_HANS"] = dictionary["zh_CN"] 102 | 103 | 104 | # def i18n(content: str) -> str: 105 | # return i18n_d(content) 106 | 107 | # def i18n_l(content: str) -> str: 108 | # global __language_code__ 109 | # if __language_code__ not in dictionary: 110 | # # return content 111 | # return dictionary["en_US"][content] 112 | 113 | # if content not in dictionary[__language_code__]: 114 | # return content 115 | 116 | # return dictionary[__language_code__][content] 117 | 118 | 119 | # # update the preferences language code and do the translation 120 | # def i18n_d(content: str) -> str: 121 | # global __language_code__ 122 | # __language_code__ = bpy.context.preferences.view.language 123 | # return i18n_l(content) 124 | -------------------------------------------------------------------------------- /node_utilities/小王-节点叠加层预览属性.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import BoolProperty, FloatProperty 3 | from bpy.types import Operator, AddonPreferences 4 | from bpy.app.handlers import persistent 5 | 6 | bl_info = { 7 | "name" : "小王-节点叠加层预览属性", 8 | "author" : "一尘不染", 9 | "description" : "节点叠加层预览属性", 10 | "blender" : (3, 0, 0), 11 | "version" : (4, 0, 0), 12 | "location" : "", 13 | "warning" : "", 14 | "doc_url": "", 15 | "tracker_url": "", 16 | "category" : "Node" 17 | } 18 | 19 | 20 | addon_keymaps = {} 21 | 22 | def find_user_keyconfig(key): 23 | km, kmi = addon_keymaps[key] 24 | for item in bpy.context.window_manager.keyconfigs.user.keymaps[km.name].keymap_items: 25 | found_item = False 26 | if kmi.idname == item.idname: 27 | found_item = True 28 | for name in dir(kmi.properties): 29 | if not name in ["bl_rna", "rna_type"] and not name[0] == "_": 30 | if name in kmi.properties and name in item.properties and not kmi.properties[name] == item.properties[name]: 31 | found_item = False 32 | if found_item: 33 | return item 34 | print(f"Couldn't find keymap item for {key}, using addon keymap instead. This won't be saved across sessions!") 35 | return kmi 36 | 37 | def pref(): 38 | return bpy.context.preferences.addons[__name__].preferences 39 | 40 | def get_view3d_overlays(): 41 | return [area.spaces[0].overlay for area in bpy.context.screen.areas if area.type == "VIEW_3D"] 42 | 43 | def update_show_attr_text(self, context): 44 | for overlay in get_view3d_overlays(): 45 | if hasattr(overlay, 'show_viewer_text'): 46 | overlay.show_viewer_text = self.show_attr_text 47 | 48 | def update_show_attr_color(self, context): 49 | for overlay in get_view3d_overlays(): 50 | overlay.show_viewer_attribute = self.show_attr_color 51 | 52 | def update_view_attr_opacity(self, context): 53 | for overlay in get_view3d_overlays(): 54 | overlay.viewer_attribute_opacity = self.view_attr_opacity 55 | 56 | class Attr_Text_AddonPrefs(AddonPreferences): 57 | bl_idname = __name__ 58 | show_attr_text : BoolProperty(name='show_attr_text', default=False, update=update_show_attr_text) 59 | show_attr_color : BoolProperty(name='show_attr_color', default=False, update=update_show_attr_color) 60 | view_attr_opacity : FloatProperty(name='view_attr_opacity', default=1, update=update_view_attr_opacity, 61 | subtype='FACTOR', min=0, max=1, precision=1) 62 | def draw(self, context): 63 | layout = self.layout 64 | split = layout.split(factor=0.4, align=False) 65 | split.label(text='预览文本', icon_value=0) 66 | split.prop(find_user_keyconfig('预览文本'), 'type', text='', full_event=True) 67 | 68 | split = layout.split(factor=0.4, align=False) 69 | split.label(text='预览透明度', icon_value=0) 70 | split.prop(find_user_keyconfig('预览透明度'), 'type', text='', full_event=True) 71 | 72 | class NODE_OT_show_attr_text(Operator): 73 | bl_idname = "node.show_attr_text" 74 | bl_label = "小王-预览属性为文本" # 显示在设置快捷键时的文本 75 | bl_description = "预览属性为文本" 76 | bl_options = {"REGISTER", "UNDO"} 77 | 78 | def execute(self, context): 79 | pref().show_attr_text = False if pref().show_attr_text else True 80 | return {"FINISHED"} 81 | 82 | class NODE_OT_show_attr_color(Operator): 83 | bl_idname = "node.show_attr_color" 84 | bl_label = "小王-预览属性颜色" 85 | bl_options = {"REGISTER", "UNDO"} 86 | 87 | def execute(self, context): 88 | pref().show_attr_color = False if pref().show_attr_color else True 89 | return {"FINISHED"} 90 | 91 | def draw_show_view_attr_text(self, context): 92 | if context.area.ui_type == "GeometryNodeTree": 93 | layout = self.layout 94 | for overlay in get_view3d_overlays(): 95 | if hasattr(overlay, 'show_viewer_text'): 96 | theme = context.preferences.themes['Default'] 97 | split1 = layout.split(factor=0.6, align=False) 98 | split1.prop(pref(), 'show_attr_text', text='预览文字') 99 | split1.prop(theme.view_3d.space, 'text_hi', text='') 100 | 101 | split2 = layout.split(factor=0.08, align=False) 102 | split2.prop(pref(), 'show_attr_color', text='') 103 | split2.prop(pref(), 'view_attr_opacity', text='预览透明度', emboss=pref().show_attr_color) 104 | 105 | if hasattr(theme.node_editor, "overlay_text_size"): 106 | layout.prop(theme.node_editor, 'overlay_text_size', text='预览文本大小') 107 | else: 108 | layout.prop(context.preferences.ui_styles[0].widget, 'points', text='预览文本大小') 109 | if hasattr(theme.node_editor, "overlay_text_decimals"): 110 | layout.prop(theme.node_editor, 'overlay_text_decimals', text='预览小数位数') 111 | # bpy.context.preferences.themes['Default'].view_3d.space.header_text_hi 112 | 113 | # ! 问题是: 新建文件时:预览文本是False,但是show_attr_text是True 114 | 115 | @persistent 116 | def load_post_handler(dummy): 117 | prefs = pref() 118 | # if not prefs.show_attr_text: 119 | # prefs.show_attr_text = True 120 | # prefs.show_attr_text = False 121 | # if prefs.show_attr_color: 122 | # prefs.show_attr_color = False 123 | # prefs.show_attr_color = True 124 | # if not prefs.show_attr_color: 125 | # prefs.show_attr_color = True 126 | # prefs.show_attr_color = False 127 | 128 | classes =[ 129 | Attr_Text_AddonPrefs, 130 | NODE_OT_show_attr_text, 131 | NODE_OT_show_attr_color 132 | ] 133 | 134 | def register(): 135 | kc = bpy.context.window_manager.keyconfigs.addon 136 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 137 | kmi = km.keymap_items.new('node.show_attr_text', 'THREE', 'PRESS', ctrl=True, alt=False, shift=False, repeat=True) 138 | addon_keymaps['预览文本'] = (km, kmi) 139 | 140 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 141 | kmi = km.keymap_items.new('node.show_attr_color', 'THREE', 'PRESS', ctrl=False, alt=False, shift=True, repeat=True) 142 | addon_keymaps['预览透明度'] = (km, kmi) 143 | 144 | for i in classes: 145 | bpy.utils.register_class(i) 146 | bpy.types.NODE_PT_overlay.append(draw_show_view_attr_text) 147 | bpy.app.handlers.load_post.append(load_post_handler) 148 | 149 | def unregister(): 150 | for km, kmi in addon_keymaps.values(): 151 | km.keymap_items.remove(kmi) 152 | addon_keymaps.clear() 153 | for i in classes: 154 | bpy.utils.unregister_class(i) 155 | bpy.types.NODE_PT_overlay.remove(draw_show_view_attr_text) 156 | bpy.app.handlers.load_post.remove(load_post_handler) 157 | 158 | 159 | -------------------------------------------------------------------------------- /VoronoiLinker/v_MassLinker_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 2 | from .v_tool import * 3 | from .globals import * 4 | from .utils_ui import * 5 | from .utils_node import * 6 | from .utils_color import * 7 | from .utils_solder import * 8 | from .utils_drawing import * 9 | from .utils_translate import * 10 | from .common_forward_func import * 11 | from .common_forward_class import * 12 | from .v_tool import VoronoiToolRoot 13 | 14 | 15 | #"批量链接器" -- 就像链接器一样, 只是一次性处理多个 (显而易见). 16 | #请查看 github 上的 wiki, 看看批量链接器的5个使用示例. 如果你发现这个工具还有其他不寻常的用法, 请告诉我. 17 | class VoronoiMassLinkerTool(VoronoiToolRoot): #"猫狗合体", 既不是节点, 也不是接口. 18 | bl_idname = 'node.voronoi_mass_linker' 19 | bl_label = "Voronoi MassLinker" #唯一一个没有空格的. 因为它太像猫狗了))00)0 20 | # 说真的, 它的确是最奇怪的. 它模仿 VLT 的 dsIsAlwaysLine. 如果从多个连接到一个, SocketArea 会堆叠起来. 它在绘制函数中写入... 21 | # 而且, 正是它出现在/将出现在插件的预览图上, 因为它在所有工具中具有最大的视觉表现力 (而且没有上限). 22 | usefulnessForCustomTree = True 23 | isIgnoreExistingLinks: bpy.props.BoolProperty(name="Ignore existing links", default=False) 24 | def CallbackDrawTool(self, drata): 25 | # TemplateDrawSksToolHh(drata, self.fotagoSk0, self.fotagoSk1, self.fotagoSk2, tool_name="Quick Dimensions - 暂时只输出有效") 26 | # TemplateDrawSksToolHh(drata, None, None, sideMarkHh=-1, isClassicFlow=True, tool_name="Linker") 27 | #这里违反了本地 VL 的读写概念, CallbackDraw 会查找并记录找到的接口, 而不是简单地读取和绘制. 我想这样更容易实现这个工具. 28 | self.list_equalFtgSks.clear() #每次都清除. P.s. 在开始时执行此操作很重要, 而不是在两个节点的分支中. 29 | if not self.ndTar0: 30 | TemplateDrawSksToolHh(drata, None, None, isClassicFlow=True, tool_name="MassLinker") 31 | elif (self.ndTar0)and(not self.ndTar1): 32 | list_ftgSksOut = self.ToolGetNearestSockets(self.ndTar0)[1] 33 | if list_ftgSksOut: 34 | #不知道它会连接到谁, 以及会成功连接到谁 -- 从所有接口开始绘制. 35 | TemplateDrawSksToolHh(drata, *list_ftgSksOut, isDrawText=False, isClassicFlow=True, tool_name="MassLinker") #"全体到光标!" 36 | else: 37 | TemplateDrawSksToolHh(drata, None, None, isClassicFlow=True, tool_name="MassLinker") 38 | else: 39 | list_ftgSksOut = self.ToolGetNearestSockets(self.ndTar0)[1] 40 | list_ftgSksIn = self.ToolGetNearestSockets(self.ndTar1)[0] 41 | for ftgo in list_ftgSksOut: 42 | for ftgi in list_ftgSksIn: 43 | #因为是“批量”的 -- 标准必须自动化, 并对所有情况都统一. 44 | if CompareSkLabelName(ftgo.tar, ftgi.tar, self.prefs.vmltIgnoreCase): #只与名称相同的接口连接. 45 | tgl = False 46 | if self.isIgnoreExistingLinks: #如果是不分青红皂白地连接, 就排除已经存在的“期望”连接. 这是为了美观. 47 | for lk in ftgi.tar.vl_sold_links_final: 48 | #需要检查 is_linked, 以便可以启用已禁用的链接, 替换它们. 49 | if (lk.from_socket.is_linked)and(lk.from_socket==ftgo.tar): 50 | tgl = True 51 | tgl = not tgl 52 | else: #否则, 不要动已经连接的. 53 | tgl = not ftgi.tar.vl_sold_is_final_linked_cou 54 | if tgl: 55 | self.list_equalFtgSks.append( (ftgo, ftgi) ) 56 | if not self.list_equalFtgSks: 57 | DrawVlWidePoint(drata, drata.cursorLoc, col1=drata.dsCursorColor, col2=drata.dsCursorColor) #否则一切都会消失. 58 | for li in self.list_equalFtgSks: 59 | #因为是按名称搜索, 所以这里会绘制并可能在下面同时从两个 (或更多) 接口连接到同一个接口. 就像同名“冲突”一样. 60 | TemplateDrawSksToolHh(drata, li[0], li[1], isDrawText=False, isClassicFlow=True, tool_name="MassLinker") #*[ti for li in self.list_equalFtgSks for ti in li] 61 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 62 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=Cursor_X_Offset): 63 | nd = ftgNd.tar 64 | CheckUncollapseNodeAndReNext(nd, self, cond=isFirstActivation, flag=True) 65 | #除了折叠的节点, 还忽略了转接点, 因为它们的输入总是相同的, 并且名称相同. 66 | if nd.type=='REROUTE': 67 | continue 68 | self.ndTar1 = nd 69 | if isFirstActivation: 70 | self.ndTar0 = nd #这里的输出节点只设置一次. 71 | if self.ndTar0==self.ndTar1: #检查是否是自我复制. 72 | self.ndTar1 = None #这里的输入节点在失败时每次都会被清空. 73 | #注意: 第一次找到 ndTar1 时 -- list_equalFtgSks == []. 74 | if self.ndTar1: 75 | list_ftgSksIn = self.ToolGetNearestSockets(self.ndTar1)[0] #仅为了展开的条件. 也可以用 list_equalFtgSks, 但又会有跳帧问题. 76 | CheckUncollapseNodeAndReNext(nd, self, cond=list_ftgSksIn, flag=False) 77 | break 78 | def MatterPurposePoll(self): 79 | return self.list_equalFtgSks 80 | def MatterPurposeTool(self, event, prefs, tree): 81 | if True: 82 | #如果一个节点的输出和另一个节点的输入总共有4个同名接口, 就会发生与工具预期不符的行为. 83 | #因此, 每个输入接口只连接一个链接 (多输入接口不计). 84 | set_alreadyDone = set() 85 | list_skipToEndEq = [] 86 | list_skipToEndSk = [] 87 | for li in self.list_equalFtgSks: 88 | sko = li[0].tar 89 | ski = li[1].tar 90 | if ski in set_alreadyDone: 91 | continue 92 | if sko in list_skipToEndSk: #注意: 线性读取就足够了, 但暂时保持这样以确保万无一失. 93 | list_skipToEndEq.append(li) 94 | continue 95 | tree.links.new(sko, ski) #注意: 考虑到批量连接和数量不限, 最好还是保留安全的“原始”连接. 96 | VlrtRememberLastSockets(sko, ski) #注意: 这行和后面的 -- “最后一个永远是最后一个”, 更高效的下面检查已经无法实现了; 至少以我的知识水平是这样. 97 | if not ski.is_multi_input: #"多输入是无底洞!" 98 | set_alreadyDone.add(ski) 99 | list_skipToEndSk.append(sko) 100 | #接下来处理上一个循环中跳过的. 101 | for li in list_skipToEndEq: 102 | sko = li[0].tar 103 | ski = li[1].tar 104 | if ski in set_alreadyDone: 105 | continue 106 | set_alreadyDone.add(ski) 107 | tree.links.new(sko, ski) 108 | VlrtRememberLastSockets(sko, ski) 109 | else: 110 | for li in self.list_equalFtgSks: 111 | tree.links.new(li[0].tar, li[1].tar) #连接所有! 112 | def InitTool(self, event, prefs, tree): 113 | self.ndTar0 = None 114 | self.ndTar1 = None 115 | self.list_equalFtgSks = [] 116 | @staticmethod 117 | def LyDrawInAddonDiscl(col, prefs): 118 | LyAddLeftProp(col, prefs,'vmltIgnoreCase') 119 | @classmethod 120 | def BringTranslations(cls): 121 | with VlTrMapForKey(GetAnnotFromCls(cls,'isIgnoreExistingLinks').name) as dm: 122 | dm["ru_RU"] = "Игнорировать существующие связи" 123 | dm["zh_CN"] = "忽略现有链接" 124 | ## 125 | with VlTrMapForKey(GetPrefsRnaProp('vmltIgnoreCase').name) as dm: 126 | dm["ru_RU"] = "Игнорировать регистр" 127 | dm["zh_CN"] = "忽略接口名称的大小写" -------------------------------------------------------------------------------- /VoronoiLinker/v_QuickDimensions_tool.py: -------------------------------------------------------------------------------- 1 | from .v_tool import * 2 | from .globals import * 3 | from .utils_ui import * 4 | from .utils_node import * 5 | from .utils_color import * 6 | from .utils_solder import * 7 | from .utils_drawing import * 8 | from .utils_translate import * 9 | from .common_forward_func import * 10 | from .common_forward_class import * 11 | from .v_tool import VoronoiToolTripleSk 12 | from .common_forward_func import * 13 | from .utils_node import GetListOfNdEnums, remember_add_link, opt_ftg_socket 14 | from .rot_or_mat_convert import Convert_Data, PIE_MT_Convert_Rotation_To, PIE_MT_Separate_Matrix 15 | from .globals import Cursor_X_Offset 16 | from .utils_drawing import TemplateDrawSksToolHh 17 | 18 | def get_dimension_node(tree: NodeTree, sk_type: str): 19 | return AllQuickDimensions.get(tree.bl_idname, None).get(sk_type, None) 20 | 21 | class VoronoiQuickDimensionsTool(VoronoiToolTripleSk): 22 | bl_idname = 'node.voronoi_quick_dimensions' 23 | bl_label = "Voronoi Quick Dimensions" 24 | usefulnessForCustomTree = False 25 | canDrawInAddonDiscl = False 26 | isPlaceImmediately: bpy.props.BoolProperty(name="Place immediately", default=False) 27 | def CallbackDrawTool(self, drata): 28 | # print(f"Quick Dimensions 类里 {drata = }") 29 | # TemplateDrawSksToolHh(drata, self.fotagoSk0, self.fotagoSk1, self.fotagoSk2) 30 | TemplateDrawSksToolHh(drata, self.fotagoSk0, self.fotagoSk1, self.fotagoSk2, tool_name="Quick Dimensions - 暂时只输出有效") 31 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 32 | if isFirstActivation: 33 | self.fotagoSk0 = None 34 | if not self.canPickThird: 35 | self.fotagoSk1 = None 36 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=Cursor_X_Offset): 37 | nd = ftgNd.tar 38 | list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=Cursor_X_Offset)[1] 39 | if not list_ftgSksOut: 40 | continue 41 | if isFirstActivation: 42 | for ftg in list_ftgSksOut: 43 | # set_utilTypeSkFields 小王-Alt D 支持的接口 44 | # if (ftg.tar.type in set_utilTypeSkFields)or(ftg.tar.type=='GEOMETRY'): 45 | if get_dimension_node(tree, ftg.tar.type): 46 | self.fotagoSk0 = ftg 47 | break 48 | CheckUncollapseNodeAndReNext(nd, self, cond=True, flag=True) 49 | break 50 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk1, flag=False) 51 | sk_out0 = opt_ftg_socket(self.fotagoSk0) 52 | if sk_out0: 53 | if not get_dimension_node(tree, sk_out0.type): 54 | break 55 | if not self.canPickThird: 56 | for ftg in list_ftgSksOut: 57 | if ftg.tar.type==sk_out0.type: 58 | self.fotagoSk1 = ftg 59 | break 60 | if (self.fotagoSk1)and(self.fotagoSk1.tar==sk_out0): 61 | self.fotagoSk1 = None 62 | break 63 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk1, flag=False) 64 | if self.fotagoSk1: 65 | break 66 | else: 67 | skOut1 = opt_ftg_socket(self.fotagoSk1) 68 | for ftg in list_ftgSksOut: 69 | if ftg.tar.type==sk_out0.type: 70 | self.fotagoSk2 = ftg 71 | break 72 | if (self.fotagoSk2)and( (self.fotagoSk2.tar==sk_out0)or(skOut1)and(self.fotagoSk2.tar==skOut1) ): 73 | self.fotagoSk2 = None 74 | break 75 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk2, flag=False) 76 | if self.fotagoSk2: 77 | break 78 | def MatterPurposePoll(self): 79 | return not not self.fotagoSk0 80 | def MatterPurposeTool(self, event, prefs, tree): 81 | sk_out0 = self.fotagoSk0.tar 82 | Q_Dimensions = AllQuickDimensions.get(tree.bl_idname, None) 83 | if not Q_Dimensions: 84 | return {'CANCELLED'} 85 | isOutNdCol = sk_out0.node.bl_idname==Q_Dimensions['RGBA'][0] #Заметка: Нод разделения; на выходе всегда флоаты. 86 | isGeoTree = tree.bl_idname=='GeometryNodeTree' 87 | isOutNdQuat = (isGeoTree)and(sk_out0.node.bl_idname==Q_Dimensions['ROTATION'][0]) 88 | #Добавить: 89 | if sk_out0.type == "ROTATION": # 小王-Alt D 旋转接口 90 | Convert_Data.sk0 = sk_out0 91 | if self.fotagoSk1: 92 | Convert_Data.sk1 = self.fotagoSk1.tar 93 | if self.fotagoSk2: 94 | Convert_Data.sk2 = self.fotagoSk2.tar 95 | bpy.ops.wm.call_menu_pie(name=PIE_MT_Convert_Rotation_To.bl_idname) 96 | elif sk_out0.type == "MATRIX": # 小王-Alt D 矩阵接口 97 | Convert_Data.sk0 = sk_out0 98 | if self.fotagoSk1: 99 | Convert_Data.sk1 = self.fotagoSk1.tar 100 | if self.fotagoSk2: 101 | Convert_Data.sk2 = self.fotagoSk2.tar 102 | bpy.ops.wm.call_menu_pie(name=PIE_MT_Separate_Matrix.bl_idname) 103 | else: 104 | bpy.ops.node.add_node('INVOKE_DEFAULT', 105 | type=Q_Dimensions[sk_out0.type][isOutNdCol if not isOutNdQuat else 2], 106 | use_transform=not self.isPlaceImmediately) 107 | aNd = tree.nodes.active 108 | aNd.width = 140 109 | if aNd.bl_idname in {Q_Dimensions['RGBA'][0], Q_Dimensions['VALUE'][1]}: #|3|. 110 | aNd.show_options = False # 不加区分地隐藏(所有选项)不太美观, 所以才有了上面的检查. 111 | if aNd.bl_idname == 'GeometryNodeStringToCurves': #|3|. 112 | aNd.show_options = False 113 | aNd.outputs[-1].hide = True 114 | aNd.outputs[-2].hide = True 115 | for i in range(2, 6): 116 | aNd.inputs[i].hide = True 117 | if sk_out0.type in {'VECTOR', 'RGBA', 'ROTATION'}: # 但这样做的好处是, 可以为每种类型节省显式的定义. 118 | aNd.inputs[0].hide_value = True 119 | # 设置相同的模式 (例如, RGB 和 HSV): 120 | for li in GetListOfNdEnums(aNd): 121 | if hasattr(sk_out0.node, li.identifier): 122 | setattr(aNd, li.identifier, getattr(sk_out0.node, li.identifier)) 123 | # 连接: 124 | skIn = aNd.inputs[0] 125 | # 遍历新建节点的输入接口,是否和目标输出接口同名,分离z 连到 合并z 126 | for ski in aNd.inputs: 127 | if sk_out0.name == ski.name and sk_out0.type == ski.type: 128 | skIn = ski 129 | break 130 | remember_add_link(sk_out0, skIn) 131 | if self.fotagoSk1: 132 | remember_add_link(self.fotagoSk1.tar, aNd.inputs[1]) 133 | if self.fotagoSk2: 134 | remember_add_link(self.fotagoSk2.tar, aNd.inputs[2]) 135 | 136 | if sk_out0.type in ["CLOSURE", "BUNDLE"]: 137 | bpy.ops.node.sockets_sync() 138 | -------------------------------------------------------------------------------- /node_socket_location/获取接口位置_精简版本.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from bpy.types import NodeSocket 3 | from mathutils import Vector as Vec2 4 | from pprint import pprint 5 | 6 | def sk_loc(socket: NodeSocket): 7 | return Vec2((ctypes.c_float * 2).from_address(ctypes.c_void_p.from_address(socket.as_pointer() + 520).value + 24)) 8 | 9 | def sk_loc(socket: NodeSocket): 10 | runtime_address = socket.as_pointer() + 520 11 | runtime_pointer = ctypes.c_void_p.from_address(runtime_address) 12 | loc_address = runtime_pointer.value + 24 13 | Float2 = ctypes.c_float * 2 14 | loc = Float2.from_address(loc_address) 15 | return Vec2(loc[:]) 16 | 17 | def sk_loc(socket: NodeSocket): 18 | """ 直接从地址创建最终的目标类型对象 """ 19 | #😍 offset 偏移量 是一个字段相对于其结构体起始位置的字节距离 20 | #😡 runtime 字段在 bNodeSocket 中的偏移量是 520, location 在 bNodeSocketRuntime 里的偏移量是 24 21 | runtime_address = socket.as_pointer() + 520 22 | #🤢 void* 不包含类型信息, 是 C/C++的“通用/泛型指针”,任何类型的指针都可以被安全隐式地转换成 void* 类型 23 | runtime_pointer = ctypes.c_void_p.from_address(runtime_address) 24 | loc_address = runtime_pointer.value + 24 25 | #👊🏿 Float2 是一个自定义的 ctypes 数组类型 26 | Float2 = ctypes.c_float * 2 27 | # 创建一个数组类型对象,在这个类型对象上调用 from_address 方法 28 | loc = Float2.from_address(loc_address) 29 | return Vec2(loc[:]) 30 | 31 | component_map = { 32 | 0: "Mesh", 33 | 1: "PointCloud", 34 | 2: "Instance", 35 | 3: "Volume", 36 | 4: "Curve", 37 | 5: "Edit", 38 | 6: "GreasePencil", 39 | } 40 | 41 | from ctypes import c_int, c_void_p 42 | 43 | def sk_support_types(socket: NodeSocket): 44 | if socket.type != "GEOMETRY": return 45 | runtime_ptr_addr = socket.as_pointer() + 520 46 | runtime_addr = c_void_p.from_address(runtime_ptr_addr).value 47 | declare_ptr_addr = runtime_addr + 8 48 | declare_addr = c_void_p.from_address(declare_ptr_addr).value 49 | declare_addr -= 8 * 5 50 | support_type_addr = declare_addr + 424 - 8 * 2 51 | begain_addr = c_void_p.from_address(support_type_addr).value 52 | end_addr = c_void_p.from_address(support_type_addr + 8).value 53 | 54 | support_num = int((end_addr - begain_addr) / 4) 55 | types: list[str] = [] 56 | if support_num: 57 | for i in range(support_num): 58 | support_type = c_int.from_address(begain_addr + i*4).value 59 | types.append(component_map[support_type]) 60 | else: 61 | types.append("支持全部类型") 62 | return types 63 | 64 | 65 | """ 66 | 😍 as_pointer + 520 是 runtime 指针成员在 bNodeSocket 里的地址 67 | 😍 declare_ptr 是 runtime(bNodeTreeRuntime) 类里的第一个成员, +8 虚函数表指针? 68 | 🤢 不知道为什么VS 查看类内存布局不准确,in_out 前有5 个40/48字节的, 难道每个多了8字节? 69 | 🤢 不准确 + 1, support_type 前有2 个64 字节的, 难道每个多了8字节? 70 | 😍 424 是 Geometry 里第一个成员, 但是继承,所以起始地址 424 (class Geometry : SocketDeclaration) """ 71 | 72 | def sk_support_types(socket: NodeSocket): 73 | if socket.type != "GEOMETRY": return 74 | runtime_ptr_addr = socket.as_pointer() + 520 75 | runtime_addr = c_void_p.from_address(runtime_ptr_addr).value 76 | declare_ptr_addr = runtime_addr + 8 77 | declare_addr = c_void_p.from_address(declare_ptr_addr).value 78 | declare_addr -= 8 * 5 79 | # in_out = c_int.from_address(declare_addr + 224) 80 | # align_pre_sk = c_bool.from_address(declare_addr + 240).value 81 | # return in_out, f"{align_pre_sk=}" 82 | support_type_addr = declare_addr + 424 - 8 * 2 83 | # supported_types_ 是 blender::Vector, begain_ 和 end_ 是Vector里前两个指针成员 84 | begain_addr = c_void_p.from_address(support_type_addr).value 85 | end_addr = c_void_p.from_address(support_type_addr + 8).value 86 | 87 | support_num = int((end_addr - begain_addr) / 4) 88 | types: list[str] = [] 89 | if support_num: 90 | for i in range(support_num): 91 | support_type = c_int.from_address(begain_addr + i*4).value 92 | types.append(component_map[support_type]) 93 | else: 94 | types.append("支持全部类型") 95 | return types 96 | 97 | 98 | # - source\blender\blenkernel\BKE_geometry_set.hh 99 | # class GeometryComponent : public ImplicitSharingMixin { 100 | # public: 101 | # enum class Type { 102 | # Mesh = 0, 103 | # PointCloud = 1, 104 | # Instance = 2, 105 | # Volume = 3, 106 | # Curve = 4, 107 | # Edit = 5, 108 | # GreasePencil = 6, 109 | # }; ......} 110 | 111 | #👊🏿 处理 SocketDeclaration 112 | # class ItemDeclaration # - source\blender\nodes\NOD_node_declaration.hh 113 | # class SocketDeclaration : public ItemDeclaration 114 | # class Geometry : public SocketDeclaration # - source\blender\nodes\NOD_socket_declarations_geometry.hh 115 | # ItemDeclaration 16字节 116 | # SocketDeclaration 416字节 117 | # Geometry 480字节 supported_types_偏移量416,大小56 118 | # geometry_decl = SocketDeclarationGeometry.from_address(declaration_obj_addr) 119 | 120 | 121 | 122 | 123 | 124 | # class Geometry : public SocketDeclaration { 125 | # private: 126 | # blender::Vector supported_types_; 127 | # bool only_realized_data_ = false; 128 | # bool only_instances_ = false; 129 | # friend GeometryBuilder; 130 | # public: 131 | # static constexpr eNodeSocketDatatype static_socket_type = SOCK_GEOMETRY; 132 | # using Builder = GeometryBuilder; 133 | # bNodeSocket &build(bNodeTree &ntree, bNode &node) const override; 134 | # bool matches(const bNodeSocket &socket) const override; 135 | # bool can_connect(const bNodeSocket &socket) const override; 136 | # Span supported_types() const; 137 | # bool only_realized_data() const; 138 | # bool only_instances() const; 139 | # }; 140 | # 141 | 142 | # class bNodeSocketRuntime : NonCopyable, NonMovable { 143 | # public: 144 | # const nodes::SocketDeclaration *declaration = nullptr; 145 | # uint32_t changed_flag = 0; 146 | # short total_inputs = 0; 147 | # float2 location; ......} 148 | 149 | 150 | 151 | #👊🏿 c++里是这样写的获取 supported_types 152 | # static std::optional create_declaration_inspection_string(const bNodeSocket &socket){ 153 | # fmt::memory_buffer buf; 154 | # if (const nodes::decl::Geometry *socket_decl = dynamic_cast(socket.runtime->declaration)){ 155 | # create_inspection_string_for_geometry_socket(buf, socket_decl); } ......} 156 | 157 | 158 | # static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf, const nodes::decl::Geometry *socket_decl){ 159 | # if (socket_decl == nullptr || socket_decl->in_out == SOCK_OUT) { return;} 160 | 161 | # Span supported_types = socket_decl->supported_types(); 162 | # if (supported_types.is_empty()) { 163 | # fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: All Types")); 164 | # return; 165 | # } 166 | # fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: ")); 167 | # for (bke::GeometryComponent::Type type : supported_types) { 168 | # switch (type) { 169 | # case bke::GeometryComponent::Type::Mesh: { 170 | # fmt::format_to(fmt::appender(buf), "{}", TIP_("Mesh")); 171 | # break; 172 | # } 173 | # case bke::GeometryComponent::Type::PointCloud: { 174 | # fmt::format_to(fmt::appender(buf), "{}", TIP_("Point Cloud")); 175 | # break; 176 | # } 177 | # ......} 178 | -------------------------------------------------------------------------------- /group_input_helper/translator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | dictionary = { 4 | "DEFAULT": {}, 5 | "en_US": { 6 | "几何数据": "Geometry", 7 | "布尔": "Boolean", 8 | "浮点": "Float", 9 | "整数": "Integer", 10 | "矢量": "Vector", 11 | "颜色": "Color", 12 | "旋转": "Rotation", 13 | "矩阵": "Matrix", 14 | "菜单": "Menu", 15 | "材质": "Material", 16 | "物体": "Object", 17 | "集合": "Collection", 18 | "图像": "Image", 19 | "纹理": "Texture", 20 | "闭包": "Closure", 21 | "捆包": "Bundle", 22 | "字符串": "String", 23 | "着色器": "Shader", 24 | "添加输入输出接口": "Add input/output socket for current node tree", 25 | "给活动节点组添加接口": "Add socket for active group node", 26 | "w-节点组输入助手-添加拆分合并移动": "Group input helper-Add Split Merge Move", 27 | "组输入助手": "Group input helper", 28 | "快速添加组输入节点-拆分合并移动组输入节点-快速添加组输入输出接口": "Qucik add and split merge move Group Input node-Qucik add Group Input Output socket", 29 | "偏好设置-标题栏-N面板": "Preferences-Editor Bar-N panel", 30 | "组输入": "Group input", 31 | "w-组输入拆分": "Group input split", 32 | "w-添加组输入输出接口": "Add group input output interface", 33 | "显示面板名字": "Show Panel name", 34 | "添加组输入菜单里显示接口所属面板名字": "Show Panel name of socket in Group input add menu", 35 | "组输入隐藏节口": "Group input hide socket", 36 | "添加一个只剩目标接口没被隐藏的组输入节点": "add a group input node that only exposes the target socket while hiding the others.", 37 | "空": "None", 38 | "标题栏和N面板显示组输入拆分": "Title bar and N panel display group input split", 39 | "添加组输入": "Add group input", 40 | "添加组输入节点": "Add group input node", 41 | "w-添加组输入": "Add group input", 42 | "添加组输入-菜单": "Add group input - menu", 43 | "添加组输入-面板": "Add group input - panel", 44 | "组输入助手-面板": "Group input helper - panel", 45 | "添加组输入输出接口": "Add group input output socket", 46 | "添加输入输出接口": "Add input output socket", 47 | "节点树": "NodeTree", 48 | "输入接口": "Input socket", 49 | "输出接口": "Output socket", 50 | "组输入合并拆分移动": "Group input merge split move", 51 | "w-组输入拆分合并移动": "Group input split merge move", 52 | "组输入拆分合并移动": "Group input split merge move", 53 | "合并组输入接口": "Merge group input socket", 54 | "合并组输入连线": "Merge group input links", 55 | "留下未隐藏的接口": "Split group input socket, leave the unhidden socket", 56 | "拆分组输入接口": "Split group input socket", 57 | "拆分组输入并移动": "Split group input and move", 58 | "拆分组输入并移动合并": "Split group input and move merge", 59 | "拆分组输入": "Split group input", 60 | "合并节点并拆分": "Merge group input and split", 61 | "合并组输入连线": "Merge group input links", 62 | "拆分组输入连线": "Split group input links", 63 | "完全拆分并移动": "Completely split and move", 64 | "拆分并移动合并": "Split and move merge", 65 | "完全拆分": "Completely split", 66 | "拆分组输入接口,只留下连线的接口": "Split group input socket, only leave the linked socket", 67 | "拆分组输入接口,留下未隐藏的接口,一连多也拆开": "Split group input socket, leave the unhidden socket, also split the one-to-many connections", 68 | "拆分组输入接口,并移动到连向接口的附近,留下未隐藏的接口,一连多也拆开": "Split group input socket, and move to the left of the connected to_socket, leave the unhidden socket, also split the one-to-many connections", 69 | "完全拆分组输入接口,并移动到连向的接口(to_socket),并合并连到一个节点上的和距离近的组输入节点": "Completely split group input socket, and move to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 70 | "先合并选中组输入节点,再完全拆分组输入接口,并移动节点们到连向的接口(to_socket),并合并 连到一个节点上的和距离近的组输入节点": "First merge the selected group input nodes, then completely split the group input socket, move the nodes to the connected to_socket, and merge the group input nodes that are connected to one node and close in distance", 71 | "隐藏未使用组输入接口": "hide unliked group input sockets", 72 | "同时添加输入接口": "Add input socket simultaneously", 73 | "同时添加输出接口": "Add output socket simultaneously", 74 | "简化<组输入合并拆分移动>菜单": "Simplify the menu of Split group input", 75 | "隐藏所有组输入节点未使用的接口": "Hide unused sockets of all group input nodes", 76 | "拆分并移动组输入节点时删除转接点": "Delete reroute when splitting and moving group input nodes", 77 | "选中组输入节点,每一个接口拆分成一个节点": "Select group input nodes, split each socket into a separate node", 78 | "选中组输入节点,合并接口到一个组输入节点": "Select group input nodes, merge sockets into one group input node", 79 | "选中组输入节点,每一个接口/连线拆分成一个节点": "Select group input nodes, split each socket/link into a separate node", 80 | "选中组输入节点,隐藏未连线节口后,拆分组输入接口": "Select group input nodes, hide unlinked sockets, then split group input sockets", 81 | "选中组输入节点,合并连线到一个组输入节点,隐藏未连线节口": "Select group input nodes, merge links into one group input node, hide unlinked sockets", 82 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近": "Select group input nodes, split each link into a separate node, and move to the left of the connected to_socket", 83 | "选中组输入节点,每一个连线拆分成一个节点,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, split each link into a separate node, move to the left of the connected to_socket, and merge group input nodes connected to one node", 84 | "选中组输入节点,先合并成一个节点,再拆分接口,并移动到连向接口(to_socket)的附近,并合并连到一个节点上的组输入节点": "Select group input nodes, first merge into one node, then split sockets, move to the left of the connected to_socket, and merge group input nodes connected to one node", 85 | "接口描述: ": "Socket description: ", 86 | "● 默认: 根据面板重命名 \n● Ctrl: 不重命名节点": "● Default: Rename node based on the panel name. \n● Ctrl: Do not rename the node.", 87 | "● 默认: 根据面板重命名 \n● Ctrl: 不重命名节点 \n● Shift:根据接口重命名": "● Default: Rename node based on the panel name. \n● Ctrl: Do not rename the node. \n● Shift: Rename node based on the socket name.", 88 | }, 89 | # "zh_CN": { 90 | # "Add-on Preferences View": "插件偏好设置", 91 | # } 92 | } 93 | 94 | 95 | def i18n(text: str) -> str: 96 | view = bpy.context.preferences.view 97 | language = view.language 98 | trans_interface = view.use_translate_interface 99 | 100 | if language in ["zh_CN", "zh_HANS"] and trans_interface: 101 | return text 102 | else: 103 | if text in dictionary["en_US"]: 104 | return dictionary["en_US"][text] 105 | else: 106 | return text 107 | 108 | 109 | # # Get the language code when addon start up 110 | # __language_code__ = bpy.context.preferences.view.language 111 | 112 | # dictionary["zh_HANS"] = dictionary["zh_CN"] 113 | 114 | 115 | # def i18n(content: str) -> str: 116 | # return i18n_d(content) 117 | 118 | # def i18n_l(content: str) -> str: 119 | # global __language_code__ 120 | # if __language_code__ not in dictionary: 121 | # # return content 122 | # return dictionary["en_US"][content] 123 | 124 | # if content not in dictionary[__language_code__]: 125 | # return content 126 | 127 | # return dictionary[__language_code__][content] 128 | 129 | 130 | # # update the preferences language code and do the translation 131 | # def i18n_d(content: str) -> str: 132 | # global __language_code__ 133 | # __language_code__ = bpy.context.preferences.view.language 134 | # return i18n_l(content) 135 | -------------------------------------------------------------------------------- /VoronoiLinker/v_Swapper_tool.py: -------------------------------------------------------------------------------- 1 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 2 | from .v_tool import * 3 | from .globals import * 4 | from .utils_ui import * 5 | from .utils_node import * 6 | from .utils_color import * 7 | from .utils_solder import * 8 | from .utils_drawing import * 9 | from .utils_translate import * 10 | from .common_forward_func import * 11 | from .common_forward_class import * 12 | from .v_tool import VoronoiToolPairSk 13 | 14 | 15 | fitVstModeItems = ( ('SWAP', "Swap", "All links from the first socket will be on the second, from the second on the first"), 16 | ('ADD', "Add", "Add all links from the second socket to the first one"), 17 | ('TRAN', "Transfer", "Move all links from the second socket to the first one with replacement") ) 18 | class VoronoiSwapperTool(VoronoiToolPairSk): 19 | bl_idname = 'node.voronoi_swaper' 20 | bl_label = "Voronoi Swapper" 21 | usefulnessForCustomTree = True 22 | canDrawInAddonDiscl = False 23 | toolMode: bpy.props.EnumProperty(name="Mode", default='SWAP', items=fitVstModeItems) 24 | isCanAnyType: bpy.props.BoolProperty(name="Can swap with any socket type", default=False) 25 | def CallbackDrawTool(self, drata): # 我模仿着加的 26 | # 小王-模式名匹配 27 | name = { 'SWAP': "交换连线", 28 | 'ADD': "移动并加入连线", 29 | 'TRAN': "移动并替换连线", 30 | } 31 | mode= name[self.toolMode] 32 | TemplateDrawSksToolHh(drata, self.fotagoSk0, self.fotagoSk1, tool_name=mode,) 33 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 34 | if isFirstActivation: 35 | self.fotagoSk0 = None 36 | self.fotagoSk1 = None 37 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 38 | nd = ftgNd.tar 39 | CheckUncollapseNodeAndReNext(nd, self, cond=isFirstActivation, flag=True) 40 | list_ftgSksIn, list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off=0) 41 | #基于Mixer的标准. 42 | if isFirstActivation: 43 | ftgSkOut, ftgSkIn = None, None 44 | for ftg in list_ftgSksOut: #todo0NA 但这不就是Findanysk吗!? 45 | if ftg.blid!='NodeSocketVirtual': 46 | ftgSkOut = ftg 47 | break 48 | for ftg in list_ftgSksIn: 49 | if ftg.blid!='NodeSocketVirtual': 50 | ftgSkIn = ftg 51 | break 52 | #也允许对输入接口使用“添加”功能,但仅限于多输入接口,因为这很明显 53 | if (self.toolMode=='ADD')and(ftgSkIn): 54 | #按类型检查,而不是按'is_multi_input',这样就可以从常规输入添加到多输入. 55 | if (ftgSkIn.blid not in ('NodeSocketGeometry','NodeSocketString')):#or(not ftgSkIn.tar.is_multi_input): #没有第二个条件可能性更多. 56 | ftgSkIn = None 57 | self.fotagoSk0 = MinFromFtgs(ftgSkOut, ftgSkIn) 58 | #这里积累了很多奇怪的关于None等的检查 -- 这是我将自己发明的许多高级函数连接在一起的结果. 59 | skOut0 = opt_ftg_socket(self.fotagoSk0) 60 | if skOut0: 61 | for ftg in list_ftgSksOut if skOut0.is_output else list_ftgSksIn: 62 | if ftg.blid=='NodeSocketVirtual': 63 | continue 64 | if (self.isCanAnyType)or(skOut0.bl_idname==ftg.blid)or(self.SkBetweenFieldsCheck(skOut0, ftg.tar)): 65 | self.fotagoSk1 = ftg 66 | if self.fotagoSk1: #如果成功则停止搜索. 67 | break 68 | if (self.fotagoSk1)and(skOut0==self.fotagoSk1.tar): #检查是否为自我复制. 69 | self.fotagoSk1 = None 70 | break #当isFirstActivation==False且接口为自我复制时,为isCanAnyType中断循环;以免一次找到两个节点. 71 | if not self.isCanAnyType: 72 | if not(self.fotagoSk1 or isFirstActivation): #如果没有结果,则继续搜索. 73 | continue 74 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk1, flag=False) 75 | break 76 | def MatterPurposePoll(self): 77 | return self.fotagoSk0 and self.fotagoSk1 78 | def MatterPurposeTool(self, event, prefs, tree): 79 | skIo0 = self.fotagoSk0.tar 80 | skIo1 = self.fotagoSk1.tar 81 | match self.toolMode: 82 | case 'SWAP': 83 | #交换第一个和第二个接口的所有连接: 84 | list_memSks = [] 85 | if skIo0.is_output: #检查 is_output 的一致性是 NextAssignmentTool() 的任务. 86 | for lk in skIo0.vl_sold_links_final: 87 | if lk.to_node!=skIo1.node: # T 1 以防止节点创建指向自身的连接。需要检查所有情况并且不处理此类连接. 88 | list_memSks.append(lk.to_socket) 89 | tree.links.remove(lk) 90 | for lk in skIo1.vl_sold_links_final: 91 | if lk.to_node!=skIo0.node: # T 0 ^ 92 | tree.links.new(skIo0, lk.to_socket) 93 | if lk.to_socket.is_multi_input: #对于多输入接口则删除. 94 | tree.links.remove(lk) 95 | for li in list_memSks: 96 | tree.links.new(skIo1, li) 97 | else: 98 | for lk in skIo0.vl_sold_links_final: 99 | if lk.from_node!=skIo1.node: # F 1 ^ 100 | list_memSks.append(lk.from_socket) 101 | tree.links.remove(lk) 102 | for lk in skIo1.vl_sold_links_final: 103 | if lk.from_node!=skIo0.node: # F 0 ^ 104 | tree.links.new(lk.from_socket, skIo0) 105 | tree.links.remove(lk) 106 | for li in list_memSks: 107 | tree.links.new(li, skIo1) 108 | case 'ADD'|'TRAN': 109 | #只需将第一个接口的连接添加到第二个接口。即合并、添加. 110 | if self.toolMode=='TRAN': 111 | #与添加相同,只是第一个接口会丢失连接. 112 | for lk in skIo1.vl_sold_links_final: 113 | tree.links.remove(lk) 114 | if skIo0.is_output: 115 | for lk in skIo0.vl_sold_links_final: 116 | if lk.to_node!=skIo1.node: # T 1 ^ 117 | tree.links.new(skIo1, lk.to_socket) 118 | if lk.to_socket.is_multi_input: #没有这个,lk仍然会指向“已添加”的连接,从而被删除。因此需要对多输入进行显式检查. 119 | tree.links.remove(lk) 120 | else: #为了多输入接口而添加. 121 | for lk in skIo0.vl_sold_links_final: 122 | if lk.from_node!=skIo1.node: # F 1 ^ 123 | tree.links.new(lk.from_socket, skIo1) 124 | tree.links.remove(lk) 125 | #VST VLRT是不需要的,对吧? 126 | @classmethod 127 | def BringTranslations(cls): 128 | tran = GetAnnotFromCls(cls,'toolMode').items 129 | with VlTrMapForKey(tran.SWAP.name) as dm: 130 | dm["ru_RU"] = "Поменять" 131 | dm["zh_CN"] = "交换" 132 | with VlTrMapForKey(tran.SWAP.description) as dm: 133 | dm["ru_RU"] = "Все линки у первого сокета будут на втором, у второго на первом." 134 | dm["zh_CN"] = "第一个接口的所有连线将移至第二个,第二个的将移至第一个。" 135 | with VlTrMapForKey(tran.ADD.name) as dm: 136 | dm["ru_RU"] = "Добавить" 137 | dm["zh_CN"] = "添加" 138 | with VlTrMapForKey(tran.ADD.description) as dm: 139 | dm["ru_RU"] = "Добавить все линки со второго сокета на первый. Второй будет пустым." 140 | dm["zh_CN"] = "将第二个接口的所有连线添加到第一个。第二个将变为空。" 141 | with VlTrMapForKey(tran.TRAN.name) as dm: 142 | dm["ru_RU"] = "Переместить" 143 | dm["zh_CN"] = "转移" 144 | with VlTrMapForKey(tran.TRAN.description) as dm: 145 | dm["ru_RU"] = "Переместить все линки со второго сокета на первый с заменой." 146 | dm["zh_CN"] = "将第二个接口的所有连线转移到第一个并替换现有连线。" 147 | with VlTrMapForKey(GetAnnotFromCls(cls,'isCanAnyType').name) as dm: 148 | dm["ru_RU"] = "Может меняться с любым типом" 149 | dm["zh_CN"] = "可以与任何类型交换" -------------------------------------------------------------------------------- /VoronoiLinker/C_Structure.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import bpy 3 | from bpy.types import NodeSocket 4 | from .globals import isWin, is_bl4_plus, is_bl5_plus 5 | # from typing import cast 6 | 7 | class StructBase(ctypes.Structure): 8 | _subclasses = [] 9 | __annotations__ = {} 10 | 11 | def __init_subclass__(cls): 12 | cls._subclasses.append(cls) 13 | 14 | @staticmethod 15 | def _init_structs(): 16 | functype = type(lambda: None) 17 | for cls in StructBase._subclasses: 18 | fields = [] 19 | for field, value in cls.__annotations__.items(): 20 | if isinstance(value, functype): 21 | value = value() 22 | fields.append((field, value)) 23 | if fields: 24 | cls._fields_ = fields 25 | cls.__annotations__.clear() 26 | StructBase._subclasses.clear() 27 | 28 | @classmethod 29 | def GetFields(cls, tar): 30 | return cls.from_address(tar.as_pointer()) 31 | 32 | class BNodeSocketRuntimeHandle(StructBase): # \source\blender\makesdna\DNA_node_types.h 33 | if isWin: 34 | _pad0 : ctypes.c_char*8 35 | declaration : ctypes.c_void_p 36 | changed_flag : ctypes.c_uint32 37 | total_inputs : ctypes.c_short 38 | _pad1 : ctypes.c_char*2 39 | location : ctypes.c_float*2 40 | 41 | class BNodeStack(StructBase): 42 | vec : ctypes.c_float*4 43 | min : ctypes.c_float 44 | max : ctypes.c_float 45 | data : ctypes.c_void_p 46 | hasinput : ctypes.c_short 47 | hasoutput : ctypes.c_short 48 | datatype : ctypes.c_short 49 | sockettype : ctypes.c_short 50 | is_copy : ctypes.c_short 51 | external : ctypes.c_short 52 | _pad : ctypes.c_char*4 53 | 54 | class BNodeSocket(StructBase): 55 | next : ctypes.c_void_p # lambda: ctypes.POINTER(BNodeSocket) 56 | prev : ctypes.c_void_p # lambda: ctypes.POINTER(BNodeSocket) 57 | prop : ctypes.c_void_p 58 | identifier : ctypes.c_char*64 59 | name : ctypes.c_char*64 60 | storage : ctypes.c_void_p 61 | in_out : ctypes.c_short 62 | typeinfo : ctypes.c_void_p 63 | idname : ctypes.c_char*64 64 | default_value : ctypes.c_void_p 65 | _pad : ctypes.c_char*4 66 | label : ctypes.c_char*64 67 | description : ctypes.c_char*64 68 | if (is_bl4_plus) and bpy.app.version < (5, 1, 0): 69 | short_label : ctypes.c_char*64 # 2025-10-28 5.1移除 https://projects.blender.org/blender/blender/pulls/148940/files 70 | default_attribute_name: ctypes.POINTER(ctypes.c_char) 71 | to_index : ctypes.c_int 72 | link : ctypes.c_void_p 73 | ns: BNodeStack 74 | runtime : ctypes.POINTER(BNodeSocketRuntimeHandle) 75 | 76 | class BNodeType(StructBase): # \source\blender\blenkernel\BKE_node.h 77 | # 从内存布局以及debug看,idname 等几个5.0改成了 std::string ,显示40字节,nclass 偏移量是168字节 78 | # 😡但是为什么std::string当做32字节才对啊,为什么从144开始读 nclass 才对 79 | idname : ctypes.c_char*32 if is_bl5_plus else ctypes.c_char*64 80 | type : ctypes.c_int 81 | if is_bl5_plus: 82 | _pad1 : ctypes.c_char*4 83 | ui_name : ctypes.c_char*32 if is_bl5_plus else ctypes.c_char*64 84 | ui_description : ctypes.c_char*32 if is_bl5_plus else ctypes.c_char*256 85 | ui_icon : ctypes.c_int 86 | _pad2 : ctypes.c_char*4 87 | if bpy.app.version >= (4, 0, 0): 88 | enum_name_legacy : ctypes.c_void_p 89 | width : ctypes.c_float 90 | minwidth : ctypes.c_float 91 | maxwidth : ctypes.c_float 92 | height : ctypes.c_float 93 | minheight : ctypes.c_float 94 | maxheight : ctypes.c_float 95 | nclass : ctypes.c_int16 # 参考链接: https://github.com/ugorek000/ManagersNodeTree 96 | 97 | class BNode(StructBase): # 用于VRT. 98 | next : ctypes.c_void_p # lambda: ctypes.POINTER(BNode) 99 | prev : ctypes.c_void_p # lambda: ctypes.POINTER(BNode) 100 | inputs : ctypes.c_void_p*2 101 | outputs : ctypes.c_void_p*2 102 | name : ctypes.c_char*64 103 | identifier : ctypes.c_int 104 | flag : ctypes.c_int 105 | idname : ctypes.c_char*64 106 | typeinfo : ctypes.POINTER(BNodeType) 107 | type : ctypes.c_int16 108 | ui_order : ctypes.c_int16 109 | custom1 : ctypes.c_int16 110 | custom2 : ctypes.c_int16 111 | custom3 : ctypes.c_float 112 | custom4 : ctypes.c_float 113 | id : ctypes.c_void_p 114 | storage : ctypes.c_void_p 115 | prop : ctypes.c_void_p 116 | parent : ctypes.c_void_p 117 | locx : ctypes.c_float 118 | locy : ctypes.c_float 119 | width : ctypes.c_float 120 | height : ctypes.c_float 121 | offsetx : ctypes.c_float 122 | offsety : ctypes.c_float 123 | label : ctypes.c_char*64 124 | color : ctypes.c_float*3 125 | 126 | # 感谢昵称为 "Oxicid" 的用户贡献了这段关于 ctypes 的代码. "原来还可以这样操作吗?! 🤔". 127 | # 唉, Blender的这些开发者啊 🤦; 我不得不自己动手添加获取节点插槽(socket)位置的功能. 128 | # 'Blender 4.0 alpha' 的一团乱麻真是把我逼到墙角了.结果这事儿用Python就搞定了, 难道官方提供一个API就那么难吗? 🤷 129 | # P.S.为陨落的英雄们默哀一分钟 🙏, https://projects.blender.org/blender/blender/pulls/117809. 130 | 131 | # 好了, 最难的部分已经过去了. 距离技术上支持折叠节点仅一步之遥了. 🚀 132 | # 那些渴望这个功能的人会面无表情地快速来到这里, 拿走他们需要的东西, 然后自己去修改. 😎 133 | # 致第一个实现这个功能的人: "干得漂亮, 兄弟! 👍 现在你可以连接到折叠节点的插槽了. 希望你幸福得合不拢腿." 😂 134 | 135 | class RectBase(StructBase): 136 | def GetRaw(self): 137 | return self.xmin, self.ymin, self.xmax, self.ymax 138 | 139 | def TranslateRaw(self, xy): 140 | self.xmin += xy[0] 141 | self.xmax += xy[0] 142 | self.ymin += xy[1] 143 | self.ymax += xy[1] 144 | 145 | def TranslateScaleFac(self, xy, fac=0.5): 146 | if xy[0] > 0: 147 | self.xmin += xy[0]*fac 148 | self.xmax += xy[0] 149 | elif xy[0] < 0: 150 | self.xmin += xy[0] 151 | self.xmax += xy[0]*fac 152 | 153 | if xy[1] > 0: 154 | self.ymin += xy[1]*fac 155 | self.ymax += xy[1] 156 | elif xy[1] < 0: 157 | self.ymin += xy[1] 158 | self.ymax += xy[1]*fac 159 | 160 | def Zooming(self, center=None, fac=1.0): 161 | if center: 162 | centerX = center[0] 163 | centerY = center[1] 164 | else: 165 | centerX = (self.xmax + self.xmin) / 2 166 | centerY = (self.ymax + self.ymin) / 2 167 | self.xmax = (self.xmax - centerX)*fac + centerX 168 | self.xmin = (self.xmin - centerX)*fac + centerX 169 | self.ymax = (self.ymax - centerY)*fac + centerY 170 | self.ymin = (self.ymin - centerY)*fac + centerY 171 | 172 | class Rctf(RectBase): 173 | xmin : ctypes.c_float 174 | xmax : ctypes.c_float 175 | ymin : ctypes.c_float 176 | ymax : ctypes.c_float 177 | 178 | class Rcti(RectBase): 179 | xmin : ctypes.c_int 180 | xmax : ctypes.c_int 181 | ymin : ctypes.c_int 182 | ymax : ctypes.c_int 183 | 184 | class View2D(StructBase): # \source\blender\makesdna\DNA_view2d_types.h 185 | tot : Rctf 186 | cur : Rctf 187 | vert : Rcti 188 | hor : Rcti 189 | mask : Rcti 190 | min : ctypes.c_float*2 191 | max : ctypes.c_float*2 192 | minzoom : ctypes.c_float 193 | maxzoom : ctypes.c_float 194 | scroll : ctypes.c_short 195 | scroll_ui : ctypes.c_short 196 | keeptot : ctypes.c_short 197 | keepzoom : ctypes.c_short 198 | 199 | def GetZoom(self): 200 | return (self.mask.xmax - self.mask.xmin) / (self.cur.xmax - self.cur.xmin) # 多亏了 keepzoom==3, 我们可以只从一个轴读取数据. ✅ 201 | 202 | StructBase._init_structs() -------------------------------------------------------------------------------- /VoronoiLinker/v_QuickConstant.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from pprint import pprint 3 | from bpy.types import (NodeSocket, UILayout, NodeTree) 4 | 5 | from .rot_or_mat_convert import Convert_Data, PIE_MT_Convert_To_Rotation, PIE_MT_Combine_Matrix 6 | from .globals import Cursor_X_Offset 7 | from .utils_drawing import TemplateDrawSksToolHh 8 | from .v_tool import * 9 | from .globals import * 10 | from .utils_ui import * 11 | from .utils_node import * 12 | from .utils_color import * 13 | from .utils_solder import * 14 | from .utils_drawing import * 15 | from .utils_translate import * 16 | from .common_forward_func import * 17 | from .common_forward_class import * 18 | from .v_tool import VoronoiToolTripleSk 19 | 20 | BT = bpy.types 21 | 22 | def get_const_node(tree: NodeTree, sk_type: str): 23 | return AllQuickConstant.get(tree.bl_idname, None).get(sk_type, None) 24 | 25 | class VoronoiQuickConstant(VoronoiToolTripleSk): 26 | bl_idname = 'node.voronoi_quick_constant' 27 | bl_label = "Voronoi Quick Constant" 28 | usefulnessForCustomTree = False 29 | canDrawInAddonDiscl = False 30 | isPlaceImmediately: bpy.props.BoolProperty(name="Place immediately", default=False) 31 | def CallbackDrawTool(self, drata): 32 | TemplateDrawSksToolHh(drata, self.fotagoSk0, self.fotagoSk1, self.fotagoSk2, tool_name="Quick Constant - 暂时只对输入有效") 33 | def NextAssignmentTool(self, isFirstActivation, prefs, tree): 34 | if isFirstActivation: 35 | self.fotagoSk0 = None 36 | if not self.canPickThird: 37 | self.fotagoSk1 = None 38 | for ftgNd in self.ToolGetNearestNodes(cur_x_off= -Cursor_X_Offset): 39 | nd = ftgNd.tar 40 | list_ftgSksOut = self.ToolGetNearestSockets(nd, cur_x_off= -Cursor_X_Offset)[0] 41 | if not list_ftgSksOut: 42 | continue 43 | if isFirstActivation: 44 | for ftg in list_ftgSksOut: 45 | # ! AllQuickConstant 新的接口类型要在这里更新 46 | if get_const_node(tree, ftg.tar.type): 47 | self.fotagoSk0 = ftg 48 | break 49 | CheckUncollapseNodeAndReNext(nd, self, cond=True, flag=True) 50 | break 51 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk1, flag=False) 52 | sk_out0 = opt_ftg_socket(self.fotagoSk0) 53 | if sk_out0: 54 | only_single_link = {'MENU'} 55 | if sk_out0.type in only_single_link: # 小王 输出接口类型 不允许同时连到多个输入接口 56 | break 57 | if not self.canPickThird: 58 | for ftg in list_ftgSksOut: 59 | if ftg.tar.type==sk_out0.type: 60 | self.fotagoSk1 = ftg 61 | break 62 | if (self.fotagoSk1)and(self.fotagoSk1.tar==sk_out0): 63 | self.fotagoSk1 = None 64 | break 65 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk1, flag=False) 66 | if self.fotagoSk1: 67 | break 68 | else: 69 | sk_out1 = opt_ftg_socket(self.fotagoSk1) 70 | for ftg in list_ftgSksOut: 71 | if ftg.tar.type==sk_out0.type: 72 | self.fotagoSk2 = ftg 73 | break 74 | if (self.fotagoSk2)and( (self.fotagoSk2.tar==sk_out0)or(sk_out1)and(self.fotagoSk2.tar==sk_out1) ): 75 | self.fotagoSk2 = None 76 | break 77 | CheckUncollapseNodeAndReNext(nd, self, cond=self.fotagoSk2, flag=False) 78 | if self.fotagoSk2: 79 | break 80 | def MatterPurposePoll(self): 81 | return not not self.fotagoSk0 82 | def MatterPurposeTool(self, event, prefs, tree): 83 | skIn0 = self.fotagoSk0.tar 84 | if skIn0.type in ["ROTATION", "MATRIX"]: 85 | Convert_Data.sk0 = skIn0 86 | if self.fotagoSk1: 87 | Convert_Data.sk1 = self.fotagoSk1.tar 88 | if self.fotagoSk2: 89 | Convert_Data.sk2 = self.fotagoSk2.tar 90 | if skIn0.type == "ROTATION": 91 | if hasattr(skIn0, "default_value"): 92 | bpy.ops.wm.call_menu_pie(name=PIE_MT_Convert_To_Rotation.bl_idname) 93 | # return {'FINISHED'} # 想松开按键确认 94 | # active_node.width = 200 # md这里不行,运行ops后立马运行下面的了?放在Rotation_Convert里的invoke就行了? (那里放好这里忘删了找了半天错误) 95 | if skIn0.type == "MATRIX": 96 | bpy.ops.wm.call_menu_pie(name=PIE_MT_Combine_Matrix.bl_idname) 97 | else: 98 | node_type = get_const_node(tree, skIn0.type) 99 | if node_type == "NodeClosureOutput": 100 | bpy.ops.node.add_closure_zone('INVOKE_DEFAULT', use_transform=True) 101 | else: 102 | bpy.ops.node.add_node('INVOKE_DEFAULT', type=node_type, use_transform=not self.isPlaceImmediately) 103 | active_node = tree.nodes.active 104 | sk_out = active_node.outputs[0] 105 | 106 | tree.links.new(sk_out, skIn0) 107 | if self.fotagoSk1: 108 | tree.links.new(sk_out, self.fotagoSk1.tar) 109 | if self.fotagoSk2: 110 | tree.links.new(sk_out, self.fotagoSk2.tar) 111 | 112 | if isinstance(active_node, (BT.NodeClosureOutput, BT.NodeCombineBundle)): 113 | bpy.ops.node.sockets_sync() 114 | return 115 | 116 | # 小王 transfer value 117 | if not hasattr(skIn0, "default_value"): 118 | return 119 | value = skIn0.default_value 120 | 121 | if isinstance(active_node, BT.ShaderNodeValue): 122 | active_node.outputs[0].default_value = value 123 | 124 | elif isinstance(active_node, (BT.FunctionNodeInputColor, BT.ShaderNodeRGB)): 125 | active_node.value = value 126 | 127 | elif isinstance(active_node, BT.FunctionNodeInputInt): 128 | active_node.integer = value 129 | 130 | elif isinstance(active_node, BT.FunctionNodeInputBool): 131 | active_node.boolean = value 132 | 133 | elif isinstance(active_node, BT.FunctionNodeInputString): 134 | active_node.string = value 135 | 136 | elif isinstance(active_node, BT.GeometryNodeInputImage): 137 | active_node.image = value 138 | 139 | elif isinstance(active_node, BT.GeometryNodeInputMaterial): 140 | active_node.material = value 141 | 142 | elif isinstance(active_node, BT.GeometryNodeInputObject): 143 | active_node.object = value 144 | 145 | elif isinstance(active_node, BT.GeometryNodeInputCollection): 146 | active_node.collection = value 147 | 148 | elif isinstance(active_node, BT.FunctionNodeEulerToRotation): 149 | active_node.inputs[0].default_value = value 150 | 151 | elif isinstance(active_node, BT.ShaderNodeCombineXYZ): 152 | for i in range(3): 153 | active_node.inputs[i].default_value = value[i] 154 | 155 | elif isinstance(active_node, BT.GeometryNodeIndexSwitch): 156 | active_node.data_type = "MENU" 157 | active_node.show_options = False 158 | id_name = skIn0.node.bl_idname 159 | if id_name == "GeometryNodeMenuSwitch": 160 | active_node.inputs[-1].hide = True 161 | items = skIn0.node.enum_items 162 | items_l = len(items) 163 | if items_l >= 2: 164 | for i in range(items_l - 2): 165 | bpy.ops.node.index_switch_item_add() 166 | for i in range(items_l): 167 | active_node.inputs[i + 1].default_value = items[i].name 168 | if id_name == "GeometryNodeIndexSwitch": 169 | if skIn0.default_value != '': 170 | active_node.inputs[1].default_value = skIn0.default_value 171 | -------------------------------------------------------------------------------- /node_align/__init__.py: -------------------------------------------------------------------------------- 1 | if "bpy" in locals(): # 偏好设置重启插件,只会重新执行__init__.py,所以重新加载需要更新的模块 2 | import importlib 3 | importlib.reload(op_align) 4 | importlib.reload(translator) 5 | from .op_align import * 6 | from .translator import i18n as tr 7 | 8 | import os 9 | import bpy 10 | from bpy.props import BoolProperty, IntProperty 11 | from bpy.types import AddonPreferences, Menu 12 | # import bpy.utils.previews 13 | from bpy.utils import previews as bl_preview 14 | #+ import bpy 只会导入bpy下的一级module,直接用bpy.utils.previews 会报错,没报错可能是别的插件导入过了 15 | # 并不是只会导入下一级,import bpy 根据子包里的导入,决定使用时能导入多少级 bpy.a.b.c 16 | 17 | # TODO 对齐Frame 18 | # TODO 把我的 Alt+1 加上 Shift+x Shift+y 19 | # TODO 根据左上角和右下角画格子,节点落在最近的的格子里 20 | # TODO 自定义栅格分布判断列的间距(或者根据节点密度/数量自动判断) 21 | 22 | addon_keymaps = {} 23 | _icons = None 24 | # 按顺序可以换成自定义图片图标 25 | images = [ 26 | '左对齐-蓝双色', 27 | '右对齐-蓝双色', 28 | '下对齐-蓝双色', 29 | '上对齐-蓝双色', 30 | '上下居中对齐-蓝双色', 31 | '垂直等距分布-蓝双色', 32 | '左右居中对齐-蓝双色', 33 | '水平等距分布-蓝双色', 34 | '网格-绿方块', 35 | '网格-蓝方块', 36 | '网格-蓝线框', 37 | '直线-橙色虚线', 38 | '水平等距分布-蓝橙', 39 | '垂直等距分布-蓝橙', 40 | ] 41 | images = [image + ".png" for image in images] 42 | 43 | def find_user_keyconfig(key): 44 | km, kmi = addon_keymaps[key] 45 | for item in bpy.context.window_manager.keyconfigs.user.keymaps[km.name].keymap_items: 46 | found_item = False 47 | if kmi.idname == item.idname: 48 | found_item = True 49 | for name in dir(kmi.properties): 50 | if not name in ["bl_rna", "rna_type"] and not name[0] == "_": 51 | if name in kmi.properties and name in item.properties and not kmi.properties[name] == item.properties[name]: 52 | found_item = False 53 | if found_item: 54 | return item 55 | print(f"Couldn't find keymap item for {key}, using addon keymap instead. This won't be saved across sessions!") 56 | return kmi 57 | 58 | class Node_Align_AddonPrefs(AddonPreferences): 59 | bl_idname = __package__ 60 | is_custom_space: BoolProperty(name="is_custom_space", default=False, description=tr("是否为等距及栅格分布启用自定义间距")) 61 | space_x : IntProperty(name="space_x", default=30, description=tr("等距及栅格分布时的节点x方向间距")) 62 | space_y : IntProperty(name="space_y", default=25, description=tr("等距及栅格分布时的节点y方向间距")) 63 | col_width: IntProperty(name="col_width", default=140, description=tr("栅格分布时判断是否在一列的宽度")) 64 | 65 | def draw(self, context): 66 | layout = self.layout 67 | 68 | split = layout.split(factor=0.5) 69 | split.label(text=tr("普通对齐饼菜单")) 70 | split.prop(find_user_keyconfig('ALIGN_MT_align_pie'), 'type', text='', full_event=True) 71 | 72 | split = layout.split(factor=0.5) 73 | split.label(text=tr("高级对齐饼菜单")) 74 | split.prop(find_user_keyconfig('ALIGN_MT_align_pie_pro'), 'type', text='', full_event=True) 75 | 76 | split = layout.split(factor=0.75) 77 | split.label(text=tr("栅格分布时判断是否在一列的宽度")) 78 | split.prop(self, 'col_width', text='') 79 | 80 | split = layout.split(factor=0.75) 81 | split.label(text=tr("等距及栅格分布启用自定义间距")) 82 | split.prop(self, 'is_custom_space', text='') 83 | 84 | split = layout.split(factor=0.75) 85 | split.label(text=tr("等距及栅格分布时的节点x方向间距")) 86 | split.prop(self, 'space_x', text='') 87 | 88 | split = layout.split(factor=0.75) 89 | split.label(text=tr("等距及栅格分布时的节点y方向间距")) 90 | split.prop(self, 'space_y', text='') 91 | 92 | def draw_align_menu(layout): 93 | layout.operator("node.align_left", text=tr("左对齐"), icon_value=_icons[images[0]].icon_id) 94 | layout.operator("node.align_right", text=tr("右对齐"), icon_value=_icons[images[1]].icon_id) 95 | layout.operator("node.align_bottom", text=tr("底对齐"), icon_value=_icons[images[2]].icon_id) 96 | layout.operator("node.align_top", text=tr("顶对齐"), icon_value=_icons[images[3]].icon_id) 97 | layout.operator("node.align_height_center", text=tr("对齐高度"), icon_value=_icons[images[4]].icon_id) 98 | layout.operator("node.distribute_vertical", text=tr("垂直等距分布"), icon_value=_icons[images[5]].icon_id) 99 | layout.operator("node.align_width_center", text=tr("对齐宽度"), icon_value=_icons[images[6]].icon_id) 100 | layout.operator("node.distribute_horizontal", text=tr("水平等距分布"), icon_value=_icons[images[7]].icon_id) 101 | 102 | def draw_align_menu_pro(layout): 103 | # pie里默认出现顺序 左 右 底 顶 左上 右上 左下 右下 104 | layout.prop(pref(), "is_custom_space", text=tr("自定义分布间距")) 105 | layout.operator("node.distribute_horizontal_vertical", text=tr("水平垂直等距"), icon_value=_icons[images[10]].icon_id) 106 | layout.operator("node.distribute_row_column", text=tr("改变行列数"), icon_value=_icons[images[10]].icon_id) 107 | layout.operator("node.align_link", text=tr("拉直连线"), icon_value=_icons[images[11]].icon_id) 108 | layout.operator("node.align_height_horizontal", text=tr("等距对齐高度"), icon_value=_icons[images[13]].icon_id) 109 | layout.operator("node.distribute_grid_relative", text=tr("相对网格分布"), icon_value=_icons[images[8]].icon_id) 110 | layout.operator("node.align_width_vertical", text=tr("等距对齐宽度"), icon_value=_icons[images[12]].icon_id) 111 | layout.operator("node.distribute_grid_absolute", text=tr("绝对网格分布"), icon_value=_icons[images[9]].icon_id) 112 | 113 | class ALIGN_MT_align_pie(Menu): 114 | bl_idname = "ALIGN_MT_align_pie" 115 | bl_label = tr("节点对齐") 116 | 117 | def draw(self, context): 118 | pie = self.layout.menu_pie() # 默认出现顺序 左 右 底 顶 左上 右上 左下 右下 119 | draw_align_menu(pie) 120 | 121 | class ALIGN_MT_align_pie_pro(Menu): 122 | bl_idname = "ALIGN_MT_align_pie_pro" 123 | bl_label = tr("节点分布") 124 | 125 | def draw(self, context): 126 | pie = self.layout.menu_pie() # 默认出现顺序 左 右 底 顶 左上 右上 左下 右下 127 | draw_align_menu_pro(pie) 128 | 129 | class NODE_MT_align(Menu): 130 | bl_idname = "NODE_MT_align" 131 | bl_label = "右键菜单-对齐" 132 | 133 | @classmethod 134 | def poll(cls, context): 135 | return bool(context.selected_nodes) 136 | 137 | def draw(self, context): 138 | layout = self.layout 139 | draw_align_menu(layout) 140 | layout.separator() 141 | draw_align_menu_pro(layout) 142 | 143 | def add_align_op_to_node_mt_context_menu(self, context): 144 | layout = self.layout 145 | layout.menu('NODE_MT_align', text=tr("节点对齐"), icon_value=_icons[images[9]].icon_id) 146 | 147 | classes = [ 148 | Node_Align_AddonPrefs, 149 | ALIGN_MT_align_pie, 150 | ALIGN_MT_align_pie_pro, 151 | NODE_MT_align, 152 | 153 | NODE_OT_align_bottom, 154 | NODE_OT_align_top, 155 | NODE_OT_align_right, 156 | NODE_OT_align_left, 157 | NODE_OT_align_heightcenter, 158 | NODE_OT_align_widthcenter, 159 | NODE_OT_distribute_horizontal, 160 | NODE_OT_distribute_vertical, 161 | NODE_OT_align_width_vertical, 162 | NODE_OT_align_height_horizontal, 163 | NODE_OT_distribute_horizontal_vertical, 164 | NODE_OT_distribute_row_column, 165 | NODE_OT_distribute_grid_relative, 166 | NODE_OT_distribute_grid_absolute, 167 | NODE_OT_align_link, 168 | ] 169 | 170 | def register(): 171 | global _icons 172 | _icons = bl_preview.new() 173 | for image in images: 174 | _icons.load(image, os.path.join(os.path.dirname(__file__), 'icons', image), "IMAGE") 175 | 176 | for cls in classes: 177 | bpy.utils.register_class(cls) 178 | 179 | kc = bpy.context.window_manager.keyconfigs.addon 180 | km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') 181 | 182 | kmi = km.keymap_items.new("wm.call_menu_pie", type="Q", value="PRESS", shift=True) 183 | kmi.properties.name = "ALIGN_MT_align_pie" 184 | addon_keymaps['ALIGN_MT_align_pie'] = (km, kmi) 185 | 186 | kmi = km.keymap_items.new("wm.call_menu_pie", type="Q", value="PRESS", ctrl=True) 187 | kmi.properties.name = "ALIGN_MT_align_pie_pro" 188 | addon_keymaps['ALIGN_MT_align_pie_pro'] = (km, kmi) 189 | 190 | bpy.types.NODE_MT_context_menu.append(add_align_op_to_node_mt_context_menu) 191 | 192 | def unregister(): 193 | global _icons 194 | bl_preview.remove(_icons) 195 | 196 | for km, kmi in addon_keymaps.values(): 197 | km.keymap_items.remove(kmi) 198 | addon_keymaps.clear() 199 | 200 | for cls in classes: 201 | bpy.utils.unregister_class(cls) 202 | bpy.types.NODE_MT_context_menu.remove(add_align_op_to_node_mt_context_menu) 203 | -------------------------------------------------------------------------------- /VoronoiLinker/v_RantoTool.py: -------------------------------------------------------------------------------- 1 | from .common_forward_func import DisplayMessage 2 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 3 | from .v_tool import * 4 | from .globals import * 5 | from .utils_ui import * 6 | from .utils_node import * 7 | from .utils_color import * 8 | from .utils_solder import * 9 | from .utils_drawing import * 10 | from .utils_translate import * 11 | from .common_forward_func import * 12 | from .common_forward_class import * 13 | from .v_tool import VoronoiToolNd 14 | from bpy.app.translations import pgettext_iface as TranslateIface 15 | 16 | 17 | # 现在 RANTO 已经集成到 VL 中了. 连我自己都感到意外. 18 | # 参见原版: https://github.com/ugorek000/RANTO 19 | 20 | class RantoData(): 21 | def __init__(self, isOnlySelected=0, widthNd=140, isUniWid=False, indentX=40, indentY=30, isIncludeMutedLinks=False, isIncludeNonValidLinks=False, isFixIslands=True): 22 | self.kapibara = "" 23 | self.dict_ndTopoWorking = {} 24 | 25 | def VrtDoRecursiveAutomaticNodeTopologyOrganization(rada, ndRoot): 26 | rada.kapibara = "kapibara" 27 | 28 | 29 | class VoronoiRantoTool(VoronoiToolNd): #完成了. 30 | bl_idname = 'node.voronoi_ranto' 31 | bl_label = "Voronoi RANTO" 32 | usefulnessForCustomTree = True 33 | usefulnessForUndefTree = True 34 | isOnlySelected: bpy.props.IntProperty(name="Only selected", default=0, min=0, max=2, description="0 – Any node.\n1 – Selected + reroutes.\n2 – Only selected") 35 | isUniWid: bpy.props.BoolProperty(name="Uniform width", default=False) 36 | widthNd: bpy.props.IntProperty(name="Node width", default=140, soft_min=100, soft_max=180, subtype='FACTOR') 37 | indentX: bpy.props.IntProperty(name="Indent x", default=40, soft_min=0, soft_max=80, subtype='FACTOR') 38 | indentY: bpy.props.IntProperty(name="Indent y", default=30, soft_min=0, soft_max=60, subtype='FACTOR') 39 | isUncollapseNodes: bpy.props.BoolProperty(name="Uncollapse nodes", default=False) 40 | isDeleteReroutes: bpy.props.BoolProperty(name="Delete reroutes", default=False) 41 | isSelectNodes: bpy.props.IntProperty(name="Select nodes", default=1, min=-1, max=1, description="-1 – All deselect.\n 0 – Do nothing.\n 1 – Selecting involveds node") 42 | isIncludeMutedLinks: bpy.props.BoolProperty(name="Include muted links", default=False) 43 | isIncludeNonValidLinks: bpy.props.BoolProperty(name="Include non valid links", default=True) 44 | isAccumulate: bpy.props.BoolProperty(name="Accumulate", default=False) 45 | def DoRANTO(self, ndTar, tree, isFixIslands=True): 46 | if ndTar==self.lastNdProc: 47 | return 48 | self.lastNdProc = ndTar 49 | ndTar.select = True 50 | if not self.isAccumulate: 51 | tree.nodes.active = ndTar 52 | #elif self.ndMaxAccRoot: 53 | # ndTar = self.ndMaxAccRoot 54 | def DoRanto(nd): 55 | rada = RantoData(self.isOnlySelected+self.isAccumulate, self.widthNd, self.isUniWid, self.indentX, self.indentY, self.isIncludeMutedLinks, self.isIncludeNonValidLinks, isFixIslands) 56 | VrtDoRecursiveAutomaticNodeTopologyOrganization(rada, nd) 57 | return rada 58 | if self.isUncollapseNodes: 59 | dict_remresNdHide = {} 60 | for nd in tree.nodes: 61 | dict_remresNdHide[nd] = nd.hide 62 | nd.hide = False 63 | bpy.ops.wm.redraw_timer(type='DRAW', iterations=0) 64 | rada = DoRanto(ndTar) 65 | if self.isDeleteReroutes: 66 | bpy.ops.node.select_all(action='DESELECT') 67 | isInvl = False 68 | for nd in rada.dict_ndTopoWorking: 69 | if nd.type=='REROUTE': 70 | nd.select = True 71 | isInvl = True 72 | if isInvl: 73 | bpy.ops.node.delete_reconnect() 74 | rada = DoRanto(ndTar) 75 | if (self.isSelectNodes==-1)and(not self.isAccumulate): 76 | bpy.ops.node.select_all(action='DESELECT') 77 | soldNAcc = not self.isAccumulate 78 | for nd in tree.nodes: 79 | tgl = nd in rada.dict_ndTopoWorking 80 | if (self.isSelectNodes==1)and(soldNAcc): 81 | nd.select = tgl 82 | if (not tgl)and(self.isUncollapseNodes): #恢复未涉及节点的隐藏状态. 83 | nd.hide = dict_remresNdHide[nd] 84 | if self.isAccumulate: 85 | tree.nodes.active = ndTar 86 | for nd in rada.dict_ndTopoWorking: 87 | nd.select = True 88 | #ndTar.location = ndTar.location #bpy.ops.wm.redraw_timer(type='DRAW', iterations=0) 89 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 90 | self.fotagoNd = None 91 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 92 | nd = ftgNd.tar 93 | if nd.type=='REROUTE': 94 | continue #为此,请参考原始的RANTO插件. 95 | self.fotagoNd = ftgNd 96 | #if not self.ndMaxAccRoot: 97 | # self.ndMaxAccRoot = nd 98 | if prefs.vrtIsLiveRanto: 99 | self.DoRANTO(nd, tree, prefs.vrtIsFixIslands) 100 | break 101 | def MatterPurposeTool(self, event, prefs, tree): 102 | ndTar = self.fotagoNd.tar 103 | #if self.isAccumulate: 104 | # self.ndMaxAccRoot = None 105 | # self.lastNdProc = None 106 | self.DoRANTO(ndTar, tree, prefs.vrtIsFixIslands) 107 | DisplayMessage("RANTO", TranslateIface("This tool is empty")+" ¯\_(ツ)_/¯") 108 | def InitTool(self, event, prefs, tree): 109 | self.lastNdProc = None 110 | #self.ndMaxAccRoot = None 111 | @staticmethod 112 | def LyDrawInAddonDiscl(col, prefs): 113 | LyAddLeftProp(col, prefs,'vrtIsLiveRanto') 114 | LyAddLeftProp(col, prefs,'vrtIsFixIslands') 115 | @classmethod 116 | def BringTranslations(cls): 117 | with VlTrMapForKey("This tool is empty") as dm: 118 | dm["ru_RU"] = "Этот инструмент пуст" 119 | dm["zh_CN"] = "该工具是空的" 120 | ## 121 | with VlTrMapForKey(GetAnnotFromCls(cls,'isOnlySelected').name) as dm: 122 | dm["ru_RU"] = "Только выделенные" 123 | dm["zh_CN"] = "仅选定的" 124 | with VlTrMapForKey(GetAnnotFromCls(cls,'isOnlySelected').description) as dm: 125 | dm["ru_RU"] = "0 – Любой нод.\n1 – Выделенные + рероуты.\n2 – Только выделенные" 126 | dm["zh_CN"] = "0 – 任意节点。\n1 – 选定+转向节点。\n2 – 仅选定的节点" 127 | with VlTrMapForKey(GetAnnotFromCls(cls,'isUniWid').name) as dm: 128 | dm["ru_RU"] = "Постоянная ширина" 129 | dm["zh_CN"] = "统一宽度" 130 | with VlTrMapForKey(GetAnnotFromCls(cls,'widthNd').name) as dm: 131 | dm["ru_RU"] = "Ширина нод" 132 | dm["zh_CN"] = "节点宽度" 133 | with VlTrMapForKey(GetAnnotFromCls(cls,'indentX').name) as dm: 134 | dm["ru_RU"] = "Отступ по X" 135 | dm["zh_CN"] = "X缩进" 136 | with VlTrMapForKey(GetAnnotFromCls(cls,'indentY').name) as dm: 137 | dm["ru_RU"] = "Отступ по Y" 138 | dm["zh_CN"] = "Y缩进" 139 | with VlTrMapForKey(GetAnnotFromCls(cls,'isUncollapseNodes').name) as dm: 140 | dm["ru_RU"] = "Разворачивать ноды" 141 | dm["zh_CN"] = "展开节点" 142 | with VlTrMapForKey(GetAnnotFromCls(cls,'isDeleteReroutes').name) as dm: 143 | dm["ru_RU"] = "Удалять рероуты" 144 | dm["zh_CN"] = "删除转向节点" 145 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectNodes').name) as dm: 146 | dm["ru_RU"] = "Выделять ноды" 147 | dm["zh_CN"] = "选择节点" 148 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectNodes').description) as dm: 149 | dm["ru_RU"] = "-1 – Де-выделять всё.\n 0 – Ничего не делать.\n 1 – Выделять задействованные ноды" 150 | dm["zh_CN"] = "-1 – 取消全选。\n 0 – 不做任何事。\n 1 – 选择涉及的节点" 151 | with VlTrMapForKey(GetAnnotFromCls(cls,'isIncludeMutedLinks').name) as dm: 152 | dm["ru_RU"] = "Разрешить выключенные линки" 153 | dm["zh_CN"] = "包含禁用的连线" 154 | with VlTrMapForKey(GetAnnotFromCls(cls,'isIncludeNonValidLinks').name) as dm: 155 | dm["ru_RU"] = "Разрешить невалидные линки" 156 | dm["zh_CN"] = "包含无效的连线" 157 | with VlTrMapForKey(GetAnnotFromCls(cls,'isAccumulate').name) as dm: 158 | dm["ru_RU"] = "Накапливать" 159 | dm["zh_CN"] = "累积" 160 | ## 161 | with VlTrMapForKey(GetPrefsRnaProp('vrtIsLiveRanto').name) as dm: 162 | dm["ru_RU"] = "Ranto в реальном времени" 163 | dm["zh_CN"] = "实时对齐" 164 | with VlTrMapForKey(GetPrefsRnaProp('vrtIsFixIslands').name) as dm: 165 | dm["ru_RU"] = "Чинить острова" 166 | dm["zh_CN"] = "修复孤岛" -------------------------------------------------------------------------------- /VoronoiLinker/v_Ranto_tool.py: -------------------------------------------------------------------------------- 1 | from .common_forward_func import DisplayMessage 2 | from .utils_translate import GetAnnotFromCls, VlTrMapForKey 3 | from .v_tool import * 4 | from .globals import * 5 | from .utils_ui import * 6 | from .utils_node import * 7 | from .utils_color import * 8 | from .utils_solder import * 9 | from .utils_drawing import * 10 | from .utils_translate import * 11 | from .common_forward_func import * 12 | from .common_forward_class import * 13 | from .v_tool import VoronoiToolNd 14 | from bpy.app.translations import pgettext_iface as TranslateIface 15 | 16 | 17 | # 现在 RANTO 已经集成到 VL 中了. 连我自己都感到意外. 18 | # 参见原版: https://github.com/ugorek000/RANTO 19 | 20 | class RantoData(): 21 | def __init__(self, isOnlySelected=0, widthNd=140, isUniWid=False, indentX=40, indentY=30, isIncludeMutedLinks=False, isIncludeNonValidLinks=False, isFixIslands=True): 22 | self.kapibara = "" 23 | self.dict_ndTopoWorking = {} 24 | 25 | def VrtDoRecursiveAutomaticNodeTopologyOrganization(rada, ndRoot): 26 | rada.kapibara = "kapibara" 27 | 28 | 29 | class VoronoiRantoTool(VoronoiToolNd): #完成了. 30 | bl_idname = 'node.voronoi_ranto' 31 | bl_label = "Voronoi RANTO" 32 | usefulnessForCustomTree = True 33 | usefulnessForUndefTree = True 34 | isOnlySelected: bpy.props.IntProperty(name="Only selected", default=0, min=0, max=2, description="0 – Any node.\n1 – Selected + reroutes.\n2 – Only selected") 35 | isUniWid: bpy.props.BoolProperty(name="Uniform width", default=False) 36 | widthNd: bpy.props.IntProperty(name="Node width", default=140, soft_min=100, soft_max=180, subtype='FACTOR') 37 | indentX: bpy.props.IntProperty(name="Indent x", default=40, soft_min=0, soft_max=80, subtype='FACTOR') 38 | indentY: bpy.props.IntProperty(name="Indent y", default=30, soft_min=0, soft_max=60, subtype='FACTOR') 39 | isUncollapseNodes: bpy.props.BoolProperty(name="Uncollapse nodes", default=False) 40 | isDeleteReroutes: bpy.props.BoolProperty(name="Delete reroutes", default=False) 41 | isSelectNodes: bpy.props.IntProperty(name="Select nodes", default=1, min=-1, max=1, description="-1 – All deselect.\n 0 – Do nothing.\n 1 – Selecting involveds node") 42 | isIncludeMutedLinks: bpy.props.BoolProperty(name="Include muted links", default=False) 43 | isIncludeNonValidLinks: bpy.props.BoolProperty(name="Include non valid links", default=True) 44 | isAccumulate: bpy.props.BoolProperty(name="Accumulate", default=False) 45 | def DoRANTO(self, ndTar, tree, isFixIslands=True): 46 | if ndTar==self.lastNdProc: 47 | return 48 | self.lastNdProc = ndTar 49 | ndTar.select = True 50 | if not self.isAccumulate: 51 | tree.nodes.active = ndTar 52 | #elif self.ndMaxAccRoot: 53 | # ndTar = self.ndMaxAccRoot 54 | def DoRanto(nd): 55 | rada = RantoData(self.isOnlySelected+self.isAccumulate, self.widthNd, self.isUniWid, self.indentX, self.indentY, self.isIncludeMutedLinks, self.isIncludeNonValidLinks, isFixIslands) 56 | VrtDoRecursiveAutomaticNodeTopologyOrganization(rada, nd) 57 | return rada 58 | if self.isUncollapseNodes: 59 | dict_remresNdHide = {} 60 | for nd in tree.nodes: 61 | dict_remresNdHide[nd] = nd.hide 62 | nd.hide = False 63 | bpy.ops.wm.redraw_timer(type='DRAW', iterations=0) 64 | rada = DoRanto(ndTar) 65 | if self.isDeleteReroutes: 66 | bpy.ops.node.select_all(action='DESELECT') 67 | isInvl = False 68 | for nd in rada.dict_ndTopoWorking: 69 | if nd.type=='REROUTE': 70 | nd.select = True 71 | isInvl = True 72 | if isInvl: 73 | bpy.ops.node.delete_reconnect() 74 | rada = DoRanto(ndTar) 75 | if (self.isSelectNodes==-1)and(not self.isAccumulate): 76 | bpy.ops.node.select_all(action='DESELECT') 77 | soldNAcc = not self.isAccumulate 78 | for nd in tree.nodes: 79 | tgl = nd in rada.dict_ndTopoWorking 80 | if (self.isSelectNodes==1)and(soldNAcc): 81 | nd.select = tgl 82 | if (not tgl)and(self.isUncollapseNodes): #恢复未涉及节点的隐藏状态. 83 | nd.hide = dict_remresNdHide[nd] 84 | if self.isAccumulate: 85 | tree.nodes.active = ndTar 86 | for nd in rada.dict_ndTopoWorking: 87 | nd.select = True 88 | #ndTar.location = ndTar.location #bpy.ops.wm.redraw_timer(type='DRAW', iterations=0) 89 | def NextAssignmentTool(self, _isFirstActivation, prefs, tree): 90 | self.fotagoNd = None 91 | for ftgNd in self.ToolGetNearestNodes(cur_x_off=0): 92 | nd = ftgNd.tar 93 | if nd.type=='REROUTE': 94 | continue #为此,请参考原始的RANTO插件. 95 | self.fotagoNd = ftgNd 96 | #if not self.ndMaxAccRoot: 97 | # self.ndMaxAccRoot = nd 98 | if prefs.vrtIsLiveRanto: 99 | self.DoRANTO(nd, tree, prefs.vrtIsFixIslands) 100 | break 101 | def MatterPurposeTool(self, event, prefs, tree): 102 | ndTar = self.fotagoNd.tar 103 | #if self.isAccumulate: 104 | # self.ndMaxAccRoot = None 105 | # self.lastNdProc = None 106 | self.DoRANTO(ndTar, tree, prefs.vrtIsFixIslands) 107 | DisplayMessage("RANTO", TranslateIface("This tool is empty")+" ¯\_(ツ)_/¯") 108 | def InitTool(self, event, prefs, tree): 109 | self.lastNdProc = None 110 | #self.ndMaxAccRoot = None 111 | @staticmethod 112 | def LyDrawInAddonDiscl(col, prefs): 113 | LyAddLeftProp(col, prefs,'vrtIsLiveRanto') 114 | LyAddLeftProp(col, prefs,'vrtIsFixIslands') 115 | @classmethod 116 | def BringTranslations(cls): 117 | with VlTrMapForKey("This tool is empty") as dm: 118 | dm["ru_RU"] = "Этот инструмент пуст" 119 | dm["zh_CN"] = "该工具是空的" 120 | ## 121 | with VlTrMapForKey(GetAnnotFromCls(cls,'isOnlySelected').name) as dm: 122 | dm["ru_RU"] = "Только выделенные" 123 | dm["zh_CN"] = "仅选定的" 124 | with VlTrMapForKey(GetAnnotFromCls(cls,'isOnlySelected').description) as dm: 125 | dm["ru_RU"] = "0 – Любой нод.\n1 – Выделенные + рероуты.\n2 – Только выделенные" 126 | dm["zh_CN"] = "0 – 任意节点。\n1 – 选定+转向节点。\n2 – 仅选定的节点" 127 | with VlTrMapForKey(GetAnnotFromCls(cls,'isUniWid').name) as dm: 128 | dm["ru_RU"] = "Постоянная ширина" 129 | dm["zh_CN"] = "统一宽度" 130 | with VlTrMapForKey(GetAnnotFromCls(cls,'widthNd').name) as dm: 131 | dm["ru_RU"] = "Ширина нод" 132 | dm["zh_CN"] = "节点宽度" 133 | with VlTrMapForKey(GetAnnotFromCls(cls,'indentX').name) as dm: 134 | dm["ru_RU"] = "Отступ по X" 135 | dm["zh_CN"] = "X缩进" 136 | with VlTrMapForKey(GetAnnotFromCls(cls,'indentY').name) as dm: 137 | dm["ru_RU"] = "Отступ по Y" 138 | dm["zh_CN"] = "Y缩进" 139 | with VlTrMapForKey(GetAnnotFromCls(cls,'isUncollapseNodes').name) as dm: 140 | dm["ru_RU"] = "Разворачивать ноды" 141 | dm["zh_CN"] = "展开节点" 142 | with VlTrMapForKey(GetAnnotFromCls(cls,'isDeleteReroutes').name) as dm: 143 | dm["ru_RU"] = "Удалять рероуты" 144 | dm["zh_CN"] = "删除转向节点" 145 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectNodes').name) as dm: 146 | dm["ru_RU"] = "Выделять ноды" 147 | dm["zh_CN"] = "选择节点" 148 | with VlTrMapForKey(GetAnnotFromCls(cls,'isSelectNodes').description) as dm: 149 | dm["ru_RU"] = "-1 – Де-выделять всё.\n 0 – Ничего не делать.\n 1 – Выделять задействованные ноды" 150 | dm["zh_CN"] = "-1 – 取消全选。\n 0 – 不做任何事。\n 1 – 选择涉及的节点" 151 | with VlTrMapForKey(GetAnnotFromCls(cls,'isIncludeMutedLinks').name) as dm: 152 | dm["ru_RU"] = "Разрешить выключенные линки" 153 | dm["zh_CN"] = "包含禁用的连线" 154 | with VlTrMapForKey(GetAnnotFromCls(cls,'isIncludeNonValidLinks').name) as dm: 155 | dm["ru_RU"] = "Разрешить невалидные линки" 156 | dm["zh_CN"] = "包含无效的连线" 157 | with VlTrMapForKey(GetAnnotFromCls(cls,'isAccumulate').name) as dm: 158 | dm["ru_RU"] = "Накапливать" 159 | dm["zh_CN"] = "累积" 160 | ## 161 | with VlTrMapForKey(GetPrefsRnaProp('vrtIsLiveRanto').name) as dm: 162 | dm["ru_RU"] = "Ranto в реальном времени" 163 | dm["zh_CN"] = "实时对齐" 164 | with VlTrMapForKey(GetPrefsRnaProp('vrtIsFixIslands').name) as dm: 165 | dm["ru_RU"] = "Чинить острова" 166 | dm["zh_CN"] = "修复孤岛" --------------------------------------------------------------------------------