├── .gitignore ├── README.md ├── __init__.py ├── blender_manifest.toml ├── build.bat ├── build.py ├── images ├── Node Pie compositor.jpg ├── Node Pie geometry.jpg ├── Node Pie preferences.jpg ├── Node Pie serpens.jpg └── Node Pie shader.jpg └── node_pie ├── __init__.py ├── generate_node_layout.py ├── node_def_files ├── builtin │ ├── CompositorNodeTree_1_0.jsonc │ ├── CompositorNodeTree_4_1.jsonc │ ├── GeometryNodeTree_1_0.jsonc │ ├── GeometryNodeTree_3_5.jsonc │ ├── GeometryNodeTree_3_6.jsonc │ ├── GeometryNodeTree_4_0.jsonc │ ├── GeometryNodeTree_4_1.jsonc │ ├── GeometryNodeTree_4_2.jsonc │ ├── GeometryNodeTree_4_3.jsonc │ ├── GeometryNodeTree_4_4.jsonc │ ├── ShaderNodeTree_1_0.jsonc │ ├── ShaderNodeTree_4_0.jsonc │ ├── ShaderNodeTree_4_1.jsonc │ ├── ShaderNodeTree_4_2.jsonc │ └── ShaderNodeTree_4_3.jsonc ├── node_def_base.jsonc ├── node_def_example.jsonc ├── sockets │ ├── CompositorNodeTree_sockets_4_1.jsonc │ ├── CompositorNodeTree_sockets_4_3.jsonc │ ├── GeometryNodeTree_sockets_4_1.jsonc │ ├── GeometryNodeTree_sockets_4_2.jsonc │ ├── GeometryNodeTree_sockets_4_3.jsonc │ ├── ShaderNodeTree_sockets_4_1.jsonc │ ├── ShaderNodeTree_sockets_4_2.jsonc │ └── ShaderNodeTree_sockets_4_3.jsonc └── user │ └── ScriptingNodesTree.jsonc ├── npie_btypes.py ├── npie_constants.py ├── npie_drawing.py ├── npie_helpers.py ├── npie_keymap.py ├── npie_menus.py ├── npie_node_def_file.py ├── npie_node_info.py ├── npie_prefs.py ├── npie_ui.py ├── operators ├── __init__.py ├── op_add_node.py ├── op_call_link_drag.py ├── op_call_node_pie.py ├── op_check_missing_nodes.py ├── op_copy_nodes_as_json.py ├── op_generate_socket_types_file.py ├── op_insert_node_pie.py ├── op_open_definition_file.py ├── op_reset_popularity.py └── op_show_info.py └── shaders ├── 2D_line_antialiased_frag.glsl └── 2D_vert.glsl /.gitignore: -------------------------------------------------------------------------------- 1 | shared/__pycache__ 2 | *.pyc 3 | **/*.json 4 | !**/node_def_files/*.json 5 | **/npie_dev.py 6 | *.zip 7 | tokens.txt 8 | builds/ 9 | .vscode 10 | .venv 11 | /docs_testing 12 | /node_pie/images -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Node Pie 2 | 3 | Node Pie is a Blender addon that converts the usual blender add node menu into a pie menu: 4 | 5 | 6 | https://github.com/strike-digital/node_pie/assets/59890307/98119501-9c5c-4795-9e5f-43f562525c28 7 | 8 | 9 | 10 | As well as that, the size of the buttons will change depending on how frequently you use each node; nodes you use a lot will have larger buttons, and nodes you don't use much will have smaller buttons: 11 | 12 |
13 | 14 | 15 | 16 |
17 | It also works in all Blender Node editors, and even in custom node editors created by python addons: 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | # Installation 26 | For automatic updates, install the addon from the Blender extensions platform, either from within Blender or from the website (Only for Blender 4.2 and above): https://extensions.blender.org/add-ons/nodepie/ 27 | 28 | You can install also download the latest release here: https://github.com/strike-digital/node_pie/releases/latest 29 | 30 | 31 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | import bpy 14 | 15 | bl_info = { 16 | "name": "Node Pie 1.2.42", 17 | "author": "Andrew Stevenson", 18 | "description": "Add nodes quickly with a pie menu", 19 | "blender": (4, 2, 0), 20 | "version": (1, 2, 42), 21 | "location": "Node editor > Shortcut", 22 | "doc_url": "https://github.com/strike-digital/node_pie/wiki", 23 | "tracker_url": "https://github.com/strike-digital/node_pie/issues", 24 | "category": "Node", 25 | } 26 | 27 | 28 | if not bpy.app.background: 29 | from .node_pie import npie_btypes 30 | 31 | npie_btypes.configure("node_pie", auto_register=True) 32 | 33 | def register(): 34 | npie_btypes.register() 35 | 36 | def unregister(): 37 | npie_btypes.unregister() 38 | 39 | else: 40 | register = unregister = lambda: None 41 | -------------------------------------------------------------------------------- /blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | 3 | id = "NodePie" 4 | version = "1.2.42" 5 | name = "Node Pie" 6 | tagline = "Add nodes faster with a pie menu" 7 | maintainer = "Andrew Stevenson" 8 | type = "add-on" 9 | website = "https://blenderartists.org/t/node-pie-menu-free-addon/1402871" 10 | tags = ["User Interface", "Node"] 11 | 12 | blender_version_min = "4.2.0" 13 | license = ["SPDX:GPL-3.0-or-later"] 14 | 15 | [permissions] 16 | files = "Reading and writing config files" -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | "%~dp0.venv\Scripts\python.exe" -u build.py 2 | @REM python -u build.py -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | import requests 3 | import subprocess 4 | import argparse 5 | from zipfile import ZipFile 6 | from pathlib import Path 7 | import webbrowser 8 | from github import Github 9 | import click 10 | import re 11 | 12 | def update_init_file(init_file: Path, version: tuple): 13 | with open(init_file, "r") as file: 14 | text = file.read() 15 | 16 | version = tuple(version) 17 | str_version = [str(v) for v in version] 18 | matcher = '"name".*:.*,' 19 | name_match = re.findall(matcher, text) 20 | matcher = '"version".*:.*,' 21 | version_match = re.findall(matcher, text) 22 | text = text.replace(name_match[0], f'"name": "Node Pie {".".join(str_version)}",') 23 | text = text.replace(version_match[0], f'"version": {str(version)},') 24 | 25 | with open(init_file, "w") as file: 26 | file.write(text) 27 | 28 | def update_constants_file(constants_file, value): 29 | with open(constants_file, "r") as file: 30 | text = file.read() 31 | 32 | matcher = '__IS_DEV__.*=.*' 33 | name_match = re.findall(matcher, text) 34 | text = text.replace(name_match[0], f'__IS_DEV__ = {str(value)}') 35 | 36 | with open(constants_file, "w") as file: 37 | file.write(text) 38 | 39 | def update_manifest_file(manifest_file: Path, version: tuple): 40 | with open(manifest_file, 'r') as file: 41 | text = file.read() 42 | 43 | version = tuple(version) 44 | matcher = r'^version *= *"\d*.\d*.\d*"' 45 | # print(text) 46 | # matcher = '^version = ' 47 | match = re.findall(matcher, text, flags=re.MULTILINE) 48 | str_version = [str(v) for v in version] 49 | text = text.replace(match[0], f'version = \"{".".join(str_version)}\"') 50 | 51 | with open(manifest_file, 'w') as file: 52 | file.write(text) 53 | 54 | # def multi_input(prompt=""): 55 | # """Get user input over multiple lines. Exit with Ctrl-Z""" 56 | # print(prompt) 57 | # contents = [] 58 | # while True: 59 | # try: 60 | # line = input() 61 | # except EOFError: 62 | # break 63 | # contents.append(line) 64 | # return "\n".join(contents) 65 | 66 | # def multi_line_input(prompt): 67 | # dark = "#26242f" 68 | # win = tkinter.Tk() 69 | 70 | # w = h = 750 71 | 72 | # # get screen width and height 73 | # ws = win.winfo_screenwidth() # width of the screen 74 | # hs = win.winfo_screenheight() # height of the screen 75 | 76 | # # calculate x and y coordinates for the Tk root window 77 | # x = int((ws/2) - (w/2)) 78 | # y = int((hs/2) - (h/2)) 79 | 80 | # win.geometry(f"{w}x{h}+{x}+{y}") 81 | # win.config(bg=dark) 82 | 83 | # def confirm(): 84 | # confirm.final_text = text.get("1.0", tkinter.END) 85 | # win.quit() 86 | 87 | # label = tkinter.Label(win, text=prompt) 88 | # label.configure(bg=dark, fg="white") 89 | # label.pack() 90 | 91 | # button = tkinter.Button(win, text="Confirm", width=20, command=confirm) 92 | # button.pack(side=tkinter.BOTTOM) 93 | # button.configure(bg=dark, fg="white") 94 | 95 | # text = tkinter.Text(win,) 96 | # scroll = tkinter.Scrollbar(win) 97 | # text.configure(yscrollcommand=scroll.set, bg=dark, fg="white") 98 | # text.focus_set() 99 | # text.pack(side=tkinter.LEFT, fill=tkinter.BOTH) 100 | 101 | # scroll.config(command=text.yview) 102 | # scroll.pack(side=tkinter.RIGHT, fill=tkinter.BOTH) 103 | 104 | # win.mainloop() 105 | # return confirm.final_text 106 | 107 | # def multi_line_input(prompt): 108 | # ctk.set_appearance_mode("dark") 109 | # ctk.set_default_color_theme("dark-blue") 110 | # win = ctk.CTk() 111 | 112 | # w = h = 750 113 | 114 | # # get screen width and height 115 | # ws = win.winfo_screenwidth() # width of the screen 116 | # hs = win.winfo_screenheight() # height of the screen 117 | 118 | # # calculate x and y coordinates for the Tk root window 119 | # x = int((ws / 2) - (w / 2)) 120 | # y = int((hs / 2) - (h / 2)) 121 | 122 | # win.geometry(f"{w}x{h}+{x}+{y}") 123 | 124 | # # win.config(bg=dark) 125 | 126 | # def confirm(): 127 | # confirm.final_text = textbox.textbox.get("1.0", ctk.END) 128 | # win.quit() 129 | 130 | # label = ctk.CTkLabel(win, text=prompt) 131 | # label.pack() 132 | 133 | # button = ctk.CTkButton(win, text="Confirm", width=20, command=confirm) 134 | # button.pack(side=ctk.BOTTOM) 135 | # # button.configure(bg=dark, fg="white") 136 | 137 | # textbox = ctk.CTkTextbox(win) 138 | # textbox.focus_set() 139 | # textbox.pack(fill=ctk.BOTH, side=ctk.LEFT) 140 | # # scroll = ctk.CTkScrollbar(win) 141 | 142 | # # textbox.configure(width=w - scroll.winfo_width() * 20) 143 | # textbox.configure(width=w) 144 | 145 | # # textbox.configure(yscrollcommand=scroll.set) 146 | 147 | # # scroll.configure(command=textbox.yview) 148 | # # scroll.pack(side=ctk.RIGHT, fill=ctk.BOTH) 149 | 150 | # win.mainloop() 151 | # return confirm.final_text 152 | 153 | def multi_line_input(prompt: str): 154 | return click.edit(text=prompt, editor="code -w -n", extension=".md") 155 | 156 | def main(): 157 | parser = argparse.ArgumentParser() 158 | parser.add_argument( 159 | "-v", 160 | "--version", 161 | help="The version number to use, in the format '0.0.1'", 162 | default="", 163 | type=str, 164 | ) 165 | args = parser.parse_args() 166 | 167 | ignore = ["images\\", "README", ".gitignore", "build.py", "build.bat", "vendor\\"] 168 | path = Path(__file__).parent 169 | files = [Path(f.decode("utf8")) for f in subprocess.check_output("git ls-files", shell=True).splitlines()] 170 | files = [f for f in files if not any(i in str(f) for i in ignore)] 171 | # pprint(files) 172 | 173 | # version 174 | if args.version: 175 | file_version = args.version.replace(".", "_") 176 | else: 177 | res = requests.get("https://api.github.com/repos/strike-digital/node_pie/releases").json()[0] 178 | latest_version: str = res["tag_name"] 179 | subversion = latest_version.split(".")[-1] 180 | 181 | file_version = "_".join(latest_version.split(".")[:-1] + [str(int(subversion) + 1)]) 182 | 183 | print(file_version) 184 | version = tuple(int(f) for f in file_version.split("_")) 185 | update_init_file(path / "__init__.py", version) 186 | update_manifest_file(path / "blender_manifest.toml", version) 187 | # update_constants_file(constants_file, False) 188 | 189 | out_path = path / "builds" / f"node_pie_{file_version}.zip" 190 | 191 | print(f"Zipping {len(files)} files") 192 | with ZipFile(out_path, 'w') as z: 193 | # writing each file one by one 194 | for file in files: 195 | print(file, file.exists()) 196 | # print(file) 197 | # z.write(file, arcname=str(file).replace("asset_bridge", f"asset_bridge_{file_version}")) 198 | z.write(file, arcname=str(f"node_pie_{file_version}" / file)) 199 | print(f"Zipped: {out_path}") 200 | 201 | try: 202 | with open("tokens.txt", "r") as f: 203 | token = f.readlines()[0] 204 | except Exception: 205 | webbrowser.open(out_path.parent) 206 | return 207 | 208 | create_release = input("Do you want to create a release on github? (y/n) ") 209 | 210 | if create_release == "y": 211 | gh = Github(token) 212 | repo = gh.get_repo("strike-digital/node_pie") 213 | version = file_version.replace("_", ".") 214 | commit = repo.get_commits()[0] 215 | 216 | message = multi_line_input("Release message:") 217 | if message.rstrip().endswith("CANCEL"): 218 | print("Cancelled release") 219 | return 220 | release = repo.create_git_tag_and_release( 221 | tag=version, 222 | release_name="v" + version, 223 | release_message=message, 224 | tag_message=version, 225 | object=commit.sha, 226 | type="", 227 | ) 228 | release.upload_asset(str(out_path)) 229 | webbrowser.open(release.html_url) 230 | webbrowser.open(out_path.parent) 231 | webbrowser.open("https://extensions.blender.org/add-ons/nodepie/manage/versions/new/") 232 | print("FINISHED!") 233 | 234 | else: 235 | webbrowser.open(out_path.parent) 236 | 237 | 238 | if __name__ == "__main__": 239 | main() 240 | -------------------------------------------------------------------------------- /images/Node Pie compositor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/images/Node Pie compositor.jpg -------------------------------------------------------------------------------- /images/Node Pie geometry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/images/Node Pie geometry.jpg -------------------------------------------------------------------------------- /images/Node Pie preferences.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/images/Node Pie preferences.jpg -------------------------------------------------------------------------------- /images/Node Pie serpens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/images/Node Pie serpens.jpg -------------------------------------------------------------------------------- /images/Node Pie shader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/images/Node Pie shader.jpg -------------------------------------------------------------------------------- /node_pie/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/node_pie/__init__.py -------------------------------------------------------------------------------- /node_pie/generate_node_layout.py: -------------------------------------------------------------------------------- 1 | import json 2 | from .npie_constants import NODE_DEF_DIR 3 | 4 | # File used to generate the initial node def file, doesn't actually do anything in the addon currently 5 | 6 | colours = { 7 | "Attribute": "attribute", 8 | "Color": "color", 9 | "Curve": "geometry", 10 | "Geometry": "geometry", 11 | "Input": "input", 12 | "Instances": "geometry", 13 | "Material": "geometry", 14 | "Mesh": "geometry", 15 | "Point": "geometry", 16 | "Curve Primitives": "geometry", 17 | "Mesh Primitives": "geometry", 18 | "Text": "geometry", 19 | "Texture": "texture", 20 | "Utilities": "converter", 21 | "Vector": "vector", 22 | "Volume": "geometry", 23 | "Layout": "layout", 24 | # 3.4+ 25 | "Mesh Topology": "input", 26 | "Curve Topology": "input", 27 | "UV": "converter", 28 | } 29 | overrides = {} 30 | color_overrides = { 31 | "Input": "input", 32 | "FunctionNode": "converter", 33 | "GeometryNodeStringJoin": "converter", 34 | "ShaderNodeValToRGB": "converter", 35 | "ShaderNodeCombineXYZ": "converter", 36 | "ShaderNodeSeparateXYZ": "converter", 37 | "GeometryNodeSplineParameter": "input", 38 | "GeometryNodeSplineLength": "input", 39 | "GeometryNodeCurveHandleTypeSelection": "input", 40 | "GeometryNodeCurveEndpointSelection": "input", 41 | } 42 | 43 | data = {} 44 | 45 | 46 | def main(): 47 | 48 | # for area in bpy.context.window.screen.areas: 49 | # if area.type == "NODE_EDITOR": 50 | # with bpy.context.temp_override(area=area): 51 | # cats = list(nodeitems_utils.node_categories_iter(bpy.context)) 52 | # for cat in cats: 53 | # # if cat.name not in colours.keys(): 54 | # # continue 55 | # items = [] 56 | # for item in cat.items(bpy.context): 57 | # if not isinstance(item, nodeitems_utils.NodeItem): 58 | # continue 59 | # settings = item.settings 60 | # data_item = {"label": item.label, "identifier": item.nodetype} 61 | # if settings: 62 | # data_item["settings"] = settings 63 | # if item.nodetype in color_overrides: 64 | # data_item["color"] = color_overrides[item.nodetype] 65 | # items.append(data_item) 66 | # data[cat.identifier] = {"label": cat.name, "color": "input", "nodes": items} 67 | 68 | # selected_nodes = bpy.context.selected_nodes 69 | # items = [] 70 | # for node in selected_nodes: 71 | # data_item = {"label": node.bl_label, "identifier": node.bl_idname} 72 | # items.append(data_item) 73 | 74 | # # Uncomment to write out the initial file from an old version that still has a working NodeItems api. 75 | # # Shouldn't need to be used now. 76 | # if data: 77 | # fpath = NODE_DEF_DIR / "CompositorNodeTree2.jsonc" 78 | # with open(fpath, "w") as f: 79 | # json.dump(data, f, indent=2) 80 | 81 | # print("Dumped!") 82 | # print(data) 83 | 84 | orig_file = NODE_DEF_DIR / "GeometryNodeTree.jsonc" 85 | new_file = NODE_DEF_DIR / "GeometryNodeTree_40 copy.jsonc" 86 | 87 | with open(orig_file, "r") as f: 88 | orig_data = json.load(f) 89 | with open(new_file, "r") as f: 90 | new_data = json.load(f) 91 | 92 | orig_categories = orig_data["categories"] 93 | new_categories = new_data["categories"] 94 | data = {} 95 | 96 | for orig_name, orig_cat in orig_categories.items(): 97 | new_cat = new_categories[orig_name] 98 | nodes = [] 99 | for node in new_cat["nodes"]: 100 | if node.get("identifier") not in {n.get("identifier") for n in orig_cat["nodes"]}: 101 | if node.get("separator"): 102 | nodes.append(node) 103 | if nodes: 104 | data[orig_name] = {"nodes": nodes} 105 | 106 | # print(dict(new_categories.items() - orig_categories.items())) 107 | data.update({k: v for k, v in new_categories.items() if k not in orig_categories}) 108 | 109 | print(json.dumps(data, indent=2) + ",") 110 | 111 | 112 | # Uncomment to call main function 113 | # bpy.app.timers.register(main) -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/CompositorNodeTree_1_0.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "layout": { 3 | "left": [ 4 | ["DISTORT", "LAYOUT"], 5 | ["CONVERTOR", "VECTOR"] 6 | ], 7 | "right": [["INPUT", "OUTPUT"], ["FILTER"]], 8 | "top": [["COLOR"]], 9 | "bottom": [["MATTE"]] 10 | }, 11 | "categories": { 12 | "INPUT": { 13 | "label": "Input", 14 | "color": "input", 15 | "nodes": [ 16 | { "identifier": "CompositorNodeBokehImage" }, 17 | { "identifier": "CompositorNodeImage" }, 18 | { "identifier": "CompositorNodeMask" }, 19 | { "identifier": "CompositorNodeMovieClip" }, 20 | { "identifier": "CompositorNodeRLayers" }, 21 | { "identifier": "CompositorNodeRGB" }, 22 | { "identifier": "CompositorNodeSceneTime" }, 23 | { "identifier": "CompositorNodeTexture" }, 24 | { "identifier": "CompositorNodeTime" }, 25 | { "identifier": "CompositorNodeTrackPos" }, 26 | { "identifier": "CompositorNodeValue" } 27 | ] 28 | }, 29 | "OUTPUT": { 30 | "label": "Output", 31 | "color": "output", 32 | "nodes": [ 33 | { "identifier": "CompositorNodeComposite" }, 34 | { "identifier": "CompositorNodeOutputFile" }, 35 | { "identifier": "CompositorNodeLevels" }, 36 | { "identifier": "CompositorNodeSplitViewer" }, 37 | { "identifier": "CompositorNodeViewer" } 38 | ] 39 | }, 40 | "COLOR": { 41 | "label": "Color", 42 | "color": "color", 43 | "nodes": [ 44 | { "identifier": "CompositorNodeAlphaOver" }, 45 | { "identifier": "CompositorNodeBrightContrast" }, 46 | { "identifier": "CompositorNodeColorBalance" }, 47 | { "identifier": "CompositorNodeColorCorrection" }, 48 | { "identifier": "CompositorNodeExposure" }, 49 | { "identifier": "CompositorNodeGamma" }, 50 | { "identifier": "CompositorNodeHueCorrect" }, 51 | { "identifier": "CompositorNodeHueSat" }, 52 | { "identifier": "CompositorNodeInvert" }, 53 | { 54 | "identifier": "CompositorNodeMixRGB", 55 | "variants": { 56 | "Add": { "blend_type": "ADD" }, 57 | "Subtract": { "blend_type": "SUBTRACT" }, 58 | "Multiply": { "blend_type": "MULTIPLY" }, 59 | "Divide": { "blend_type": "DIVIDE" }, 60 | "Overlay": { "blend_type": "OVERLAY" }, 61 | "Linear Light": { "blend_type": "LINEAR_LIGHT" } 62 | } 63 | }, 64 | { "identifier": "CompositorNodePosterize" }, 65 | { "identifier": "CompositorNodeCurveRGB" }, 66 | { "identifier": "CompositorNodeTonemap" }, 67 | { "identifier": "CompositorNodeZcombine" } 68 | ] 69 | }, 70 | "CONVERTOR": { 71 | "label": "Converter", 72 | "color": "converter", 73 | "nodes": [ 74 | { "identifier": "CompositorNodePremulKey" }, 75 | { "identifier": "CompositorNodeConvertColorSpace" }, 76 | { "identifier": "CompositorNodeValToRGB" }, 77 | { "identifier": "CompositorNodeCombineColor" }, 78 | { "identifier": "CompositorNodeCombineXYZ" }, 79 | { "identifier": "CompositorNodeIDMask" }, 80 | { 81 | "identifier": "CompositorNodeMath", 82 | "variants": { 83 | "Negate": { 84 | "operation": "MULTIPLY", 85 | "show_options": false, 86 | "label": "Negate", 87 | "inputs[1].hide": true, 88 | "inputs[1].default_value": -1 89 | }, 90 | "One Minus": { 91 | "operation": "SUBTRACT", 92 | "show_options": false, 93 | "label": "One Minus", 94 | "inputs[0].hide": true, 95 | "inputs[0].default_value": 1 96 | }, 97 | "One Over": { 98 | "operation": "DIVIDE", 99 | "show_options": false, 100 | "label": "One Over", 101 | "inputs[0].hide": true, 102 | "inputs[0].default_value": 1 103 | }, 104 | "separator": true, 105 | "Multiply": { "operation": "MULTIPLY" }, 106 | "Divide": { "operation": "DIVIDE" }, 107 | "Subtract": { "operation": "SUBTRACT" }, 108 | "Modulo": { "operation": "MODULO" }, 109 | "Absolute": { "operation": "ABSOLUTE" }, 110 | "Power": { "operation": "POWER" }, 111 | "Sine": { "operation": "SINE" }, 112 | "Cosine": { "operation": "COSINE" } 113 | } 114 | }, 115 | { "identifier": "CompositorNodeRGBToBW" }, 116 | { "identifier": "CompositorNodeSeparateColor" }, 117 | { "identifier": "CompositorNodeSeparateXYZ" }, 118 | { "identifier": "CompositorNodeSetAlpha" }, 119 | { "identifier": "CompositorNodeSwitch" }, 120 | { "identifier": "CompositorNodeSwitchView" } 121 | ] 122 | }, 123 | "FILTER": { 124 | "label": "Filter", 125 | "color": "filter", 126 | "nodes": [ 127 | { "identifier": "CompositorNodeAntiAliasing" }, 128 | { "identifier": "CompositorNodeBilateralblur" }, 129 | { "identifier": "CompositorNodeBlur" }, 130 | { "identifier": "CompositorNodeBokehBlur" }, 131 | { "identifier": "CompositorNodeDefocus" }, 132 | { "identifier": "CompositorNodeDenoise" }, 133 | { "identifier": "CompositorNodeDespeckle" }, 134 | { "identifier": "CompositorNodeDilateErode" }, 135 | { "identifier": "CompositorNodeDBlur" }, 136 | { "identifier": "CompositorNodeFilter" }, 137 | { "identifier": "CompositorNodeGlare" }, 138 | { "identifier": "CompositorNodeInpaint" }, 139 | { "identifier": "CompositorNodeKuwahara" }, 140 | { "identifier": "CompositorNodePixelate" }, 141 | { "identifier": "CompositorNodeSunBeams" }, 142 | { "identifier": "CompositorNodeVecBlur" } 143 | ] 144 | }, 145 | "VECTOR": { 146 | "label": "Vector", 147 | "color": "vector", 148 | "nodes": [ 149 | { "identifier": "CompositorNodeMapRange" }, 150 | { "identifier": "CompositorNodeMapValue" }, 151 | { "identifier": "CompositorNodeNormal" }, 152 | { "identifier": "CompositorNodeNormalize" }, 153 | { "identifier": "CompositorNodeCurveVec" } 154 | ] 155 | }, 156 | "MATTE": { 157 | "label": "Matte", 158 | "color": "matte", 159 | "nodes": [ 160 | { "identifier": "CompositorNodeBoxMask" }, 161 | { "identifier": "CompositorNodeChannelMatte" }, 162 | { "identifier": "CompositorNodeChromaMatte" }, 163 | { "identifier": "CompositorNodeColorMatte" }, 164 | { "identifier": "CompositorNodeColorSpill" }, 165 | { "identifier": "CompositorNodeCryptomatteV2" }, 166 | { "identifier": "CompositorNodeCryptomatte" }, 167 | { "identifier": "CompositorNodeDiffMatte" }, 168 | { "identifier": "CompositorNodeDistanceMatte" }, 169 | { "identifier": "CompositorNodeDoubleEdgeMask" }, 170 | { "identifier": "CompositorNodeEllipseMask" }, 171 | { "identifier": "CompositorNodeKeying" }, 172 | { "identifier": "CompositorNodeKeyingScreen" }, 173 | { "identifier": "CompositorNodeLumaMatte" } 174 | ] 175 | }, 176 | "DISTORT": { 177 | "label": "Distort", 178 | "color": "distor", 179 | "nodes": [ 180 | { "identifier": "CompositorNodeCornerPin" }, 181 | { "identifier": "CompositorNodeCrop" }, 182 | { "identifier": "CompositorNodeDisplace" }, 183 | { "identifier": "CompositorNodeFlip" }, 184 | { "identifier": "CompositorNodeLensdist" }, 185 | { "identifier": "CompositorNodeMapUV" }, 186 | { "identifier": "CompositorNodeMovieDistortion" }, 187 | { "identifier": "CompositorNodePlaneTrackDeform" }, 188 | { "identifier": "CompositorNodeRotate" }, 189 | { "identifier": "CompositorNodeScale" }, 190 | { "identifier": "CompositorNodeStabilize" }, 191 | { "identifier": "CompositorNodeTransform" }, 192 | { "identifier": "CompositorNodeTranslate" } 193 | ] 194 | }, 195 | "LAYOUT": { 196 | "label": "Layout", 197 | "color": "layout", 198 | "nodes": [{ "identifier": "NodeFrame" }, { "identifier": "NodeReroute" }] 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/CompositorNodeTree_4_1.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "blender_version": [4, 1, 0], 3 | "removals": { 4 | "nodes": ["CompositorNodeSplitViewer"] 5 | }, 6 | "additions": { 7 | "categories": { 8 | "CONVERTOR": { 9 | "nodes": [ 10 | { 11 | "identifier": "CompositorNodeSplit", 12 | "before_node": "CompositorNodeSwitch" 13 | } 14 | ] 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_3_5.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 3.5, compared to the base file. 2 | { 3 | "blender_version": [3, 5, 0], 4 | "additions": { 5 | "categories": { 6 | "ATTRIBUTE": { 7 | "nodes": [ 8 | { "identifier": "GeometryNodeBlurAttribute", "after_node": "top" } 9 | ] 10 | }, 11 | "INPUT": { 12 | "nodes": [ 13 | { 14 | "identifier": "GeometryNodeInputImage", 15 | "after_node": "FunctionNodeInputColor" 16 | }, 17 | { 18 | "identifier": "GeometryNodeImageInfo", 19 | "before_node": "GeometryNodeIsViewport" 20 | } 21 | ] 22 | }, 23 | "CURVE": { 24 | "nodes": [ 25 | { 26 | "identifier": "GeometryNodeInterpolateCurves", 27 | "after_node": "GeometryNodeTrimCurve" 28 | } 29 | ] 30 | }, 31 | "MESH": { 32 | "nodes": [ 33 | { 34 | "identifier": "GeometryNodeEdgesToFaceGroups", 35 | "after_node": "GeometryNodeMeshFaceSetBoundaries", 36 | "color": "input" 37 | } 38 | ] 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_3_6.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 3.6, compared to the base file. 2 | { 3 | "blender_version": [3, 6, 0], 4 | "additions": { 5 | "layout": { 6 | "right": [[], [], ["SIMULATION"]] 7 | }, 8 | "categories": { 9 | "GEOMETRY": { 10 | "nodes": [ 11 | { 12 | "identifier": "GeometryNodeIndexOfNearest", 13 | "after_node": "GeometryNodeSampleIndex" 14 | } 15 | ] 16 | }, 17 | "SIMULATION": { 18 | "label": "Simulation", 19 | "color": "layout", 20 | "icon": "PHYSICS", 21 | "nodes": [ 22 | { 23 | "label": "Simulation Zone", 24 | "operator": "node.add_simulation_zone", 25 | "settings": { "use_transform": true } 26 | } 27 | ] 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_4_0.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 4.0, compared to the base file. 2 | // Adds the new nodes, and also replaces the simulation category with one for all zones. 3 | // New tool specific nodes are also added, with poll conditions so that they only show up in the correct context. 4 | { 5 | "blender_version": [4, 0, 0], 6 | "removals": { 7 | "layout": { 8 | "right": [[], ["VOLUME"], ["SIMULATION"]] 9 | }, 10 | "categories": { 11 | "SIMULATION": {} 12 | } 13 | }, 14 | "additions": { 15 | "layout": { 16 | "right": [["ROTATION"], [], ["VOLUME", "ZONE"]] 17 | }, 18 | "poll_types": { 19 | "is_tool": [ 20 | { 21 | "context_path": "space_data.geometry_nodes_type", 22 | "operand": "equals", 23 | "value": "TOOL" 24 | } 25 | ] 26 | }, 27 | "categories": { 28 | "INPUT": { 29 | "nodes": [ 30 | { 31 | "identifier": "GeometryNodeInputEdgeSmooth", 32 | "after_node": "GeometryNodeInputIndex" 33 | }, 34 | { 35 | "separator": true, 36 | "label": "Tool", 37 | "after_node": "bottom", 38 | "poll_type": "is_tool" 39 | }, 40 | { 41 | "identifier": "GeometryNodeTool3DCursor", 42 | "poll_type": "is_tool" 43 | }, 44 | { 45 | "identifier": "GeometryNodeToolFaceSet", 46 | "poll_type": "is_tool" 47 | }, 48 | { 49 | "identifier": "GeometryNodeToolSelection", 50 | "poll_type": "is_tool" 51 | } 52 | ] 53 | }, 54 | "ROTATION": { 55 | "label": "Rotation", 56 | "color": "converter", 57 | "icon": "CON_ROTLIKE", 58 | "nodes": [ 59 | { "identifier": "FunctionNodeAxisAngleToRotation" }, 60 | { "identifier": "FunctionNodeEulerToRotation" }, 61 | { "identifier": "FunctionNodeInvertRotation" }, 62 | { 63 | "identifier": "ShaderNodeMix", 64 | "label": "Mix Rotation", 65 | "settings": { "data_type": "ROTATION" } 66 | }, 67 | { "identifier": "FunctionNodeQuaternionToRotation" }, 68 | { "identifier": "FunctionNodeRotateVector" }, 69 | { "identifier": "FunctionNodeRotationToAxisAngle" }, 70 | { "identifier": "FunctionNodeRotationToEuler" }, 71 | { "identifier": "FunctionNodeRotationToQuaternion" } 72 | ] 73 | }, 74 | "GEOMETRY": { 75 | "nodes": [ 76 | { 77 | "identifier": "GeometryNodeToolSetSelection", 78 | "poll_type": "is_tool" 79 | }, 80 | { 81 | "identifier": "GeometryNodeToolSetFaceSet", 82 | "before_node": "GeometryNodeSetID", 83 | "poll_type": "is_tool" 84 | } 85 | ] 86 | }, 87 | "MESH": { 88 | "nodes": [ 89 | { 90 | "identifier": "GeometryNodeMeshToSDFVolume", 91 | "after_node": "GeometryNodeMeshToVolume" 92 | }, 93 | { 94 | "identifier": "GeometryNodeInputEdgeSmooth", 95 | "before_node": "GeometryNodeInputMeshFaceIsPlanar", 96 | "color": "input" 97 | }, 98 | { 99 | "identifier": "GeometryNodeCornersOfEdge", 100 | "color": "input", 101 | "before_node": "GeometryNodeCornersOfFace" 102 | } 103 | ] 104 | }, 105 | "POINT": { 106 | "nodes": [ 107 | { 108 | "identifier": "GeometryNodePointsToSDFVolume", 109 | "after_node": "GeometryNodePointsToVolume" 110 | } 111 | ] 112 | }, 113 | "VOLUME": { 114 | "nodes": [ 115 | { "separator": true }, 116 | { "identifier": "GeometryNodeInputSignedDistance" }, 117 | { "identifier": "GeometryNodeSampleVolume" }, 118 | { "identifier": "GeometryNodeSDFVolumeSphere" }, 119 | { "identifier": "GeometryNodeOffsetSDFVolume" }, 120 | { "identifier": "GeometryNodeMeanFilterSDFVolume" } 121 | ] 122 | }, 123 | "ZONE": { 124 | "label": "Zones", 125 | "color": "layout", 126 | "icon": "PHYSICS", 127 | "nodes": [ 128 | { 129 | "label": "Simulation Zone", 130 | "identifier": "SimulationZone", 131 | "operator": "node.add_simulation_zone", 132 | "settings": { "use_transform": true } 133 | }, 134 | { 135 | "label": "Repeat Zone", 136 | "identifier": "RepeatZone", 137 | "operator": "node.add_repeat_zone", 138 | "settings": { "use_transform": true } 139 | } 140 | ] 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_4_1.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 3.5, compared to the base file. 2 | { 3 | "blender_version": [4, 1, 0], 4 | "removals": { 5 | "layout": { 6 | "left": [["UV"]] 7 | }, 8 | "nodes": [ 9 | "ShaderNodeTexMusgrave", 10 | "GeometryNodeSDFVolumeSphere", 11 | "GeometryNodeInputSignedDistance", 12 | "GeometryNodeOffsetSDFVolume", 13 | "GeometryNodeSampleVolume", 14 | "GeometryNodeMeanFilterSDFVolume", 15 | "GeometryNodeMeshToSDFVolume", 16 | "GeometryNodePointsToSDFVolume", 17 | "FunctionNodeRotateEuler" 18 | ] 19 | }, 20 | "additions": { 21 | "layout": { 22 | "right": [[], ["UV"]] 23 | }, 24 | "categories": { 25 | "GEOMETRY": { 26 | "nodes": [ 27 | { 28 | "identifier": "GeometryNodeSplitToInstances", 29 | "after_node": "GeometryNodeSeparateGeometry" 30 | }, 31 | { 32 | "identifier": "GeometryNodeSortElements", 33 | "before_node": "GeometryNodeSplitToInstances" 34 | } 35 | ] 36 | }, 37 | "UTILITIES": { 38 | "nodes": [ 39 | { 40 | "identifier": "GeometryNodeIndexSwitch", 41 | "after_node": "FunctionNodeFloatToInt" 42 | } 43 | ] 44 | }, 45 | "INPUT": { 46 | "nodes": [ 47 | { 48 | "identifier": "GeometryNodeInputActiveCamera", 49 | "before_node": "GeometryNodeCollectionInfo" 50 | } 51 | ] 52 | }, 53 | "ROTATION": { 54 | "nodes": [ 55 | { "identifier": "FunctionNodeRotateRotation" , "before_node": "FunctionNodeRotateVector"} 56 | ] 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_4_2.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 4.2, compared to the previous version. 2 | // This version added a lot of matrix nodes which requires the layout to be rearranged a lot 3 | // It also added some more tool nodes that need polls 4 | { 5 | "blender_version": [4, 2, 0], 6 | "removals": { 7 | "layout": { 8 | "left": [[], [], ["UTILITIES", "INSTANCE", "POINT"]], 9 | "right": [["INPUT", "COLOR", "ROTATION"], ["UV"]], 10 | "top": [["ATTRIBUTE"]] 11 | }, 12 | "nodes": [ 13 | "FunctionNodeAlignEulerToVector", 14 | "SepRotation", 15 | "GeometryNodeIndexSwitch", 16 | "GeometryNodeSwitch" 17 | ] 18 | }, 19 | "additions": { 20 | "layout": { 21 | "left": [[], [], ["MATRIX", "UTILITIES", "ROTATION"]], 22 | "right": [["INSTANCE", "INPUT"], ["POINT"]], 23 | "top": [["COLOR", "ATTRIBUTE"]], 24 | "bottom": [["UV"]] 25 | }, 26 | "categories": { 27 | "COLOR": { 28 | "nodes": [ 29 | { 30 | "identifier": "ShaderNodeBlackbody", 31 | "before_node": "FunctionNodeCompare" 32 | } 33 | ] 34 | }, 35 | "GEOMETRY": { 36 | "nodes": [ 37 | { 38 | "identifier": "GeometryNodeBake", 39 | "before_node": "GeometryNodeBoundBox" 40 | } 41 | ] 42 | }, 43 | "ROTATION": { 44 | "nodes": [ 45 | { 46 | "identifier": "FunctionNodeAlignRotationToVector", 47 | "before_node": "FunctionNodeAxisAngleToRotation" 48 | } 49 | ] 50 | }, 51 | "INPUT": { 52 | "nodes": [ 53 | { 54 | "identifier": "GeometryNodeToolMousePosition", 55 | "after_node": "GeometryNodeToolFaceSet", 56 | "poll_type": "is_tool" 57 | }, 58 | { 59 | "identifier": "GeometryNodeViewportTransform", 60 | "after_node": "bottom", 61 | "poll_type": "is_tool" 62 | }, 63 | { 64 | "identifier": "FunctionNodeInputRotation", 65 | "before_node": "FunctionNodeInputString" 66 | } 67 | ] 68 | }, 69 | "MATRIX": { 70 | "icon": "MESH_GRID", 71 | "label": "Matrix", 72 | "color": "converter", 73 | "nodes": [ 74 | { "identifier": "FunctionNodeCombineMatrix" }, 75 | { "identifier": "FunctionNodeCombineTransform" }, 76 | { "identifier": "FunctionNodeInvertMatrix" }, 77 | { "identifier": "FunctionNodeMatrixMultiply" }, 78 | { "identifier": "FunctionNodeProjectPoint" }, 79 | { "identifier": "FunctionNodeSeparateMatrix" }, 80 | { "identifier": "FunctionNodeSeparateTransform" }, 81 | { "identifier": "FunctionNodeTransformDirection" }, 82 | { "identifier": "FunctionNodeTransformPoint" }, 83 | { "identifier": "FunctionNodeTransposeMatrix" } 84 | ] 85 | }, 86 | "POINT": { 87 | "nodes": [ 88 | { 89 | "identifier": "GeometryNodePointsToCurves", 90 | "before_node": "GeometryNodePointsToVertices" 91 | } 92 | ] 93 | }, 94 | "UTILITIES": { 95 | "nodes": [ 96 | // Create a new "Switch" section 97 | { "separator": true, "label": "Switch", "after_node": "bottom" }, 98 | { "identifier": "GeometryNodeIndexSwitch" }, 99 | { "identifier": "GeometryNodeMenuSwitch" }, 100 | { "identifier": "GeometryNodeSwitch" } 101 | ] 102 | }, 103 | "INSTANCE": { 104 | "nodes": [ 105 | { 106 | "identifier": "GeometryNodeSetInstanceTransform", 107 | "after_node": "GeometryNodeScaleInstances" 108 | }, 109 | { 110 | "identifier": "GeometryNodeInstanceTransform", 111 | "after_node": "GeometryNodeInputInstanceScale", 112 | "color": "input" 113 | } 114 | ] 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_4_3.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 4.3, compared to the previous version. 2 | // Lots of misc nodes added, plus a new zone. 3 | { 4 | "blender_version": [4, 3, 0], 5 | "removals": {}, 6 | "additions": { 7 | "layout": { 8 | "right": [["GIZMO"]] 9 | }, 10 | "categories": { 11 | "GIZMO": { 12 | "label": "Gizmo", 13 | "color": "layout", 14 | "nodes": [ 15 | { "identifier": "GeometryNodeGizmoDial" }, 16 | { "identifier": "GeometryNodeGizmoLinear" }, 17 | { "identifier": "GeometryNodeGizmoTransform" } 18 | ] 19 | }, 20 | "INPUT": { 21 | "nodes": [ 22 | { 23 | "identifier": "GeometryNodeInputNamedLayerSelection", 24 | "before_node": "GeometryNodeObjectInfo" 25 | }, 26 | { 27 | "identifier": "GeometryNodeWarning", 28 | "after_node": "FunctionNodeInputVector" 29 | }, 30 | { 31 | "identifier": "GeometryNodeToolActiveElement", 32 | "after_node": "GeometryNodeTool3DCursor" 33 | } 34 | ] 35 | }, 36 | "CURVE": { 37 | "nodes": [ 38 | { 39 | "identifier": "GeometryNodeCurvesToGreasePencil", 40 | "before_node": "GeometryNodeCurveToMesh" 41 | }, 42 | { 43 | "identifier": "GeometryNodeMergeLayers", 44 | "after_node": "GeometryNodeFilletCurve" 45 | }, 46 | { 47 | "identifier": "GeometryNodeGreasePencilToCurves", 48 | "after_node": "GeometryNodeFilletCurve" 49 | } 50 | ] 51 | }, 52 | "GEOMETRY": { 53 | "nodes": [ 54 | { 55 | "identifier": "GeometryNodeSetGeometryName", 56 | "before_node": "GeometryNodeSetID" 57 | } 58 | ] 59 | }, 60 | "MATRIX": { 61 | "nodes": [ 62 | { 63 | "identifier": "FunctionNodeMatrixDeterminant", 64 | "after_node": "FunctionNodeCombineTransform" 65 | } 66 | ] 67 | }, 68 | "ROTATION": { 69 | "nodes": [ 70 | { 71 | "identifier": "FunctionNodeAxesToRotation", 72 | "before_node": "FunctionNodeAxisAngleToRotation" 73 | } 74 | ] 75 | }, 76 | "TEXTURE": { 77 | "nodes": [ 78 | { 79 | "identifier": "ShaderNodeTexGabor", 80 | "before_node": "ShaderNodeTexGradient" 81 | } 82 | ] 83 | }, 84 | "UTILITIES": { 85 | "nodes": [ 86 | { 87 | "identifier": "FunctionNodeIntegerMath", 88 | "after_node": "FunctionNodeFloatToInt" 89 | }, 90 | { 91 | "identifier": "FunctionNodeHashValue", 92 | "after_node": "FunctionNodeFloatToInt" 93 | } 94 | ] 95 | }, 96 | "ZONE": { 97 | "nodes": [ 98 | { 99 | "label": "For Each Element Zone", 100 | "identifier": "ForEachElementZone", 101 | "before_node": "SimulationZone", 102 | "operator": "node.add_foreach_geometry_element_zone", 103 | "settings": { "use_transform": true } 104 | } 105 | ] 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/GeometryNodeTree_4_4.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 4.4, compared to the previous version. 2 | // Lots of misc nodes added, plus a new zone. 3 | { 4 | "blender_version": [4, 4, 0], 5 | "removals": {"layout": { 6 | "right": [["GIZMO"]] 7 | }}, 8 | "additions": { 9 | "layout": { 10 | "left": [[], ["GIZMO"]] 11 | }, 12 | "categories": { 13 | "INPUT": { 14 | "nodes": [ 15 | { "identifier": "GeometryNodeInputObject", "after_node": "GeometryNodeInputMaterial" }, 16 | { "identifier": "GeometryNodeInputCollection", "before_node": "FunctionNodeInputColor" } 17 | ] 18 | }, 19 | "TEXT": { 20 | "nodes": [ 21 | { "identifier": "FunctionNodeFindInString", "before_node": "GeometryNodeStringJoin" } 22 | ] 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/ShaderNodeTree_1_0.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "layout": { 3 | "left": [ 4 | ["TEXTURE", "LAYOUT"], 5 | ["CONVERTOR", "SCRIPT"] 6 | ], 7 | "right": [["INPUT"], ["SHADER"]], 8 | "top": [["VECTOR"]], 9 | "bottom": [["COLOR"]] 10 | }, 11 | "poll_types": { 12 | "is_world": [ 13 | { 14 | "context_path": "space_data.shader_type", 15 | "operand": "equals", 16 | "value": "WORLD" 17 | } 18 | ], 19 | "is_not_world": [ 20 | { 21 | "context_path": "space_data.shader_type", 22 | "operand": "not_equals", 23 | "value": "WORLD" 24 | } 25 | ] 26 | }, 27 | "categories": { 28 | "INPUT": { 29 | "label": "Input", 30 | "color": "input", 31 | "nodes": [ 32 | { "identifier": "ShaderNodeAmbientOcclusion" }, 33 | { "identifier": "ShaderNodeAttribute" }, 34 | { "identifier": "ShaderNodeBackground", "poll_type": "is_world" }, 35 | { "identifier": "ShaderNodeCameraData" }, 36 | { "identifier": "ShaderNodeVertexColor" }, 37 | { "identifier": "ShaderNodeHairInfo" }, 38 | { "identifier": "ShaderNodeFresnel" }, 39 | { "identifier": "ShaderNodeNewGeometry" }, 40 | { "identifier": "ShaderNodeLayerWeight" }, 41 | { "identifier": "ShaderNodeLightPath" }, 42 | { "identifier": "ShaderNodeObjectInfo" }, 43 | { "identifier": "ShaderNodePointInfo" }, 44 | { "identifier": "ShaderNodeRGB" }, 45 | { "identifier": "ShaderNodeTangent" }, 46 | { "identifier": "ShaderNodeTexCoord" }, 47 | { 48 | "identifier": "ShaderNodeUVAlongStroke", 49 | "poll_conditions": [ 50 | { 51 | "context_path": "space_data.shader_type", 52 | "operand": "equals", 53 | "value": "LINESTYLE" 54 | } 55 | ] 56 | }, 57 | { "identifier": "ShaderNodeUVMap" }, 58 | { "identifier": "ShaderNodeValue" }, 59 | { "identifier": "ShaderNodeVolumeInfo" }, 60 | { "identifier": "ShaderNodeWireframe" }, 61 | { "separator": true, "label": "Cycles" }, 62 | { "identifier": "ShaderNodeBevel" }, 63 | { "identifier": "ShaderNodeParticleInfo" } 64 | ] 65 | }, 66 | "OUTPUT": { 67 | "label": "Output", 68 | "color": "output", 69 | "nodes": [ 70 | { "identifier": "ShaderNodeOutputAOV" }, 71 | { "identifier": "ShaderNodeOutputLight" }, 72 | { "identifier": "ShaderNodeOutputMaterial" } 73 | ] 74 | }, 75 | "SHADER": { 76 | "label": "Shader", 77 | "color": "shader", 78 | "nodes": [ 79 | // A lot of shader nodes are not available in the world context 80 | { "identifier": "ShaderNodeAddShader" }, 81 | { "identifier": "ShaderNodeBsdfDiffuse", "poll_type": "is_not_world" }, 82 | { "identifier": "ShaderNodeEmission" }, 83 | { "identifier": "ShaderNodeBsdfGlass", "poll_type": "is_not_world" }, 84 | { "identifier": "ShaderNodeBsdfGlossy", "poll_type": "is_not_world" }, 85 | { "identifier": "ShaderNodeHoldout", "poll_type": "is_not_world" }, 86 | { "identifier": "ShaderNodeMixShader" }, 87 | { "identifier": "ShaderNodeBsdfPrincipled", "poll_type": "is_not_world" }, 88 | { "identifier": "ShaderNodeVolumePrincipled" }, 89 | { "identifier": "ShaderNodeBsdfRefraction", "poll_type": "is_not_world" }, 90 | { "identifier": "ShaderNodeSubsurfaceScattering", "poll_type": "is_not_world" }, 91 | { "identifier": "ShaderNodeBsdfTranslucent", "poll_type": "is_not_world" }, 92 | { "identifier": "ShaderNodeBsdfTransparent", "poll_type": "is_not_world" }, 93 | { "identifier": "ShaderNodeVolumeAbsorption" }, 94 | { "identifier": "ShaderNodeVolumeScatter" }, 95 | { 96 | "separator": true, 97 | "label": "Cycles", 98 | "poll_type": "is_not_world" 99 | }, 100 | { "identifier": "ShaderNodeBsdfAnisotropic", "poll_type": "is_not_world" }, 101 | { "identifier": "ShaderNodeBsdfToon", "poll_type": "is_not_world" }, 102 | { "identifier": "ShaderNodeBsdfHair", "poll_type": "is_not_world" }, 103 | { "identifier": "ShaderNodeBsdfHairPrincipled", "poll_type": "is_not_world" }, 104 | { "identifier": "ShaderNodeBsdfVelvet", "poll_type": "is_not_world" }, 105 | { "separator": true, "label": "Eevee", "poll_type": "is_not_world" }, 106 | { "identifier": "ShaderNodeEeveeSpecular", "poll_type": "is_not_world" } 107 | ] 108 | }, 109 | "TEXTURE": { 110 | "label": "Texture", 111 | "color": "texture", 112 | "nodes": [ 113 | { "identifier": "ShaderNodeTexBrick" }, 114 | { "identifier": "ShaderNodeTexChecker" }, 115 | { "identifier": "ShaderNodeTexEnvironment" }, 116 | { "identifier": "ShaderNodeTexGradient" }, 117 | { "identifier": "ShaderNodeTexImage" }, 118 | { "identifier": "ShaderNodeTexMagic" }, 119 | { "identifier": "ShaderNodeTexMusgrave" }, 120 | { "identifier": "ShaderNodeTexNoise" }, 121 | { "identifier": "ShaderNodeTexSky" }, 122 | { "identifier": "ShaderNodeTexVoronoi" }, 123 | { "identifier": "ShaderNodeTexWave" }, 124 | { "identifier": "ShaderNodeTexWhiteNoise" }, 125 | { "separator": true, "label": "Cycles" }, 126 | { "identifier": "ShaderNodeTexIES" }, 127 | { "identifier": "ShaderNodeTexPointDensity" } 128 | ] 129 | }, 130 | "COLOR": { 131 | "label": "Color", 132 | "color": "color", 133 | "nodes": [ 134 | { "identifier": "ShaderNodeBrightContrast" }, 135 | { "identifier": "ShaderNodeGamma" }, 136 | { "identifier": "ShaderNodeHueSaturation" }, 137 | { "identifier": "ShaderNodeInvert" }, 138 | { 139 | "identifier": "ShaderNodeMix", 140 | "settings": { "data_type": "RGBA" }, 141 | "color": "color", 142 | "variants": { 143 | "Add": { "blend_type": "ADD" }, 144 | "Subtract": { "blend_type": "SUBTRACT" }, 145 | "Multiply": { "blend_type": "MULTIPLY" }, 146 | "Divide": { "blend_type": "DIVIDE" }, 147 | "Overlay": { "blend_type": "OVERLAY" }, 148 | "Linear Light": { "blend_type": "LINEAR_LIGHT" } 149 | } 150 | }, 151 | { "identifier": "ShaderNodeRGBCurve" }, 152 | { "separator": true, "label": "Cycles" }, 153 | { "identifier": "ShaderNodeLightFalloff" } 154 | ] 155 | }, 156 | "VECTOR": { 157 | "label": "Vector", 158 | "color": "vector", 159 | "nodes": [ 160 | { "identifier": "ShaderNodeBump" }, 161 | { "identifier": "ShaderNodeDisplacement" }, 162 | { "identifier": "ShaderNodeMapping" }, 163 | { 164 | "identifier": "ShaderNodeMix", 165 | "settings": { "data_type": "VECTOR" } 166 | }, 167 | { "identifier": "ShaderNodeNormal" }, 168 | { "identifier": "ShaderNodeNormalMap" }, 169 | { "identifier": "ShaderNodeVectorCurve" }, 170 | { "identifier": "ShaderNodeVectorDisplacement" }, 171 | { "identifier": "ShaderNodeVectorRotate" }, 172 | { "identifier": "ShaderNodeVectorTransform" }, 173 | { 174 | "identifier": "ShaderNodeVectorMath", 175 | "variants": { 176 | "Negate": { 177 | "operation": "SCALE", 178 | "show_options": false, 179 | "label": "Negate", 180 | "inputs[3].hide": true, 181 | "inputs[3].default_value": -1 182 | }, 183 | "One Minus": { 184 | "operation": "SUBTRACT", 185 | "show_options": false, 186 | "label": "One Minus", 187 | "inputs[0].hide": true, 188 | "inputs[0].default_value": [1, 1, 1] 189 | }, 190 | "One Over": { 191 | "operation": "DIVIDE", 192 | "show_options": false, 193 | "label": "One Over", 194 | "inputs[0].hide": true, 195 | "inputs[0].default_value": [1, 1, 1] 196 | }, 197 | "separator": true, 198 | "Multiply": { "operation": "MULTIPLY" }, 199 | "Divide": { "operation": "DIVIDE" }, 200 | "Subtract": { "operation": "SUBTRACT" }, 201 | "Scale": { "operation": "SCALE" }, 202 | "Dot Product": { "operation": "DOT_PRODUCT" }, 203 | "Cross Product": { "operation": "CROSS_PRODUCT" }, 204 | "Normalize": { "operation": "NORMALIZE" }, 205 | "Absolute": { "operation": "ABSOLUTE" }, 206 | "Distance": { "operation": "DISTANCE" }, 207 | "Length": { "operation": "LENGTH" } 208 | } 209 | } 210 | ] 211 | }, 212 | "CONVERTOR": { 213 | "label": "Converter", 214 | "color": "converter", 215 | "nodes": [ 216 | { "identifier": "ShaderNodeBlackbody" }, 217 | { "identifier": "ShaderNodeClamp" }, 218 | { "identifier": "ShaderNodeValToRGB", "color": "converter" }, 219 | { "identifier": "ShaderNodeCombineColor" }, 220 | { "identifier": "ShaderNodeCombineXYZ", "color": "converter" }, 221 | { "identifier": "ShaderNodeFloatCurve" }, 222 | { "identifier": "ShaderNodeMapRange" }, 223 | { 224 | "identifier": "ShaderNodeMath", 225 | "variants": { 226 | "Negate": { 227 | "operation": "MULTIPLY", 228 | "show_options": false, 229 | "label": "Negate", 230 | "inputs[1].hide": true, 231 | "inputs[1].default_value": -1 232 | }, 233 | "One Minus": { 234 | "operation": "SUBTRACT", 235 | "show_options": false, 236 | "label": "One Minus", 237 | "inputs[0].hide": true, 238 | "inputs[0].default_value": 1 239 | }, 240 | "One Over": { 241 | "operation": "DIVIDE", 242 | "show_options": false, 243 | "label": "One Over", 244 | "inputs[0].hide": true, 245 | "inputs[0].default_value": 1 246 | }, 247 | "separator": true, 248 | "Multiply": { "operation": "MULTIPLY" }, 249 | "Divide": { "operation": "DIVIDE" }, 250 | "Subtract": { "operation": "SUBTRACT" }, 251 | "Modulo": { "operation": "MODULO" }, 252 | "Absolute": { "operation": "ABSOLUTE" }, 253 | "Power": { "operation": "POWER" }, 254 | "Sine": { "operation": "SINE" }, 255 | "Cosine": { "operation": "COSINE" } 256 | } 257 | }, 258 | { "identifier": "ShaderNodeMix" }, 259 | { "identifier": "ShaderNodeRGBToBW" }, 260 | { "identifier": "ShaderNodeSeparateColor" }, 261 | { 262 | "identifier": "ShaderNodeSeparateXYZ", 263 | "color": "converter", 264 | "variants": { 265 | "X": { 266 | "outputs[1].hide": true, 267 | "outputs[2].hide": true, 268 | "label": "Get X" 269 | }, 270 | "Y": { 271 | "outputs[0].hide": true, 272 | "outputs[2].hide": true, 273 | "label": "Get Y" 274 | }, 275 | "Z": { 276 | "outputs[0].hide": true, 277 | "outputs[1].hide": true, 278 | "label": "Get Z" 279 | } 280 | } 281 | }, 282 | 283 | { "identifier": "ShaderNodeWavelength" }, 284 | { "separator": true, "label": "Eevee" }, 285 | { "identifier": "ShaderNodeShaderToRGB" } 286 | ] 287 | }, 288 | "SCRIPT": { 289 | "label": "Script", 290 | "color": "script", 291 | "nodes": [{ "identifier": "ShaderNodeScript" }] 292 | }, 293 | "LAYOUT": { 294 | "label": "Layout", 295 | "color": "layout", 296 | "nodes": [{ "identifier": "NodeFrame" }, { "identifier": "NodeReroute" }] 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/ShaderNodeTree_4_0.jsonc: -------------------------------------------------------------------------------- 1 | // This is a versioning file that only specifies the new nodes in 4.0, compared to the base file. 2 | 3 | // In this version, the glossy bsdf node was replaced by an improved version of the anisotropic bsdf. 4 | // However, while the display label is now "Glossy Bsdf", the identifier is still "ShaderNodeBsdfAnisotropic". 5 | // This just removes the old nodes and adds the anisotropic back in in the correct location 6 | { 7 | "blender_version": [4, 0, 0], 8 | "removals": { 9 | "categories": { 10 | "SHADER": { 11 | "nodes": [ 12 | { "identifier": "ShaderNodeBsdfVelvet" }, 13 | { "identifier": "ShaderNodeBsdfAnisotropic" }, 14 | { "identifier": "ShaderNodeBsdfGlossy" } 15 | ] 16 | } 17 | } 18 | }, 19 | "additions": { 20 | "categories": { 21 | "SHADER": { 22 | "nodes": [ 23 | { 24 | "identifier": "ShaderNodeBsdfAnisotropic", 25 | "after_node": "ShaderNodeBsdfGlass", 26 | "poll_type": "is_not_world" 27 | } 28 | ] 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/ShaderNodeTree_4_1.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "blender_version": [4, 1, 0], 3 | "removals": { 4 | "nodes": ["ShaderNodeTexMusgrave"] 5 | } 6 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/ShaderNodeTree_4_2.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "blender_version": [4, 2, 0], 3 | "additions": { 4 | "categories": { 5 | "SHADER": { 6 | "nodes": [ 7 | { "identifier": "ShaderNodeBsdfSheen", "before_node": "ShaderNodeSubsurfaceScattering", "poll_type": "is_not_world" } 8 | ] 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /node_pie/node_def_files/builtin/ShaderNodeTree_4_3.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "blender_version": [4, 3, 0], 3 | "additions": { 4 | "categories": { 5 | "TEXTURE": { 6 | "nodes": [{ "identifier": "ShaderNodeTexGabor", "before_node": "ShaderNodeTexGradient" }] 7 | }, 8 | "SHADER": { 9 | "nodes": [ 10 | { "identifier": "ShaderNodeBsdfMetallic", "before_node": "ShaderNodeMixShader", "poll_type": "is_not_world" }, 11 | { "identifier": "ShaderNodeBsdfRayPortal", "after_node": "ShaderNodeBsdfHairPrincipled", "poll_type": "is_not_world" } 12 | ] 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /node_pie/node_def_files/node_def_base.jsonc: -------------------------------------------------------------------------------- 1 | // For more info about how to use this file, use the "Open Example File" operator in the right click menu of the node editor 2 | // You'll need Node Pie developer extras to be enabled in the preferences to see it. 3 | 4 | { 5 | "layout": { 6 | "left": [["CATEGORY_NAME"]], 7 | "right": [], 8 | "top": [], 9 | "bottom": [] 10 | }, 11 | "categories": { 12 | "CATEGORY_NAME": { 13 | "label": "Category name", 14 | "color": "converter", 15 | "nodes": [ 16 | // Use the "Copy nodes as json" operator for this 17 | { "identifier": "NodeIdentifier" }, 18 | { "separator": true } 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /node_pie/node_def_files/node_def_example.jsonc: -------------------------------------------------------------------------------- 1 | // This example shows how to structure a node pie definition file (This one is for the Serpens addon) 2 | // For the best experience it is recommended to edit this using a code editor 3 | // with syntax highlighting such as vscode or notepad++. 4 | 5 | // The definition file will be reloaded automatically each time the pie men is called, 6 | // just make sure to save the file after making a change for it to be shown. 7 | { 8 | // This defines how each category is placed in the pie menu 9 | // The menu is divided into four sections (left, right, etc.) 10 | // Each section then contains a list of columns (the first set of brackets) 11 | // Then each column contains a list of rows (the second set of brackets) 12 | // Each row is just defined by the category identifier (defined below) 13 | // The layout below will create a pie menu with one column, containing two rows, on the left hand side 14 | "layout": { 15 | "left": [["DEBUG", "DEBUG"]], 16 | "right": [], 17 | "top": [], 18 | "bottom": [] 19 | }, 20 | // These are preset poll conditions that get prepended to the conditions of any items that they are applied to 21 | "poll_types": { 22 | "poll_type_name": [ 23 | { 24 | // condition 1 etc. 25 | } 26 | ] 27 | }, 28 | // Categories are the different visual groups of nodes in the node tree. 29 | // It doesn't matter what order they are defined in here, since they are laid out above 30 | "categories": { 31 | // The identifier of the category doesn't matter, just as long as it is unique 32 | "DEBUG": { 33 | "label": "Debug", // The name that is displayed at the top of the category 34 | // The color to draw at the side of the node. 35 | // This is defined by the name of that node category color in the Blender theme preferences. 36 | // The possible values, and their default colors are: 37 | // "converter": light blue 38 | // "color": yellow 39 | // "group": dark green 40 | // "layout": grey 41 | // "matte": rust 42 | // "distor": grey-blue This should be distort, but there is a typo in the blender code 43 | // "input": red 44 | // "output": dark red 45 | // "filter": deep purple 46 | // "vector": light purple 47 | // "texture": orange 48 | // "shader": green 49 | // "script": dark greeny blue 50 | // "geometry": light green 51 | // "attribute": dark blue 52 | "color": "converter", 53 | // A list of the nodes in this category 54 | "nodes": [ 55 | // IMPORTANT: You can auto generate this from the selected nodes with the 'Copy nodes as json' operator 56 | { 57 | // The internal blender name of the node 58 | "identifier": "SN_TriggerNode", 59 | // The display name of the node. This is optional, only needs to be used if the auto generated one is wrong 60 | "label": "Trigger", 61 | // Optional, only needed if the color is different from the category color 62 | "color": "input", 63 | // Variants show up as a sub menu at the right of the node button, and allow for 64 | // having multple preset configurations for a node. 65 | // Each variant then has a list of setting names the corresponding values that they should be set to. 66 | // For a good reference on what's possible, have a look at the geometry nodes math nodes variants in the built in definition files. 67 | "variants": { 68 | // This defines a variant called "My variant", that has the setting "node_setting" set to the string "node_value" 69 | // This is equivalent to writing node.node_setting = "node_value" in python. 70 | "My variant": { "node_setting": "node_value" }, 71 | // This sets the value of the first node input to 5 72 | "My other variant": { "inputs[0].default_value": 5 } 73 | }, 74 | // Apply preset poll conditions 75 | "poll_type": "poll_type_name", 76 | // Poll conditions allow node items to only be displayed conditionally, depending on the context. 77 | // They also work on entire categories. 78 | "poll_conditions": [ 79 | { 80 | // This is the python data path starting from "bpy.context." 81 | "context_path": "object.name", 82 | // The logical operator to apply to the value. One of: 83 | // "equals": check whether the returned value equals the provided value 84 | // "in": Check whether the returned value is in the provided value 85 | // "bool": cast the returned value to a boolean. 86 | "operand": "equals", 87 | // The value to check the returned value from the context_path against 88 | "value": "Cube" 89 | } 90 | ] 91 | }, 92 | // Define a separator like this, the label is optional, is only show if subcategory labels are enabled in the preferences 93 | { "separator": true, "label": "My separator" }, 94 | { "identifier": "SN_TimestampNode" } 95 | ] 96 | } 97 | }, 98 | // This will add the imported files on top of the config specified in this file 99 | // There can be multiple given in the list to be added one on top of each other. 100 | // The name should just be the name of the definition file without the file extension. 101 | "imports": ["other_def_file"] 102 | } 103 | -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/CompositorNodeTree_sockets_4_1.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It can be auto generated using the 'generate socket types file' operator 4 | { 5 | "bl_version": [ 6 | 4, 7 | 1 8 | ], 9 | "nodes": { 10 | "CompositorNodeBokehImage": { 11 | "inputs": [], 12 | "outputs": [ 13 | "NodeSocketColor" 14 | ] 15 | }, 16 | "CompositorNodeImage": { 17 | "inputs": [], 18 | "outputs": [ 19 | "NodeSocketFloat", 20 | "NodeSocketColor" 21 | ] 22 | }, 23 | "CompositorNodeMask": { 24 | "inputs": [], 25 | "outputs": [ 26 | "NodeSocketFloat" 27 | ] 28 | }, 29 | "CompositorNodeMovieClip": { 30 | "inputs": [], 31 | "outputs": [ 32 | "NodeSocketFloat", 33 | "NodeSocketColor" 34 | ] 35 | }, 36 | "CompositorNodeRLayers": { 37 | "inputs": [], 38 | "outputs": [ 39 | "NodeSocketFloat", 40 | "NodeSocketColor", 41 | "NodeSocketVector" 42 | ] 43 | }, 44 | "CompositorNodeRGB": { 45 | "inputs": [], 46 | "outputs": [ 47 | "NodeSocketColor" 48 | ] 49 | }, 50 | "CompositorNodeSceneTime": { 51 | "inputs": [], 52 | "outputs": [ 53 | "NodeSocketFloat" 54 | ] 55 | }, 56 | "CompositorNodeTexture": { 57 | "inputs": [ 58 | "NodeSocketVectorXYZ", 59 | "NodeSocketVectorTranslation" 60 | ], 61 | "outputs": [ 62 | "NodeSocketFloat", 63 | "NodeSocketColor" 64 | ] 65 | }, 66 | "CompositorNodeTime": { 67 | "inputs": [], 68 | "outputs": [ 69 | "NodeSocketFloat" 70 | ] 71 | }, 72 | "CompositorNodeTrackPos": { 73 | "inputs": [], 74 | "outputs": [ 75 | "NodeSocketVectorVelocity", 76 | "NodeSocketFloat" 77 | ] 78 | }, 79 | "CompositorNodeValue": { 80 | "inputs": [], 81 | "outputs": [ 82 | "NodeSocketFloat" 83 | ] 84 | }, 85 | "CompositorNodeComposite": { 86 | "inputs": [ 87 | "NodeSocketFloat", 88 | "NodeSocketColor" 89 | ], 90 | "outputs": [] 91 | }, 92 | "CompositorNodeOutputFile": { 93 | "inputs": [ 94 | "NodeSocketColor" 95 | ], 96 | "outputs": [] 97 | }, 98 | "CompositorNodeLevels": { 99 | "inputs": [ 100 | "NodeSocketColor" 101 | ], 102 | "outputs": [ 103 | "NodeSocketFloat" 104 | ] 105 | }, 106 | "CompositorNodeViewer": { 107 | "inputs": [ 108 | "NodeSocketFloat", 109 | "NodeSocketColor" 110 | ], 111 | "outputs": [] 112 | }, 113 | "CompositorNodeAlphaOver": { 114 | "inputs": [ 115 | "NodeSocketFloatFactor", 116 | "NodeSocketColor" 117 | ], 118 | "outputs": [ 119 | "NodeSocketColor" 120 | ] 121 | }, 122 | "CompositorNodeBrightContrast": { 123 | "inputs": [ 124 | "NodeSocketFloat", 125 | "NodeSocketColor" 126 | ], 127 | "outputs": [ 128 | "NodeSocketColor" 129 | ] 130 | }, 131 | "CompositorNodeColorBalance": { 132 | "inputs": [ 133 | "NodeSocketFloatFactor", 134 | "NodeSocketColor" 135 | ], 136 | "outputs": [ 137 | "NodeSocketColor" 138 | ] 139 | }, 140 | "CompositorNodeColorCorrection": { 141 | "inputs": [ 142 | "NodeSocketFloat", 143 | "NodeSocketColor" 144 | ], 145 | "outputs": [ 146 | "NodeSocketColor" 147 | ] 148 | }, 149 | "CompositorNodeExposure": { 150 | "inputs": [ 151 | "NodeSocketFloat", 152 | "NodeSocketColor" 153 | ], 154 | "outputs": [ 155 | "NodeSocketColor" 156 | ] 157 | }, 158 | "CompositorNodeGamma": { 159 | "inputs": [ 160 | "NodeSocketFloatUnsigned", 161 | "NodeSocketColor" 162 | ], 163 | "outputs": [ 164 | "NodeSocketColor" 165 | ] 166 | }, 167 | "CompositorNodeHueCorrect": { 168 | "inputs": [ 169 | "NodeSocketFloatFactor", 170 | "NodeSocketColor" 171 | ], 172 | "outputs": [ 173 | "NodeSocketColor" 174 | ] 175 | }, 176 | "CompositorNodeHueSat": { 177 | "inputs": [ 178 | "NodeSocketFloatFactor", 179 | "NodeSocketColor" 180 | ], 181 | "outputs": [ 182 | "NodeSocketColor" 183 | ] 184 | }, 185 | "CompositorNodeInvert": { 186 | "inputs": [ 187 | "NodeSocketFloatFactor", 188 | "NodeSocketColor" 189 | ], 190 | "outputs": [ 191 | "NodeSocketColor" 192 | ] 193 | }, 194 | "CompositorNodeMixRGB": { 195 | "inputs": [ 196 | "NodeSocketFloatFactor", 197 | "NodeSocketColor" 198 | ], 199 | "outputs": [ 200 | "NodeSocketColor" 201 | ] 202 | }, 203 | "CompositorNodePosterize": { 204 | "inputs": [ 205 | "NodeSocketFloat", 206 | "NodeSocketColor" 207 | ], 208 | "outputs": [ 209 | "NodeSocketColor" 210 | ] 211 | }, 212 | "CompositorNodeCurveRGB": { 213 | "inputs": [ 214 | "NodeSocketFloatFactor", 215 | "NodeSocketColor" 216 | ], 217 | "outputs": [ 218 | "NodeSocketColor" 219 | ] 220 | }, 221 | "CompositorNodeTonemap": { 222 | "inputs": [ 223 | "NodeSocketColor" 224 | ], 225 | "outputs": [ 226 | "NodeSocketColor" 227 | ] 228 | }, 229 | "CompositorNodeZcombine": { 230 | "inputs": [ 231 | "NodeSocketFloat", 232 | "NodeSocketColor" 233 | ], 234 | "outputs": [ 235 | "NodeSocketFloat", 236 | "NodeSocketColor" 237 | ] 238 | }, 239 | "CompositorNodePremulKey": { 240 | "inputs": [ 241 | "NodeSocketColor" 242 | ], 243 | "outputs": [ 244 | "NodeSocketColor" 245 | ] 246 | }, 247 | "CompositorNodeConvertColorSpace": { 248 | "inputs": [ 249 | "NodeSocketColor" 250 | ], 251 | "outputs": [ 252 | "NodeSocketColor" 253 | ] 254 | }, 255 | "CompositorNodeValToRGB": { 256 | "inputs": [ 257 | "NodeSocketFloatFactor" 258 | ], 259 | "outputs": [ 260 | "NodeSocketFloat", 261 | "NodeSocketColor" 262 | ] 263 | }, 264 | "CompositorNodeCombineColor": { 265 | "inputs": [ 266 | "NodeSocketFloatFactor" 267 | ], 268 | "outputs": [ 269 | "NodeSocketColor" 270 | ] 271 | }, 272 | "CompositorNodeCombineXYZ": { 273 | "inputs": [ 274 | "NodeSocketFloat" 275 | ], 276 | "outputs": [ 277 | "NodeSocketVector" 278 | ] 279 | }, 280 | "CompositorNodeIDMask": { 281 | "inputs": [ 282 | "NodeSocketFloat" 283 | ], 284 | "outputs": [ 285 | "NodeSocketFloat" 286 | ] 287 | }, 288 | "CompositorNodeMath": { 289 | "inputs": [ 290 | "NodeSocketFloat" 291 | ], 292 | "outputs": [ 293 | "NodeSocketFloat" 294 | ] 295 | }, 296 | "CompositorNodeRGBToBW": { 297 | "inputs": [ 298 | "NodeSocketColor" 299 | ], 300 | "outputs": [ 301 | "NodeSocketFloat" 302 | ] 303 | }, 304 | "CompositorNodeSeparateColor": { 305 | "inputs": [ 306 | "NodeSocketColor" 307 | ], 308 | "outputs": [ 309 | "NodeSocketFloat" 310 | ] 311 | }, 312 | "CompositorNodeSeparateXYZ": { 313 | "inputs": [ 314 | "NodeSocketVector" 315 | ], 316 | "outputs": [ 317 | "NodeSocketFloat" 318 | ] 319 | }, 320 | "CompositorNodeSetAlpha": { 321 | "inputs": [ 322 | "NodeSocketFloat", 323 | "NodeSocketColor" 324 | ], 325 | "outputs": [ 326 | "NodeSocketColor" 327 | ] 328 | }, 329 | "CompositorNodeSwitch": { 330 | "inputs": [ 331 | "NodeSocketColor" 332 | ], 333 | "outputs": [ 334 | "NodeSocketColor" 335 | ] 336 | }, 337 | "CompositorNodeSwitchView": { 338 | "inputs": [ 339 | "NodeSocketColor" 340 | ], 341 | "outputs": [ 342 | "NodeSocketColor" 343 | ] 344 | }, 345 | "CompositorNodeAntiAliasing": { 346 | "inputs": [ 347 | "NodeSocketColor" 348 | ], 349 | "outputs": [ 350 | "NodeSocketColor" 351 | ] 352 | }, 353 | "CompositorNodeBilateralblur": { 354 | "inputs": [ 355 | "NodeSocketColor" 356 | ], 357 | "outputs": [ 358 | "NodeSocketColor" 359 | ] 360 | }, 361 | "CompositorNodeBlur": { 362 | "inputs": [ 363 | "NodeSocketFloat", 364 | "NodeSocketColor" 365 | ], 366 | "outputs": [ 367 | "NodeSocketColor" 368 | ] 369 | }, 370 | "CompositorNodeBokehBlur": { 371 | "inputs": [ 372 | "NodeSocketFloat", 373 | "NodeSocketColor" 374 | ], 375 | "outputs": [ 376 | "NodeSocketColor" 377 | ] 378 | }, 379 | "CompositorNodeDefocus": { 380 | "inputs": [ 381 | "NodeSocketFloat", 382 | "NodeSocketColor" 383 | ], 384 | "outputs": [ 385 | "NodeSocketColor" 386 | ] 387 | }, 388 | "CompositorNodeDenoise": { 389 | "inputs": [ 390 | "NodeSocketColor", 391 | "NodeSocketVector" 392 | ], 393 | "outputs": [ 394 | "NodeSocketColor" 395 | ] 396 | }, 397 | "CompositorNodeDespeckle": { 398 | "inputs": [ 399 | "NodeSocketFloatFactor", 400 | "NodeSocketColor" 401 | ], 402 | "outputs": [ 403 | "NodeSocketColor" 404 | ] 405 | }, 406 | "CompositorNodeDilateErode": { 407 | "inputs": [ 408 | "NodeSocketFloat" 409 | ], 410 | "outputs": [ 411 | "NodeSocketFloat" 412 | ] 413 | }, 414 | "CompositorNodeDBlur": { 415 | "inputs": [ 416 | "NodeSocketColor" 417 | ], 418 | "outputs": [ 419 | "NodeSocketColor" 420 | ] 421 | }, 422 | "CompositorNodeFilter": { 423 | "inputs": [ 424 | "NodeSocketFloatFactor", 425 | "NodeSocketColor" 426 | ], 427 | "outputs": [ 428 | "NodeSocketColor" 429 | ] 430 | }, 431 | "CompositorNodeGlare": { 432 | "inputs": [ 433 | "NodeSocketColor" 434 | ], 435 | "outputs": [ 436 | "NodeSocketColor" 437 | ] 438 | }, 439 | "CompositorNodeInpaint": { 440 | "inputs": [ 441 | "NodeSocketColor" 442 | ], 443 | "outputs": [ 444 | "NodeSocketColor" 445 | ] 446 | }, 447 | "CompositorNodeKuwahara": { 448 | "inputs": [ 449 | "NodeSocketFloat", 450 | "NodeSocketColor" 451 | ], 452 | "outputs": [ 453 | "NodeSocketColor" 454 | ] 455 | }, 456 | "CompositorNodePixelate": { 457 | "inputs": [ 458 | "NodeSocketColor" 459 | ], 460 | "outputs": [ 461 | "NodeSocketColor" 462 | ] 463 | }, 464 | "CompositorNodeSunBeams": { 465 | "inputs": [ 466 | "NodeSocketColor" 467 | ], 468 | "outputs": [ 469 | "NodeSocketColor" 470 | ] 471 | }, 472 | "CompositorNodeVecBlur": { 473 | "inputs": [ 474 | "NodeSocketVectorVelocity", 475 | "NodeSocketFloat", 476 | "NodeSocketColor" 477 | ], 478 | "outputs": [ 479 | "NodeSocketColor" 480 | ] 481 | }, 482 | "CompositorNodeMapRange": { 483 | "inputs": [ 484 | "NodeSocketFloat" 485 | ], 486 | "outputs": [ 487 | "NodeSocketFloat" 488 | ] 489 | }, 490 | "CompositorNodeMapValue": { 491 | "inputs": [ 492 | "NodeSocketFloat" 493 | ], 494 | "outputs": [ 495 | "NodeSocketFloat" 496 | ] 497 | }, 498 | "CompositorNodeNormal": { 499 | "inputs": [ 500 | "NodeSocketVectorDirection" 501 | ], 502 | "outputs": [ 503 | "NodeSocketFloat", 504 | "NodeSocketVectorDirection" 505 | ] 506 | }, 507 | "CompositorNodeNormalize": { 508 | "inputs": [ 509 | "NodeSocketFloat" 510 | ], 511 | "outputs": [ 512 | "NodeSocketFloat" 513 | ] 514 | }, 515 | "CompositorNodeCurveVec": { 516 | "inputs": [ 517 | "NodeSocketVector" 518 | ], 519 | "outputs": [ 520 | "NodeSocketVector" 521 | ] 522 | }, 523 | "CompositorNodeBoxMask": { 524 | "inputs": [ 525 | "NodeSocketFloat" 526 | ], 527 | "outputs": [ 528 | "NodeSocketFloat" 529 | ] 530 | }, 531 | "CompositorNodeChannelMatte": { 532 | "inputs": [ 533 | "NodeSocketColor" 534 | ], 535 | "outputs": [ 536 | "NodeSocketFloat", 537 | "NodeSocketColor" 538 | ] 539 | }, 540 | "CompositorNodeChromaMatte": { 541 | "inputs": [ 542 | "NodeSocketColor" 543 | ], 544 | "outputs": [ 545 | "NodeSocketFloat", 546 | "NodeSocketColor" 547 | ] 548 | }, 549 | "CompositorNodeColorMatte": { 550 | "inputs": [ 551 | "NodeSocketColor" 552 | ], 553 | "outputs": [ 554 | "NodeSocketFloat", 555 | "NodeSocketColor" 556 | ] 557 | }, 558 | "CompositorNodeColorSpill": { 559 | "inputs": [ 560 | "NodeSocketFloatFactor", 561 | "NodeSocketColor" 562 | ], 563 | "outputs": [ 564 | "NodeSocketColor" 565 | ] 566 | }, 567 | "CompositorNodeCryptomatteV2": { 568 | "inputs": [ 569 | "NodeSocketColor" 570 | ], 571 | "outputs": [ 572 | "NodeSocketFloat", 573 | "NodeSocketColor" 574 | ] 575 | }, 576 | "CompositorNodeCryptomatte": { 577 | "inputs": [ 578 | "NodeSocketColor" 579 | ], 580 | "outputs": [ 581 | "NodeSocketFloat", 582 | "NodeSocketColor" 583 | ] 584 | }, 585 | "CompositorNodeDiffMatte": { 586 | "inputs": [ 587 | "NodeSocketColor" 588 | ], 589 | "outputs": [ 590 | "NodeSocketFloat", 591 | "NodeSocketColor" 592 | ] 593 | }, 594 | "CompositorNodeDistanceMatte": { 595 | "inputs": [ 596 | "NodeSocketColor" 597 | ], 598 | "outputs": [ 599 | "NodeSocketFloat", 600 | "NodeSocketColor" 601 | ] 602 | }, 603 | "CompositorNodeDoubleEdgeMask": { 604 | "inputs": [ 605 | "NodeSocketFloat" 606 | ], 607 | "outputs": [ 608 | "NodeSocketFloat" 609 | ] 610 | }, 611 | "CompositorNodeEllipseMask": { 612 | "inputs": [ 613 | "NodeSocketFloat" 614 | ], 615 | "outputs": [ 616 | "NodeSocketFloat" 617 | ] 618 | }, 619 | "CompositorNodeKeying": { 620 | "inputs": [ 621 | "NodeSocketFloat", 622 | "NodeSocketColor" 623 | ], 624 | "outputs": [ 625 | "NodeSocketFloat", 626 | "NodeSocketColor" 627 | ] 628 | }, 629 | "CompositorNodeKeyingScreen": { 630 | "inputs": [], 631 | "outputs": [ 632 | "NodeSocketColor" 633 | ] 634 | }, 635 | "CompositorNodeLumaMatte": { 636 | "inputs": [ 637 | "NodeSocketColor" 638 | ], 639 | "outputs": [ 640 | "NodeSocketFloat", 641 | "NodeSocketColor" 642 | ] 643 | }, 644 | "CompositorNodeCornerPin": { 645 | "inputs": [ 646 | "NodeSocketColor", 647 | "NodeSocketVector" 648 | ], 649 | "outputs": [ 650 | "NodeSocketFloat", 651 | "NodeSocketColor" 652 | ] 653 | }, 654 | "CompositorNodeCrop": { 655 | "inputs": [ 656 | "NodeSocketColor" 657 | ], 658 | "outputs": [ 659 | "NodeSocketColor" 660 | ] 661 | }, 662 | "CompositorNodeDisplace": { 663 | "inputs": [ 664 | "NodeSocketFloat", 665 | "NodeSocketColor", 666 | "NodeSocketVectorTranslation" 667 | ], 668 | "outputs": [ 669 | "NodeSocketColor" 670 | ] 671 | }, 672 | "CompositorNodeFlip": { 673 | "inputs": [ 674 | "NodeSocketColor" 675 | ], 676 | "outputs": [ 677 | "NodeSocketColor" 678 | ] 679 | }, 680 | "CompositorNodeLensdist": { 681 | "inputs": [ 682 | "NodeSocketFloat", 683 | "NodeSocketColor" 684 | ], 685 | "outputs": [ 686 | "NodeSocketColor" 687 | ] 688 | }, 689 | "CompositorNodeMapUV": { 690 | "inputs": [ 691 | "NodeSocketColor", 692 | "NodeSocketVector" 693 | ], 694 | "outputs": [ 695 | "NodeSocketColor" 696 | ] 697 | }, 698 | "CompositorNodeMovieDistortion": { 699 | "inputs": [ 700 | "NodeSocketColor" 701 | ], 702 | "outputs": [ 703 | "NodeSocketColor" 704 | ] 705 | }, 706 | "CompositorNodePlaneTrackDeform": { 707 | "inputs": [ 708 | "NodeSocketColor" 709 | ], 710 | "outputs": [ 711 | "NodeSocketFloat", 712 | "NodeSocketColor" 713 | ] 714 | }, 715 | "CompositorNodeRotate": { 716 | "inputs": [ 717 | "NodeSocketColor", 718 | "NodeSocketFloatAngle" 719 | ], 720 | "outputs": [ 721 | "NodeSocketColor" 722 | ] 723 | }, 724 | "CompositorNodeScale": { 725 | "inputs": [ 726 | "NodeSocketFloat", 727 | "NodeSocketColor" 728 | ], 729 | "outputs": [ 730 | "NodeSocketColor" 731 | ] 732 | }, 733 | "CompositorNodeStabilize": { 734 | "inputs": [ 735 | "NodeSocketColor" 736 | ], 737 | "outputs": [ 738 | "NodeSocketColor" 739 | ] 740 | }, 741 | "CompositorNodeTransform": { 742 | "inputs": [ 743 | "NodeSocketFloat", 744 | "NodeSocketColor", 745 | "NodeSocketFloatAngle" 746 | ], 747 | "outputs": [ 748 | "NodeSocketColor" 749 | ] 750 | }, 751 | "CompositorNodeTranslate": { 752 | "inputs": [ 753 | "NodeSocketFloat", 754 | "NodeSocketColor" 755 | ], 756 | "outputs": [ 757 | "NodeSocketColor" 758 | ] 759 | }, 760 | "NodeFrame": { 761 | "inputs": [], 762 | "outputs": [] 763 | }, 764 | "NodeReroute": { 765 | "inputs": [ 766 | "NodeSocketColor" 767 | ], 768 | "outputs": [ 769 | "NodeSocketColor" 770 | ] 771 | } 772 | } 773 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/CompositorNodeTree_sockets_4_3.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It contains all of the new and updated nodes in this blender version. 4 | // It can be auto generated using the 'generate socket types file' operator 5 | { 6 | "bl_version": [ 7 | 4, 8 | 3 9 | ], 10 | "nodes": { 11 | "CompositorNodeSplit": { 12 | "inputs": [ 13 | "NodeSocketColor" 14 | ], 15 | "outputs": [ 16 | "NodeSocketColor" 17 | ] 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/GeometryNodeTree_sockets_4_2.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It contains all of the new and updated nodes in this blender version. 4 | // It can be auto generated using the 'generate socket types file' operator 5 | { 6 | "bl_version": [ 7 | 4, 8 | 2 9 | ], 10 | "nodes": { 11 | "GeometryNodeCaptureAttribute": { 12 | "inputs": [ 13 | "NodeSocketVirtual", 14 | "NodeSocketGeometry" 15 | ], 16 | "outputs": [ 17 | "NodeSocketVirtual", 18 | "NodeSocketGeometry" 19 | ] 20 | }, 21 | "GeometryNodeCurveToPoints": { 22 | "inputs": [ 23 | "NodeSocketGeometry", 24 | "NodeSocketInt", 25 | "NodeSocketFloatDistance" 26 | ], 27 | "outputs": [ 28 | "NodeSocketRotation", 29 | "NodeSocketGeometry", 30 | "NodeSocketVector" 31 | ] 32 | }, 33 | "GeometryNodeSetCurveNormal": { 34 | "inputs": [ 35 | "NodeSocketGeometry", 36 | "NodeSocketBool", 37 | "NodeSocketVectorXYZ" 38 | ], 39 | "outputs": [ 40 | "NodeSocketGeometry" 41 | ] 42 | }, 43 | "GeometryNodeTransform": { 44 | "inputs": [ 45 | "NodeSocketVectorTranslation", 46 | "NodeSocketGeometry", 47 | "NodeSocketVectorXYZ", 48 | "NodeSocketRotation", 49 | "NodeSocketMatrix" 50 | ], 51 | "outputs": [ 52 | "NodeSocketGeometry" 53 | ] 54 | }, 55 | "GeometryNodeProximity": { 56 | "inputs": [ 57 | "NodeSocketGeometry", 58 | "NodeSocketVector", 59 | "NodeSocketInt" 60 | ], 61 | "outputs": [ 62 | "NodeSocketVector", 63 | "NodeSocketFloat", 64 | "NodeSocketBool" 65 | ] 66 | }, 67 | "GeometryNodeToolMousePosition": { 68 | "inputs": [], 69 | "outputs": [ 70 | "NodeSocketInt" 71 | ] 72 | }, 73 | "GeometryNodeObjectInfo": { 74 | "inputs": [ 75 | "NodeSocketObject", 76 | "NodeSocketBool" 77 | ], 78 | "outputs": [ 79 | "NodeSocketRotation", 80 | "NodeSocketGeometry", 81 | "NodeSocketMatrix", 82 | "NodeSocketVector" 83 | ] 84 | }, 85 | "GeometryNodeViewportTransform": { 86 | "inputs": [], 87 | "outputs": [ 88 | "NodeSocketMatrix", 89 | "NodeSocketBool" 90 | ] 91 | }, 92 | "GeometryNodeRealizeInstances": { 93 | "inputs": [ 94 | "NodeSocketGeometry", 95 | "NodeSocketInt", 96 | "NodeSocketBool" 97 | ], 98 | "outputs": [ 99 | "NodeSocketGeometry" 100 | ] 101 | }, 102 | "GeometryNodeIndexSwitch": { 103 | "inputs": [ 104 | "NodeSocketObject", 105 | "NodeSocketCollection", 106 | "NodeSocketVirtual", 107 | "NodeSocketImage", 108 | "NodeSocketFloat", 109 | "NodeSocketColor", 110 | "NodeSocketMaterial", 111 | "NodeSocketInt", 112 | "NodeSocketGeometry", 113 | "NodeSocketMenu", 114 | "NodeSocketVector", 115 | "NodeSocketRotation", 116 | "NodeSocketString", 117 | "NodeSocketMatrix", 118 | "NodeSocketBool" 119 | ], 120 | "outputs": [ 121 | "NodeSocketObject", 122 | "NodeSocketCollection", 123 | "NodeSocketImage", 124 | "NodeSocketFloat", 125 | "NodeSocketColor", 126 | "NodeSocketMaterial", 127 | "NodeSocketGeometry", 128 | "NodeSocketMenu", 129 | "NodeSocketInt", 130 | "NodeSocketVector", 131 | "NodeSocketRotation", 132 | "NodeSocketString", 133 | "NodeSocketMatrix", 134 | "NodeSocketBool" 135 | ] 136 | }, 137 | "GeometryNodeSwitch": { 138 | "inputs": [ 139 | "NodeSocketObject", 140 | "NodeSocketCollection", 141 | "NodeSocketImage", 142 | "NodeSocketFloat", 143 | "NodeSocketColor", 144 | "NodeSocketMaterial", 145 | "NodeSocketGeometry", 146 | "NodeSocketMenu", 147 | "NodeSocketInt", 148 | "NodeSocketVector", 149 | "NodeSocketRotation", 150 | "NodeSocketString", 151 | "NodeSocketMatrix", 152 | "NodeSocketBool" 153 | ], 154 | "outputs": [ 155 | "NodeSocketObject", 156 | "NodeSocketCollection", 157 | "NodeSocketImage", 158 | "NodeSocketFloat", 159 | "NodeSocketColor", 160 | "NodeSocketMaterial", 161 | "NodeSocketGeometry", 162 | "NodeSocketMenu", 163 | "NodeSocketInt", 164 | "NodeSocketVector", 165 | "NodeSocketRotation", 166 | "NodeSocketString", 167 | "NodeSocketMatrix", 168 | "NodeSocketBool" 169 | ] 170 | }, 171 | "FunctionNodeAlignRotationToVector": { 172 | "inputs": [ 173 | "NodeSocketRotation", 174 | "NodeSocketFloatFactor", 175 | "NodeSocketVectorXYZ" 176 | ], 177 | "outputs": [ 178 | "NodeSocketRotation" 179 | ] 180 | }, 181 | "FunctionNodeCombineTransform": { 182 | "inputs": [ 183 | "NodeSocketVectorTranslation", 184 | "NodeSocketRotation", 185 | "NodeSocketVectorXYZ" 186 | ], 187 | "outputs": [ 188 | "NodeSocketMatrix" 189 | ] 190 | }, 191 | "FunctionNodeInvertMatrix": { 192 | "inputs": [ 193 | "NodeSocketMatrix" 194 | ], 195 | "outputs": [ 196 | "NodeSocketMatrix", 197 | "NodeSocketBool" 198 | ] 199 | }, 200 | "FunctionNodeMatrixMultiply": { 201 | "inputs": [ 202 | "NodeSocketMatrix" 203 | ], 204 | "outputs": [ 205 | "NodeSocketMatrix" 206 | ] 207 | }, 208 | "FunctionNodeProjectPoint": { 209 | "inputs": [ 210 | "NodeSocketMatrix", 211 | "NodeSocketVectorXYZ" 212 | ], 213 | "outputs": [ 214 | "NodeSocketVectorXYZ" 215 | ] 216 | }, 217 | "FunctionNodeSeparateTransform": { 218 | "inputs": [ 219 | "NodeSocketMatrix" 220 | ], 221 | "outputs": [ 222 | "NodeSocketVectorTranslation", 223 | "NodeSocketRotation", 224 | "NodeSocketVectorXYZ" 225 | ] 226 | }, 227 | "FunctionNodeTransformDirection": { 228 | "inputs": [ 229 | "NodeSocketMatrix", 230 | "NodeSocketVectorXYZ" 231 | ], 232 | "outputs": [ 233 | "NodeSocketVectorXYZ" 234 | ] 235 | }, 236 | "FunctionNodeTransformPoint": { 237 | "inputs": [ 238 | "NodeSocketMatrix", 239 | "NodeSocketVectorXYZ" 240 | ], 241 | "outputs": [ 242 | "NodeSocketVectorXYZ" 243 | ] 244 | }, 245 | "FunctionNodeTransposeMatrix": { 246 | "inputs": [ 247 | "NodeSocketMatrix" 248 | ], 249 | "outputs": [ 250 | "NodeSocketMatrix" 251 | ] 252 | } 253 | } 254 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/GeometryNodeTree_sockets_4_3.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It contains all of the new and updated nodes in this blender version. 4 | // It can be auto generated using the 'generate socket types file' operator 5 | { 6 | "bl_version": [ 7 | 4, 8 | 3 9 | ], 10 | "nodes": { 11 | "ShaderNodeBlackbody": { 12 | "inputs": [ 13 | "NodeSocketFloatColorTemperature" 14 | ], 15 | "outputs": [ 16 | "NodeSocketColor" 17 | ] 18 | }, 19 | "GeometryNodeCurvesToGreasePencil": { 20 | "inputs": [ 21 | "NodeSocketGeometry", 22 | "NodeSocketBool" 23 | ], 24 | "outputs": [ 25 | "NodeSocketGeometry" 26 | ] 27 | }, 28 | "GeometryNodeGreasePencilToCurves": { 29 | "inputs": [ 30 | "NodeSocketGeometry", 31 | "NodeSocketBool" 32 | ], 33 | "outputs": [ 34 | "NodeSocketGeometry" 35 | ] 36 | }, 37 | "GeometryNodeMergeLayers": { 38 | "inputs": [ 39 | "NodeSocketGeometry", 40 | "NodeSocketInt", 41 | "NodeSocketBool" 42 | ], 43 | "outputs": [ 44 | "NodeSocketGeometry" 45 | ] 46 | }, 47 | "GeometryNodeBake": { 48 | "inputs": [ 49 | "NodeSocketGeometry", 50 | "NodeSocketVirtual" 51 | ], 52 | "outputs": [ 53 | "NodeSocketGeometry", 54 | "NodeSocketVirtual" 55 | ] 56 | }, 57 | "GeometryNodeSetGeometryName": { 58 | "inputs": [ 59 | "NodeSocketGeometry", 60 | "NodeSocketString" 61 | ], 62 | "outputs": [ 63 | "NodeSocketGeometry" 64 | ] 65 | }, 66 | "FunctionNodeInputRotation": { 67 | "inputs": [], 68 | "outputs": [ 69 | "NodeSocketRotation" 70 | ] 71 | }, 72 | "GeometryNodeWarning": { 73 | "inputs": [ 74 | "NodeSocketBool", 75 | "NodeSocketString" 76 | ], 77 | "outputs": [ 78 | "NodeSocketBool" 79 | ] 80 | }, 81 | "GeometryNodeInputNamedLayerSelection": { 82 | "inputs": [ 83 | "NodeSocketString" 84 | ], 85 | "outputs": [ 86 | "NodeSocketBool" 87 | ] 88 | }, 89 | "GeometryNodeToolActiveElement": { 90 | "inputs": [], 91 | "outputs": [ 92 | "NodeSocketInt", 93 | "NodeSocketBool" 94 | ] 95 | }, 96 | "GeometryNodeToolSelection": { 97 | "inputs": [], 98 | "outputs": [ 99 | "NodeSocketFloat", 100 | "NodeSocketBool" 101 | ] 102 | }, 103 | "GeometryNodeSetInstanceTransform": { 104 | "inputs": [ 105 | "NodeSocketGeometry", 106 | "NodeSocketBool", 107 | "NodeSocketMatrix" 108 | ], 109 | "outputs": [ 110 | "NodeSocketGeometry" 111 | ] 112 | }, 113 | "GeometryNodeInstanceTransform": { 114 | "inputs": [], 115 | "outputs": [ 116 | "NodeSocketMatrix" 117 | ] 118 | }, 119 | "GeometryNodePointsToCurves": { 120 | "inputs": [ 121 | "NodeSocketGeometry", 122 | "NodeSocketInt", 123 | "NodeSocketFloat" 124 | ], 125 | "outputs": [ 126 | "NodeSocketGeometry" 127 | ] 128 | }, 129 | "ShaderNodeTexGabor": { 130 | "inputs": [ 131 | "NodeSocketVectorDirection", 132 | "NodeSocketFloatFactor", 133 | "NodeSocketFloat", 134 | "NodeSocketFloatAngle", 135 | "NodeSocketVector" 136 | ], 137 | "outputs": [ 138 | "NodeSocketFloat" 139 | ] 140 | }, 141 | "FunctionNodeHashValue": { 142 | "inputs": [ 143 | "NodeSocketMatrix", 144 | "NodeSocketString", 145 | "NodeSocketRotation", 146 | "NodeSocketColor", 147 | "NodeSocketFloat", 148 | "NodeSocketVector", 149 | "NodeSocketInt" 150 | ], 151 | "outputs": [ 152 | "NodeSocketInt" 153 | ] 154 | }, 155 | "FunctionNodeIntegerMath": { 156 | "inputs": [ 157 | "NodeSocketInt" 158 | ], 159 | "outputs": [ 160 | "NodeSocketInt" 161 | ] 162 | }, 163 | "GeometryNodeMenuSwitch": { 164 | "inputs": [ 165 | "NodeSocketGeometry", 166 | "NodeSocketVirtual", 167 | "NodeSocketMenu", 168 | "NodeSocketMatrix", 169 | "NodeSocketString", 170 | "NodeSocketRotation", 171 | "NodeSocketBool", 172 | "NodeSocketColor", 173 | "NodeSocketMaterial", 174 | "NodeSocketObject", 175 | "NodeSocketFloat", 176 | "NodeSocketVector", 177 | "NodeSocketImage", 178 | "NodeSocketInt", 179 | "NodeSocketCollection" 180 | ], 181 | "outputs": [ 182 | "NodeSocketGeometry", 183 | "NodeSocketMatrix", 184 | "NodeSocketString", 185 | "NodeSocketRotation", 186 | "NodeSocketBool", 187 | "NodeSocketColor", 188 | "NodeSocketMaterial", 189 | "NodeSocketObject", 190 | "NodeSocketFloat", 191 | "NodeSocketVector", 192 | "NodeSocketImage", 193 | "NodeSocketInt", 194 | "NodeSocketCollection" 195 | ] 196 | }, 197 | "FunctionNodeAxesToRotation": { 198 | "inputs": [ 199 | "NodeSocketVector" 200 | ], 201 | "outputs": [ 202 | "NodeSocketRotation" 203 | ] 204 | }, 205 | "FunctionNodeCombineMatrix": { 206 | "inputs": [ 207 | "NodeSocketFloat" 208 | ], 209 | "outputs": [ 210 | "NodeSocketMatrix" 211 | ] 212 | }, 213 | "FunctionNodeMatrixDeterminant": { 214 | "inputs": [ 215 | "NodeSocketMatrix" 216 | ], 217 | "outputs": [ 218 | "NodeSocketFloat" 219 | ] 220 | }, 221 | "FunctionNodeSeparateMatrix": { 222 | "inputs": [ 223 | "NodeSocketMatrix" 224 | ], 225 | "outputs": [ 226 | "NodeSocketFloat" 227 | ] 228 | }, 229 | "GeometryNodeGizmoDial": { 230 | "inputs": [ 231 | "NodeSocketFloat", 232 | "NodeSocketVectorTranslation", 233 | "NodeSocketBool", 234 | "NodeSocketVectorXYZ" 235 | ], 236 | "outputs": [ 237 | "NodeSocketGeometry" 238 | ] 239 | }, 240 | "GeometryNodeGizmoLinear": { 241 | "inputs": [ 242 | "NodeSocketFloat", 243 | "NodeSocketVectorTranslation", 244 | "NodeSocketVectorXYZ" 245 | ], 246 | "outputs": [ 247 | "NodeSocketGeometry" 248 | ] 249 | }, 250 | "GeometryNodeGizmoTransform": { 251 | "inputs": [ 252 | "NodeSocketVectorTranslation", 253 | "NodeSocketMatrix", 254 | "NodeSocketRotation" 255 | ], 256 | "outputs": [ 257 | "NodeSocketGeometry" 258 | ] 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/ShaderNodeTree_sockets_4_1.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It can be auto generated using the 'generate socket types file' operator 4 | { 5 | "bl_version": [ 6 | 4, 7 | 1 8 | ], 9 | "nodes": { 10 | "ShaderNodeAmbientOcclusion": { 11 | "inputs": [ 12 | "NodeSocketFloat", 13 | "NodeSocketColor", 14 | "NodeSocketVector" 15 | ], 16 | "outputs": [ 17 | "NodeSocketFloat", 18 | "NodeSocketColor" 19 | ] 20 | }, 21 | "ShaderNodeAttribute": { 22 | "inputs": [], 23 | "outputs": [ 24 | "NodeSocketFloat", 25 | "NodeSocketColor", 26 | "NodeSocketVector" 27 | ] 28 | }, 29 | "ShaderNodeCameraData": { 30 | "inputs": [], 31 | "outputs": [ 32 | "NodeSocketFloat", 33 | "NodeSocketVector" 34 | ] 35 | }, 36 | "ShaderNodeVertexColor": { 37 | "inputs": [], 38 | "outputs": [ 39 | "NodeSocketFloat", 40 | "NodeSocketColor" 41 | ] 42 | }, 43 | "ShaderNodeHairInfo": { 44 | "inputs": [], 45 | "outputs": [ 46 | "NodeSocketFloat", 47 | "NodeSocketVector" 48 | ] 49 | }, 50 | "ShaderNodeFresnel": { 51 | "inputs": [ 52 | "NodeSocketFloat", 53 | "NodeSocketVector" 54 | ], 55 | "outputs": [ 56 | "NodeSocketFloat" 57 | ] 58 | }, 59 | "ShaderNodeNewGeometry": { 60 | "inputs": [], 61 | "outputs": [ 62 | "NodeSocketFloat", 63 | "NodeSocketVector" 64 | ] 65 | }, 66 | "ShaderNodeLayerWeight": { 67 | "inputs": [ 68 | "NodeSocketFloat", 69 | "NodeSocketVector" 70 | ], 71 | "outputs": [ 72 | "NodeSocketFloat" 73 | ] 74 | }, 75 | "ShaderNodeLightPath": { 76 | "inputs": [], 77 | "outputs": [ 78 | "NodeSocketFloat" 79 | ] 80 | }, 81 | "ShaderNodeObjectInfo": { 82 | "inputs": [], 83 | "outputs": [ 84 | "NodeSocketFloat", 85 | "NodeSocketColor", 86 | "NodeSocketVector" 87 | ] 88 | }, 89 | "ShaderNodePointInfo": { 90 | "inputs": [], 91 | "outputs": [ 92 | "NodeSocketFloat", 93 | "NodeSocketVector" 94 | ] 95 | }, 96 | "ShaderNodeRGB": { 97 | "inputs": [], 98 | "outputs": [ 99 | "NodeSocketColor" 100 | ] 101 | }, 102 | "ShaderNodeTangent": { 103 | "inputs": [], 104 | "outputs": [ 105 | "NodeSocketVector" 106 | ] 107 | }, 108 | "ShaderNodeTexCoord": { 109 | "inputs": [], 110 | "outputs": [ 111 | "NodeSocketVector" 112 | ] 113 | }, 114 | "ShaderNodeUVMap": { 115 | "inputs": [], 116 | "outputs": [ 117 | "NodeSocketVector" 118 | ] 119 | }, 120 | "ShaderNodeValue": { 121 | "inputs": [], 122 | "outputs": [ 123 | "NodeSocketFloat" 124 | ] 125 | }, 126 | "ShaderNodeVolumeInfo": { 127 | "inputs": [], 128 | "outputs": [ 129 | "NodeSocketFloat", 130 | "NodeSocketColor" 131 | ] 132 | }, 133 | "ShaderNodeWireframe": { 134 | "inputs": [ 135 | "NodeSocketFloat" 136 | ], 137 | "outputs": [ 138 | "NodeSocketFloat" 139 | ] 140 | }, 141 | "ShaderNodeBevel": { 142 | "inputs": [ 143 | "NodeSocketFloat", 144 | "NodeSocketVector" 145 | ], 146 | "outputs": [ 147 | "NodeSocketVector" 148 | ] 149 | }, 150 | "ShaderNodeParticleInfo": { 151 | "inputs": [], 152 | "outputs": [ 153 | "NodeSocketFloat", 154 | "NodeSocketVector" 155 | ] 156 | }, 157 | "ShaderNodeOutputAOV": { 158 | "inputs": [ 159 | "NodeSocketFloat", 160 | "NodeSocketColor" 161 | ], 162 | "outputs": [] 163 | }, 164 | "ShaderNodeOutputLight": { 165 | "inputs": [ 166 | "NodeSocketShader" 167 | ], 168 | "outputs": [] 169 | }, 170 | "ShaderNodeOutputMaterial": { 171 | "inputs": [ 172 | "NodeSocketShader", 173 | "NodeSocketFloat", 174 | "NodeSocketVector" 175 | ], 176 | "outputs": [] 177 | }, 178 | "ShaderNodeAddShader": { 179 | "inputs": [ 180 | "NodeSocketShader" 181 | ], 182 | "outputs": [ 183 | "NodeSocketShader" 184 | ] 185 | }, 186 | "ShaderNodeBsdfDiffuse": { 187 | "inputs": [ 188 | "NodeSocketFloatFactor", 189 | "NodeSocketFloat", 190 | "NodeSocketColor", 191 | "NodeSocketVector" 192 | ], 193 | "outputs": [ 194 | "NodeSocketShader" 195 | ] 196 | }, 197 | "ShaderNodeEmission": { 198 | "inputs": [ 199 | "NodeSocketFloat", 200 | "NodeSocketColor" 201 | ], 202 | "outputs": [ 203 | "NodeSocketShader" 204 | ] 205 | }, 206 | "ShaderNodeBsdfGlass": { 207 | "inputs": [ 208 | "NodeSocketFloatFactor", 209 | "NodeSocketFloat", 210 | "NodeSocketColor", 211 | "NodeSocketVector" 212 | ], 213 | "outputs": [ 214 | "NodeSocketShader" 215 | ] 216 | }, 217 | "ShaderNodeBsdfAnisotropic": { 218 | "inputs": [ 219 | "NodeSocketFloatFactor", 220 | "NodeSocketFloat", 221 | "NodeSocketColor", 222 | "NodeSocketVector" 223 | ], 224 | "outputs": [ 225 | "NodeSocketShader" 226 | ] 227 | }, 228 | "ShaderNodeHoldout": { 229 | "inputs": [ 230 | "NodeSocketFloat" 231 | ], 232 | "outputs": [ 233 | "NodeSocketShader" 234 | ] 235 | }, 236 | "ShaderNodeMixShader": { 237 | "inputs": [ 238 | "NodeSocketFloatFactor", 239 | "NodeSocketShader" 240 | ], 241 | "outputs": [ 242 | "NodeSocketShader" 243 | ] 244 | }, 245 | "ShaderNodeBsdfPrincipled": { 246 | "inputs": [ 247 | "NodeSocketFloatFactor", 248 | "NodeSocketVector", 249 | "NodeSocketFloat", 250 | "NodeSocketColor", 251 | "NodeSocketFloatDistance" 252 | ], 253 | "outputs": [ 254 | "NodeSocketShader" 255 | ] 256 | }, 257 | "ShaderNodeVolumePrincipled": { 258 | "inputs": [ 259 | "NodeSocketString", 260 | "NodeSocketFloatFactor", 261 | "NodeSocketFloat", 262 | "NodeSocketColor" 263 | ], 264 | "outputs": [ 265 | "NodeSocketShader" 266 | ] 267 | }, 268 | "ShaderNodeBsdfRefraction": { 269 | "inputs": [ 270 | "NodeSocketFloatFactor", 271 | "NodeSocketFloat", 272 | "NodeSocketColor", 273 | "NodeSocketVector" 274 | ], 275 | "outputs": [ 276 | "NodeSocketShader" 277 | ] 278 | }, 279 | "ShaderNodeSubsurfaceScattering": { 280 | "inputs": [ 281 | "NodeSocketFloatFactor", 282 | "NodeSocketFloat", 283 | "NodeSocketColor", 284 | "NodeSocketVector" 285 | ], 286 | "outputs": [ 287 | "NodeSocketShader" 288 | ] 289 | }, 290 | "ShaderNodeBsdfTranslucent": { 291 | "inputs": [ 292 | "NodeSocketFloat", 293 | "NodeSocketColor", 294 | "NodeSocketVector" 295 | ], 296 | "outputs": [ 297 | "NodeSocketShader" 298 | ] 299 | }, 300 | "ShaderNodeBsdfTransparent": { 301 | "inputs": [ 302 | "NodeSocketFloat", 303 | "NodeSocketColor" 304 | ], 305 | "outputs": [ 306 | "NodeSocketShader" 307 | ] 308 | }, 309 | "ShaderNodeVolumeAbsorption": { 310 | "inputs": [ 311 | "NodeSocketFloat", 312 | "NodeSocketColor" 313 | ], 314 | "outputs": [ 315 | "NodeSocketShader" 316 | ] 317 | }, 318 | "ShaderNodeVolumeScatter": { 319 | "inputs": [ 320 | "NodeSocketFloatFactor", 321 | "NodeSocketFloat", 322 | "NodeSocketColor" 323 | ], 324 | "outputs": [ 325 | "NodeSocketShader" 326 | ] 327 | }, 328 | "ShaderNodeBsdfToon": { 329 | "inputs": [ 330 | "NodeSocketFloatFactor", 331 | "NodeSocketFloat", 332 | "NodeSocketColor", 333 | "NodeSocketVector" 334 | ], 335 | "outputs": [ 336 | "NodeSocketShader" 337 | ] 338 | }, 339 | "ShaderNodeBsdfHair": { 340 | "inputs": [ 341 | "NodeSocketFloatFactor", 342 | "NodeSocketVector", 343 | "NodeSocketFloat", 344 | "NodeSocketColor", 345 | "NodeSocketFloatAngle" 346 | ], 347 | "outputs": [ 348 | "NodeSocketShader" 349 | ] 350 | }, 351 | "ShaderNodeBsdfHairPrincipled": { 352 | "inputs": [ 353 | "NodeSocketFloatFactor", 354 | "NodeSocketVector", 355 | "NodeSocketFloat", 356 | "NodeSocketColor", 357 | "NodeSocketFloatAngle" 358 | ], 359 | "outputs": [ 360 | "NodeSocketShader" 361 | ] 362 | }, 363 | "ShaderNodeEeveeSpecular": { 364 | "inputs": [ 365 | "NodeSocketFloatFactor", 366 | "NodeSocketFloat", 367 | "NodeSocketColor", 368 | "NodeSocketVector" 369 | ], 370 | "outputs": [ 371 | "NodeSocketShader" 372 | ] 373 | }, 374 | "ShaderNodeTexBrick": { 375 | "inputs": [ 376 | "NodeSocketFloat", 377 | "NodeSocketColor", 378 | "NodeSocketVector" 379 | ], 380 | "outputs": [ 381 | "NodeSocketFloat", 382 | "NodeSocketColor" 383 | ] 384 | }, 385 | "ShaderNodeTexChecker": { 386 | "inputs": [ 387 | "NodeSocketFloat", 388 | "NodeSocketColor", 389 | "NodeSocketVector" 390 | ], 391 | "outputs": [ 392 | "NodeSocketFloat", 393 | "NodeSocketColor" 394 | ] 395 | }, 396 | "ShaderNodeTexEnvironment": { 397 | "inputs": [ 398 | "NodeSocketVector" 399 | ], 400 | "outputs": [ 401 | "NodeSocketColor" 402 | ] 403 | }, 404 | "ShaderNodeTexGradient": { 405 | "inputs": [ 406 | "NodeSocketVector" 407 | ], 408 | "outputs": [ 409 | "NodeSocketFloat", 410 | "NodeSocketColor" 411 | ] 412 | }, 413 | "ShaderNodeTexImage": { 414 | "inputs": [ 415 | "NodeSocketVector" 416 | ], 417 | "outputs": [ 418 | "NodeSocketFloat", 419 | "NodeSocketColor" 420 | ] 421 | }, 422 | "ShaderNodeTexMagic": { 423 | "inputs": [ 424 | "NodeSocketFloat", 425 | "NodeSocketVector" 426 | ], 427 | "outputs": [ 428 | "NodeSocketFloat", 429 | "NodeSocketColor" 430 | ] 431 | }, 432 | "ShaderNodeTexNoise": { 433 | "inputs": [ 434 | "NodeSocketFloatFactor", 435 | "NodeSocketFloat", 436 | "NodeSocketVector" 437 | ], 438 | "outputs": [ 439 | "NodeSocketFloat", 440 | "NodeSocketColor" 441 | ] 442 | }, 443 | "ShaderNodeTexSky": { 444 | "inputs": [ 445 | "NodeSocketVector" 446 | ], 447 | "outputs": [ 448 | "NodeSocketColor" 449 | ] 450 | }, 451 | "ShaderNodeTexVoronoi": { 452 | "inputs": [ 453 | "NodeSocketFloatFactor", 454 | "NodeSocketFloat", 455 | "NodeSocketVector" 456 | ], 457 | "outputs": [ 458 | "NodeSocketFloat", 459 | "NodeSocketColor", 460 | "NodeSocketVector" 461 | ] 462 | }, 463 | "ShaderNodeTexWave": { 464 | "inputs": [ 465 | "NodeSocketFloatFactor", 466 | "NodeSocketFloat", 467 | "NodeSocketVector" 468 | ], 469 | "outputs": [ 470 | "NodeSocketFloat", 471 | "NodeSocketColor" 472 | ] 473 | }, 474 | "ShaderNodeTexWhiteNoise": { 475 | "inputs": [ 476 | "NodeSocketFloat", 477 | "NodeSocketVector" 478 | ], 479 | "outputs": [ 480 | "NodeSocketFloat", 481 | "NodeSocketColor" 482 | ] 483 | }, 484 | "ShaderNodeTexIES": { 485 | "inputs": [ 486 | "NodeSocketFloat", 487 | "NodeSocketVector" 488 | ], 489 | "outputs": [ 490 | "NodeSocketFloat" 491 | ] 492 | }, 493 | "ShaderNodeTexPointDensity": { 494 | "inputs": [ 495 | "NodeSocketVector" 496 | ], 497 | "outputs": [ 498 | "NodeSocketFloat", 499 | "NodeSocketColor" 500 | ] 501 | }, 502 | "ShaderNodeBrightContrast": { 503 | "inputs": [ 504 | "NodeSocketFloat", 505 | "NodeSocketColor" 506 | ], 507 | "outputs": [ 508 | "NodeSocketColor" 509 | ] 510 | }, 511 | "ShaderNodeGamma": { 512 | "inputs": [ 513 | "NodeSocketFloatUnsigned", 514 | "NodeSocketColor" 515 | ], 516 | "outputs": [ 517 | "NodeSocketColor" 518 | ] 519 | }, 520 | "ShaderNodeHueSaturation": { 521 | "inputs": [ 522 | "NodeSocketFloatFactor", 523 | "NodeSocketFloat", 524 | "NodeSocketColor" 525 | ], 526 | "outputs": [ 527 | "NodeSocketColor" 528 | ] 529 | }, 530 | "ShaderNodeInvert": { 531 | "inputs": [ 532 | "NodeSocketFloatFactor", 533 | "NodeSocketColor" 534 | ], 535 | "outputs": [ 536 | "NodeSocketColor" 537 | ] 538 | }, 539 | "ShaderNodeMix": { 540 | "inputs": [ 541 | "NodeSocketFloatFactor", 542 | "NodeSocketRotation", 543 | "NodeSocketVector", 544 | "NodeSocketFloat", 545 | "NodeSocketColor" 546 | ], 547 | "outputs": [ 548 | "NodeSocketRotation", 549 | "NodeSocketFloat", 550 | "NodeSocketColor", 551 | "NodeSocketVector" 552 | ] 553 | }, 554 | "ShaderNodeRGBCurve": { 555 | "inputs": [ 556 | "NodeSocketFloatFactor", 557 | "NodeSocketColor" 558 | ], 559 | "outputs": [ 560 | "NodeSocketColor" 561 | ] 562 | }, 563 | "ShaderNodeLightFalloff": { 564 | "inputs": [ 565 | "NodeSocketFloat" 566 | ], 567 | "outputs": [ 568 | "NodeSocketFloat" 569 | ] 570 | }, 571 | "ShaderNodeBump": { 572 | "inputs": [ 573 | "NodeSocketFloatFactor", 574 | "NodeSocketFloat", 575 | "NodeSocketVector" 576 | ], 577 | "outputs": [ 578 | "NodeSocketVector" 579 | ] 580 | }, 581 | "ShaderNodeDisplacement": { 582 | "inputs": [ 583 | "NodeSocketFloat", 584 | "NodeSocketVector" 585 | ], 586 | "outputs": [ 587 | "NodeSocketVector" 588 | ] 589 | }, 590 | "ShaderNodeMapping": { 591 | "inputs": [ 592 | "NodeSocketVectorXYZ", 593 | "NodeSocketVectorEuler", 594 | "NodeSocketVectorTranslation", 595 | "NodeSocketVector" 596 | ], 597 | "outputs": [ 598 | "NodeSocketVector" 599 | ] 600 | }, 601 | "ShaderNodeNormal": { 602 | "inputs": [ 603 | "NodeSocketVectorDirection" 604 | ], 605 | "outputs": [ 606 | "NodeSocketFloat", 607 | "NodeSocketVectorDirection" 608 | ] 609 | }, 610 | "ShaderNodeNormalMap": { 611 | "inputs": [ 612 | "NodeSocketFloat", 613 | "NodeSocketColor" 614 | ], 615 | "outputs": [ 616 | "NodeSocketVector" 617 | ] 618 | }, 619 | "ShaderNodeVectorCurve": { 620 | "inputs": [ 621 | "NodeSocketFloatFactor", 622 | "NodeSocketVector" 623 | ], 624 | "outputs": [ 625 | "NodeSocketVector" 626 | ] 627 | }, 628 | "ShaderNodeVectorDisplacement": { 629 | "inputs": [ 630 | "NodeSocketFloat", 631 | "NodeSocketColor" 632 | ], 633 | "outputs": [ 634 | "NodeSocketVector" 635 | ] 636 | }, 637 | "ShaderNodeVectorRotate": { 638 | "inputs": [ 639 | "NodeSocketVectorEuler", 640 | "NodeSocketFloatAngle", 641 | "NodeSocketVector" 642 | ], 643 | "outputs": [ 644 | "NodeSocketVector" 645 | ] 646 | }, 647 | "ShaderNodeVectorTransform": { 648 | "inputs": [ 649 | "NodeSocketVector" 650 | ], 651 | "outputs": [ 652 | "NodeSocketVector" 653 | ] 654 | }, 655 | "ShaderNodeVectorMath": { 656 | "inputs": [ 657 | "NodeSocketFloat", 658 | "NodeSocketVector" 659 | ], 660 | "outputs": [ 661 | "NodeSocketFloat", 662 | "NodeSocketVector" 663 | ] 664 | }, 665 | "ShaderNodeBlackbody": { 666 | "inputs": [ 667 | "NodeSocketFloat" 668 | ], 669 | "outputs": [ 670 | "NodeSocketColor" 671 | ] 672 | }, 673 | "ShaderNodeClamp": { 674 | "inputs": [ 675 | "NodeSocketFloat" 676 | ], 677 | "outputs": [ 678 | "NodeSocketFloat" 679 | ] 680 | }, 681 | "ShaderNodeValToRGB": { 682 | "inputs": [ 683 | "NodeSocketFloatFactor" 684 | ], 685 | "outputs": [ 686 | "NodeSocketFloat", 687 | "NodeSocketColor" 688 | ] 689 | }, 690 | "ShaderNodeCombineColor": { 691 | "inputs": [ 692 | "NodeSocketFloatFactor" 693 | ], 694 | "outputs": [ 695 | "NodeSocketColor" 696 | ] 697 | }, 698 | "ShaderNodeCombineXYZ": { 699 | "inputs": [ 700 | "NodeSocketFloat" 701 | ], 702 | "outputs": [ 703 | "NodeSocketVector" 704 | ] 705 | }, 706 | "ShaderNodeFloatCurve": { 707 | "inputs": [ 708 | "NodeSocketFloatFactor", 709 | "NodeSocketFloat" 710 | ], 711 | "outputs": [ 712 | "NodeSocketFloat" 713 | ] 714 | }, 715 | "ShaderNodeMapRange": { 716 | "inputs": [ 717 | "NodeSocketFloat", 718 | "NodeSocketVector" 719 | ], 720 | "outputs": [ 721 | "NodeSocketFloat", 722 | "NodeSocketVector" 723 | ] 724 | }, 725 | "ShaderNodeMath": { 726 | "inputs": [ 727 | "NodeSocketFloat" 728 | ], 729 | "outputs": [ 730 | "NodeSocketFloat" 731 | ] 732 | }, 733 | "ShaderNodeRGBToBW": { 734 | "inputs": [ 735 | "NodeSocketColor" 736 | ], 737 | "outputs": [ 738 | "NodeSocketFloat" 739 | ] 740 | }, 741 | "ShaderNodeSeparateColor": { 742 | "inputs": [ 743 | "NodeSocketColor" 744 | ], 745 | "outputs": [ 746 | "NodeSocketFloat" 747 | ] 748 | }, 749 | "ShaderNodeSeparateXYZ": { 750 | "inputs": [ 751 | "NodeSocketVector" 752 | ], 753 | "outputs": [ 754 | "NodeSocketFloat" 755 | ] 756 | }, 757 | "ShaderNodeWavelength": { 758 | "inputs": [ 759 | "NodeSocketFloat" 760 | ], 761 | "outputs": [ 762 | "NodeSocketColor" 763 | ] 764 | }, 765 | "ShaderNodeShaderToRGB": { 766 | "inputs": [ 767 | "NodeSocketShader" 768 | ], 769 | "outputs": [ 770 | "NodeSocketFloat", 771 | "NodeSocketColor" 772 | ] 773 | }, 774 | "ShaderNodeScript": { 775 | "inputs": [], 776 | "outputs": [] 777 | }, 778 | "NodeFrame": { 779 | "inputs": [], 780 | "outputs": [] 781 | }, 782 | "NodeReroute": { 783 | "inputs": [ 784 | "NodeSocketColor" 785 | ], 786 | "outputs": [ 787 | "NodeSocketColor" 788 | ] 789 | } 790 | } 791 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/ShaderNodeTree_sockets_4_2.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It contains all of the new and updated nodes in this blender version. 4 | // It can be auto generated using the 'generate socket types file' operator 5 | { 6 | "bl_version": [ 7 | 4, 8 | 2 9 | ], 10 | "nodes": { 11 | "ShaderNodeBsdfPrincipled": { 12 | "inputs": [ 13 | "NodeSocketFloatWavelength", 14 | "NodeSocketFloatDistance", 15 | "NodeSocketFloatFactor", 16 | "NodeSocketVector", 17 | "NodeSocketColor", 18 | "NodeSocketFloat" 19 | ], 20 | "outputs": [ 21 | "NodeSocketShader" 22 | ] 23 | }, 24 | "ShaderNodeWavelength": { 25 | "inputs": [ 26 | "NodeSocketFloatWavelength" 27 | ], 28 | "outputs": [ 29 | "NodeSocketColor" 30 | ] 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/sockets/ShaderNodeTree_sockets_4_3.jsonc: -------------------------------------------------------------------------------- 1 | // This is a list of the socket types of all nodes 2 | // used for telling whether a node is valid during link drag. 3 | // It contains all of the new and updated nodes in this blender version. 4 | // It can be auto generated using the 'generate socket types file' operator 5 | { 6 | "bl_version": [ 7 | 4, 8 | 3 9 | ], 10 | "nodes": { 11 | "ShaderNodeBackground": { 12 | "inputs": [ 13 | "NodeSocketFloat", 14 | "NodeSocketColor" 15 | ], 16 | "outputs": [ 17 | "NodeSocketShader" 18 | ] 19 | }, 20 | "ShaderNodeUVAlongStroke": { 21 | "inputs": [], 22 | "outputs": [ 23 | "NodeSocketVector" 24 | ] 25 | }, 26 | "ShaderNodeBsdfMetallic": { 27 | "inputs": [ 28 | "NodeSocketFloatFactor", 29 | "NodeSocketFloat", 30 | "NodeSocketVector", 31 | "NodeSocketColor" 32 | ], 33 | "outputs": [ 34 | "NodeSocketShader" 35 | ] 36 | }, 37 | "ShaderNodeVolumePrincipled": { 38 | "inputs": [ 39 | "NodeSocketString", 40 | "NodeSocketColor", 41 | "NodeSocketFloatFactor", 42 | "NodeSocketFloat", 43 | "NodeSocketFloatColorTemperature" 44 | ], 45 | "outputs": [ 46 | "NodeSocketShader" 47 | ] 48 | }, 49 | "ShaderNodeBsdfSheen": { 50 | "inputs": [ 51 | "NodeSocketFloatFactor", 52 | "NodeSocketFloat", 53 | "NodeSocketVector", 54 | "NodeSocketColor" 55 | ], 56 | "outputs": [ 57 | "NodeSocketShader" 58 | ] 59 | }, 60 | "ShaderNodeBsdfRayPortal": { 61 | "inputs": [ 62 | "NodeSocketFloat", 63 | "NodeSocketVector", 64 | "NodeSocketColor" 65 | ], 66 | "outputs": [ 67 | "NodeSocketShader" 68 | ] 69 | }, 70 | "ShaderNodeTexGabor": { 71 | "inputs": [ 72 | "NodeSocketVectorDirection", 73 | "NodeSocketFloatFactor", 74 | "NodeSocketFloat", 75 | "NodeSocketFloatAngle", 76 | "NodeSocketVector" 77 | ], 78 | "outputs": [ 79 | "NodeSocketFloat" 80 | ] 81 | }, 82 | "ShaderNodeGamma": { 83 | "inputs": [ 84 | "NodeSocketFloat", 85 | "NodeSocketColor" 86 | ], 87 | "outputs": [ 88 | "NodeSocketColor" 89 | ] 90 | }, 91 | "ShaderNodeBlackbody": { 92 | "inputs": [ 93 | "NodeSocketFloatColorTemperature" 94 | ], 95 | "outputs": [ 96 | "NodeSocketColor" 97 | ] 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /node_pie/node_def_files/user/ScriptingNodesTree.jsonc: -------------------------------------------------------------------------------- 1 | // For more info about how to use this file, use the "Open Example File" operator in the right click menu of the node editor 2 | // You'll need Node Pie developer extras to be enabled in the preferences to see it. 3 | 4 | // The config for the Serpens node editor, big thanks to @ADRs on Blender Artists for making it :) 5 | 6 | { 7 | "layout": { 8 | "left": [ 9 | ["INPUT"], 10 | ["INPUT_II", "SNIPPETS", "LAYOUT"], 11 | ["INTERFACE", "DEBUG"] 12 | ], 13 | "right": [ 14 | ["PROGRAM", "PROPERTIES"], 15 | ["PROGRAM_II", "VARIABLES"], 16 | ["UTILITIES"] 17 | ], 18 | "top": [["EVENTS"]], 19 | "bottom": [["PYTHON"]] 20 | }, 21 | "categories": { 22 | "INPUT": { 23 | "label": "Input", 24 | "color": "input", 25 | "nodes": [ 26 | { "identifier": "SN_AddonInfoNode" }, 27 | { "identifier": "SN_AssetNode" }, 28 | { "identifier": "SN_GetEditSelectNode" }, 29 | { "identifier": "SN_IconNode" }, 30 | { "identifier": "SN_InModeNode" }, 31 | { "identifier": "SN_IsExportNode" }, 32 | { "identifier": "SN_IsObjectType" }, 33 | { "identifier": "SN_NamedIconNode" }, 34 | { "identifier": "SN_NodeIdnameNode" }, 35 | { "identifier": "SN_NodeIsIdname" }, 36 | { "identifier": "SN_RandomColorNode" }, 37 | { "identifier": "SN_RandomNumberNode" }, 38 | { "identifier": "SN_SceneContextNode" }, 39 | { "identifier": "SN_TimeNode" }, 40 | { "separator": true, "label": "Data" }, 41 | { "identifier": "SN_BooleanNode" }, 42 | { "identifier": "SN_BooleanVectorNode" }, 43 | { "identifier": "SN_ColorNode" }, 44 | { "identifier": "SN_FloatNode" }, 45 | { "identifier": "SN_FloatVectorNode" }, 46 | { "identifier": "SN_IntegerNode" }, 47 | { "identifier": "SN_IntegerVectorNode" }, 48 | { "identifier": "SN_ListNode" }, 49 | { "identifier": "SN_NoneNode" }, 50 | { "identifier": "SN_StringNode" }, 51 | { "separator": true, "label": "Geometry" }, 52 | { "identifier": "SN_BMeshEdgeDataNode" }, 53 | { "identifier": "SN_BMeshFaceDataNode" }, 54 | { "identifier": "SN_FacesToVertexLocationsNode" }, 55 | { "identifier": "SN_BMeshDataNode" }, 56 | { "identifier": "SN_BMeshVertexDataNode" }, 57 | { "identifier": "SN_CreateLineLocationsNode" }, 58 | { "identifier": "SN_CreateQuadLocationsNode" }, 59 | { "identifier": "SN_CreateTriangleLocationsNode" }, 60 | { "identifier": "SN_NgonToTriangleLocationsNode" }, 61 | { "identifier": "SN_ObjectToBMeshNode" }, 62 | { "identifier": "SN_TriangulateBmeshNode" } 63 | ] 64 | }, 65 | "INPUT_II": { 66 | "label": "Areas", 67 | "color": "input", 68 | "nodes": [ 69 | // { "separator": true, "label": "Areas" }, 70 | { "identifier": "SN_2DViewZoomNode" }, 71 | { "identifier": "SN_AreaByTypeNode" }, 72 | { "identifier": "SN_AreaLocationsNode" }, 73 | { "separator": true, "label": "Blend Data" }, 74 | { "identifier": "SN_ActionsBlendDataNode" }, 75 | { "identifier": "SN_ArmaturesBlendDataNode" }, 76 | { "identifier": "SN_BlenderDataNode" }, 77 | { "identifier": "SN_BrushesBlendDataNode" }, 78 | { "identifier": "SN_CamerasBlendDataNode" }, 79 | { "identifier": "SN_CollectionsBlendDataNode" }, 80 | { "identifier": "SN_CurvesBlendDataNode" }, 81 | { "identifier": "SN_FontsBlendDataNode" }, 82 | { "identifier": "SN_GreasePencilsBlendDataNode" }, 83 | { "identifier": "SN_ImagesBlendDataNode" }, 84 | { "identifier": "SN_LatticesBlendDataNode" }, 85 | { "identifier": "SN_LightsBlendDataNode" }, 86 | { "identifier": "SN_MaterialsBlendDataNode" }, 87 | { "identifier": "SN_MeshBlendDataNode" }, 88 | { "identifier": "SN_MetaballsBlendDataNode" }, 89 | { "identifier": "SN_NodeGroupsBlendDataNode" }, 90 | { "identifier": "SN_ObjectBlendDataNode" }, 91 | { "identifier": "SN_ScenesBlendDataNode" }, 92 | { "identifier": "SN_ScreensBlendDataNode" }, 93 | { "identifier": "SN_ShapeKeysBlendDataNode" }, 94 | { "identifier": "SN_TextsBlendDataNode" }, 95 | { "identifier": "SN_TexturesBlendDataNode" }, 96 | { "identifier": "SN_VolumesBlendDataNode" }, 97 | { "identifier": "SN_WindowManagersBlendDataNode" }, 98 | { "identifier": "SN_WorkspacesBlendDataNode" }, 99 | { "identifier": "SN_WorldsBlendDataNode" } 100 | ] 101 | }, 102 | "INTERFACE": { 103 | "label": "Interface", 104 | "color": "vector", 105 | "nodes": [ 106 | { "identifier": "SN_ButtonNodeNew" }, 107 | { "identifier": "SN_DisplayCollectionListNodeNew" }, 108 | { "identifier": "SN_DisplayEnumItemNode" }, 109 | { "identifier": "SN_DisplayIconNodeNew" }, 110 | { "identifier": "SN_DisplayPreviewNodeNew" }, 111 | { "identifier": "SN_DisplayPropertyNodeNew" }, 112 | { "identifier": "SN_DisplaySearchNodeNew" }, 113 | { "identifier": "SN_DisplaySerpensShortcutNodeNew" }, 114 | { "identifier": "SN_EnumMapInterfaceNode" }, 115 | { "identifier": "SN_IconGalleryNodeNew" }, 116 | { "identifier": "SN_LabelNodeNew" }, 117 | { "identifier": "SN_SeparatorNodeNew" }, 118 | { "separator": true, "label": "Layout" }, 119 | { "identifier": "SN_AddToMenuNodeNew" }, 120 | { "identifier": "SN_AddToPanelNodeNew" }, 121 | { "identifier": "SN_InterfaceFunctionNode" }, 122 | { "identifier": "SN_RunInterfaceFunctionNodeNew" }, 123 | { "identifier": "SN_MenuNode" }, 124 | { "identifier": "SN_PanelNode" }, 125 | { "identifier": "SN_PieMenuNode" }, 126 | { "identifier": "SN_PreferencesNode" }, 127 | { "separator": true, "label": "SubLayout" }, 128 | { "identifier": "SN_LayoutBoxNodeNew" }, 129 | { "identifier": "SN_LayoutColumnNodeNew" }, 130 | { "identifier": "SN_CopyMenuNodeNew" }, 131 | { "identifier": "SN_CopyPanelNodeNew" }, 132 | { "identifier": "SN_LayoutGridNode" }, 133 | { "identifier": "SN_IfElseInterfaceNodeNew" }, 134 | { "identifier": "SN_ForInterfaceNodeNew" }, 135 | { "identifier": "SN_RepeatInterfaceNodeNew" }, 136 | { "identifier": "SN_PopoverNodeNew" }, 137 | { "identifier": "SN_LayoutRowNodeNew" }, 138 | { "identifier": "SN_LayoutSplitNodeNew" }, 139 | { "identifier": "SN_SubmenuNodeNew" } 140 | ] 141 | }, 142 | "PROGRAM": { 143 | "label": "Program", 144 | "color": "geometry", 145 | "nodes": [ 146 | { "identifier": "SN_FunctionNode" }, 147 | { "identifier": "SN_FunctionReturnNode" }, 148 | { "identifier": "SN_RunFunctionNode" }, 149 | { "identifier": "SN_OperatorNode" }, 150 | { "identifier": "SN_RunOperatorNode" }, 151 | { "separator": true, "label": "Drawing" }, 152 | { "identifier": "SN_DrawCircleNode" }, 153 | { "identifier": "SN_DrawLineNode" }, 154 | { "identifier": "SN_DrawPointNode" }, 155 | { "identifier": "SN_DrawQuadNode" }, 156 | { "identifier": "SN_DrawModalTextNode" }, 157 | { "identifier": "SN_DrawTriangleNode" }, 158 | { "identifier": "SN_EndDrawingNode" }, 159 | { "identifier": "SN_StartDrawingNode" }, 160 | { "separator": true, "label": "Files" }, 161 | { "identifier": "SN_JoinPathNode" }, 162 | { "identifier": "SN_ListBlendContentNode" }, 163 | { "identifier": "SN_ListDirectoryFilesNode" }, 164 | { "identifier": "SN_AbsolutePathNode" }, 165 | { "identifier": "SN_PathInfoNode" } 166 | ] 167 | }, 168 | "PROGRAM_II": { 169 | "label": "Modal", 170 | "color": "geometry", 171 | "nodes": [ 172 | //{ "separator": true, "label": "Modal" }, 173 | { "identifier": "SN_ModalEventNode" }, 174 | { "identifier": "SN_ModalOperatorNode" }, 175 | { "identifier": "SN_ModalShortcutPressedNode" }, 176 | { "identifier": "SN_ModalViewportMovedNode" }, 177 | { "identifier": "SN_ReturnModalNode" }, 178 | { "identifier": "SN_SetModalCursorNode" }, 179 | { "identifier": "SN_TextSizeNode" }, 180 | { "separator": true, "label": "Operations" }, 181 | { "identifier": "SN_BreakNode" }, 182 | { "identifier": "SN_EnumMapNode" }, 183 | { "identifier": "SN_FindShortcutOperator" }, 184 | { "identifier": "SN_IfElseExecuteNode" }, 185 | { "identifier": "SN_ForExecuteNode" }, 186 | { "identifier": "SN_RepeatExecuteNode" }, 187 | { "identifier": "SN_OpenMenuNode" }, 188 | { "identifier": "SN_OpenPanelNode" }, 189 | { "identifier": "SN_OpenPieMenuNode" }, 190 | { "identifier": "SN_OverrideContextNode" }, 191 | { "identifier": "SN_RefreshViewNode" }, 192 | { "identifier": "SN_ReportNode" }, 193 | { "identifier": "SN_SetEditSelectNode" }, 194 | { "identifier": "SN_SetHeaderTextNode" }, 195 | { "identifier": "SN_SetStatusTextNode" } 196 | ] 197 | }, 198 | "PROGRAM_TIMER": { 199 | "label": "Timer", 200 | "color": "geometry", 201 | "nodes": [ 202 | { "identifier": "SN_RunInIntervalsNode" }, 203 | { "identifier": "SN_RunMultipleTimesNode" }, 204 | { "identifier": "SN_RunWithDelayNode" } 205 | ] 206 | }, 207 | "PROPERTIES": { 208 | "label": "Properties", 209 | "color": "converter", 210 | "nodes": [ 211 | { "identifier": "SN_BlenderPropertyNode" }, 212 | { "identifier": "SN_OnPropertyUpdateNode" }, 213 | { "identifier": "SN_PropertyExistsNode" }, 214 | { "identifier": "SN_RunPropertyFunctionNode" }, 215 | { "identifier": "SN_SerpensPropertyNode" }, 216 | { "identifier": "SN_SetPropertyNode" }, 217 | { "separator": true, "label": "Collection" }, 218 | { "identifier": "SN_AddCollectionItemNode" }, 219 | { "identifier": "SN_CollectionLengthNode" }, 220 | { "identifier": "SN_IndexCollectionPropertyNode" }, 221 | { "identifier": "SN_IsIndexInCollectionPropertyNode" }, 222 | { "identifier": "SN_MoveCollectionItemNode" }, 223 | { "identifier": "SN_RemoveCollectionItemNode" }, 224 | { "separator": true, "label": "Custom" }, 225 | { "identifier": "SN_GetCustomPropertyNode" }, 226 | { "identifier": "SN_HasCustomPropertyNode" }, 227 | { "identifier": "SN_SetCustomPropertyNode" }, 228 | { "separator": true, "label": "Enum" }, 229 | { "identifier": "SN_GenerateEnumItemsNode" }, 230 | { "identifier": "SN_MakeEnumItemNode" } 231 | ] 232 | }, 233 | "PYTHON": { 234 | "label": "Python", 235 | "color": "texture", 236 | "nodes": [ 237 | { "identifier": "SN_GetDataScriptlineNode" }, 238 | { "identifier": "SN_GetPropertyScriptlineNode" }, 239 | { "identifier": "SN_InterfaceScriptNode" }, 240 | { "identifier": "SN_InterfaceScriptlineNode" }, 241 | { "identifier": "SN_RunScriptNode" }, 242 | { "identifier": "SN_ScriptNode" }, 243 | { "identifier": "SN_ScriptlineNode" }, 244 | { "separator": true, "label": "Attributes" }, 245 | { "identifier": "SN_GetAttributeNode" }, 246 | { "identifier": "SN_HasAttributeNode" }, 247 | { "identifier": "SN_SetAttributeNode" } 248 | ] 249 | }, 250 | "UTILITIES": { 251 | "label": "Utilities", 252 | "color": "group", 253 | "nodes": [ 254 | { "identifier": "SN_CompareNode" }, 255 | { "identifier": "SN_SwitchDataNode" }, 256 | { "identifier": "SN_SwitchIconNode" }, 257 | { "separator": true, "label": "Boolean" }, 258 | { "identifier": "SN_BooleanMathNode" }, 259 | { "identifier": "SN_InvertBooleanNode" }, 260 | { "separator": true, "label": "Converter" }, 261 | { "identifier": "SN_3DLocationTo2DNode" }, 262 | { "identifier": "SN_CombineVectorNode" }, 263 | { "identifier": "SN_RadiansNode" }, 264 | { "identifier": "SN_DataToIconNode" }, 265 | { "identifier": "SN_DefineDataType" }, 266 | { "identifier": "SN_EnumSetToListNode" }, 267 | { "identifier": "SN_RegionToViewNode" }, 268 | { "identifier": "SN_SplitVectorNode" }, 269 | { "identifier": "SN_ViewToRegionNode" }, 270 | { "separator": true, "label": "Files" }, 271 | { "identifier": "SN_JoinPathNode" }, 272 | { "identifier": "SN_ListBlendContentNode" }, 273 | { "identifier": "SN_ListDirectoryFilesNode" }, 274 | { "identifier": "SN_AbsolutePathNode" }, 275 | { "identifier": "SN_PathInfoNode" }, 276 | { "separator": true, "label": "Math" }, 277 | { "identifier": "SN_ClampNode" }, 278 | { "identifier": "SN_MathNode" }, 279 | { "identifier": "SN_RoundNode" }, 280 | { "identifier": "SN_VectorMathNode" }, 281 | { "separator": true, "label": "Text" }, 282 | { "identifier": "SN_CombineStringsNode" }, 283 | { "identifier": "SN_DecodeStringNode" }, 284 | { "identifier": "SN_EncodeStringNode" }, 285 | { "identifier": "SN_JoinStringsNode" }, 286 | { "identifier": "SN_MapStringsNode" }, 287 | { "identifier": "SN_PadStringNode" }, 288 | { "identifier": "SN_ReplaceStringNode" }, 289 | { "identifier": "SN_SliceStringNode" }, 290 | { "identifier": "SN_SplitStringNode" }, 291 | { "identifier": "SN_StringLengthNode" }, 292 | { "identifier": "SN_StripStringNode" }, 293 | { "identifier": "SN_IsInStringNode" } 294 | ] 295 | }, 296 | "VARIABLES": { 297 | "label": "Variables", 298 | "color": "attribute", 299 | "nodes": [ 300 | { "identifier": "SN_ChangeVariableByNode" }, 301 | { "identifier": "SN_GetVariableNode" }, 302 | { "identifier": "SN_ResetVariableNode" }, 303 | { "identifier": "SN_SetVariableNode" }, 304 | { "identifier": "SN_ToggleVariableNode" }, 305 | { "separator": true, "label": "List" }, 306 | { "identifier": "SN_AddToListNode" }, 307 | { "identifier": "SN_IsInListNode" }, 308 | { "identifier": "SN_IndexListNode" }, 309 | { "identifier": "SN_IndexOfElementNode" }, 310 | { "identifier": "SN_ListLengthNode" }, 311 | { "identifier": "SN_RemoveFromListNode" }, 312 | { "identifier": "SN_SortListNode" } 313 | ] 314 | }, 315 | "EVENTS": { 316 | "label": "Events", 317 | "color": "color", 318 | "nodes": [ 319 | { "identifier": "SN_BeforeExitNode" }, 320 | { "identifier": "SN_DepsgraphUpdateNode" }, 321 | { "identifier": "SN_ChangeFrameNode" }, 322 | { "identifier": "SN_OnKeypressNode" }, 323 | { "identifier": "SN_OnLoadNode" }, 324 | { "identifier": "SN_RedoEventNode" }, 325 | { "identifier": "SN_AfterRenderNode" }, 326 | { "identifier": "SN_BeforeRenderNode" }, 327 | { "identifier": "SN_OnSaveNode" }, 328 | { "identifier": "SN_UndoEventNode" } 329 | ] 330 | }, 331 | "DEBUG": { 332 | "label": "Debug", 333 | "color": "output", 334 | "nodes": [ 335 | { "identifier": "SN_PrintNode" }, 336 | { "identifier": "SN_SleepNode" }, 337 | { "identifier": "SN_TimestampNode" }, 338 | { "identifier": "SN_TriggerNode" } 339 | ] 340 | }, 341 | "SNIPPETS": { 342 | "label": "Snippets", 343 | "color": "script", 344 | "nodes": [{ "identifier": "SN_SnippetNode" }] 345 | }, 346 | "LAYOUT": { 347 | "label": "Layout", 348 | "color": "layout", 349 | "nodes": [ 350 | { "identifier": "NodeFrame" }, 351 | { "identifier": "SN_PortalNode" }, 352 | { "identifier": "NodeReroute" } 353 | ] 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /node_pie/npie_constants.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from pathlib import Path 3 | 4 | import bpy 5 | 6 | IS_4_0 = bpy.app.version >= (4, 0, 0) 7 | IS_MACOS = platform.system() == "Darwin" 8 | 9 | POPULARITY_FILE = Path(__file__).parent / "nodes.json" 10 | if not POPULARITY_FILE.exists(): 11 | with open(POPULARITY_FILE, "w") as f: 12 | pass 13 | POPULARITY_FILE_VERSION = (0, 0, 1) 14 | 15 | NODE_DEF_EXAMPLE_PREFIX = "node_def_" 16 | NODE_DEF_DIR = Path(__file__).parent / "node_def_files" 17 | NODE_DEF_BUILTIN = NODE_DEF_DIR / "builtin" 18 | NODE_DEF_USER = NODE_DEF_DIR / "user" 19 | NODE_DEF_BASE_FILE = NODE_DEF_DIR / "node_def_base.jsonc" 20 | NODE_DEF_EXAMPLE_FILE = NODE_DEF_DIR / "node_def_example.jsonc" 21 | NODE_DEF_SOCKETS = NODE_DEF_DIR / "sockets" 22 | 23 | SHADERS_DIR = Path(__file__).parent / "shaders" 24 | -------------------------------------------------------------------------------- /node_pie/npie_drawing.py: -------------------------------------------------------------------------------- 1 | import gpu 2 | from gpu.types import GPUBatch, GPUShader, GPUShaderCreateInfo, GPUStageInterfaceInfo 3 | from gpu_extras.batch import batch_for_shader 4 | from mathutils import Vector as V 5 | 6 | from .npie_constants import IS_4_0, IS_MACOS, SHADERS_DIR 7 | 8 | # Create the the antialiased line shader 9 | # This is not trivial... 10 | # And it doesn't work on Apple Silicone because of course it doesn't 11 | 12 | if IS_MACOS: 13 | name = "UNIFORM_COLOR" if IS_4_0 else "2D_UNIFORM_COLOR" 14 | line_shader: GPUShader = gpu.shader.from_builtin(name) 15 | 16 | def draw_line(from_pos: V, to_pos: V, color: V, width=1): 17 | """Draw a rubbish normal line""" 18 | 19 | batch: GPUBatch = batch_for_shader(line_shader, "LINES", {"pos": [from_pos, to_pos]}) 20 | line_shader.bind() 21 | gpu.state.blend_set("ALPHA") 22 | gpu.state.line_width_set(width * 3) 23 | line_shader.uniform_float("color", color) 24 | batch.draw(line_shader) 25 | 26 | else: 27 | vert_code = (SHADERS_DIR / "2D_vert.glsl").read_text() 28 | frag_code = (SHADERS_DIR / "2D_line_antialiased_frag.glsl").read_text() 29 | 30 | # Create a gpu shader from scratch 31 | line_shader_info = GPUShaderCreateInfo() 32 | line_shader_info.define("is_compile", "true") 33 | line_shader_info.push_constant("MAT4", "ModelViewProjectionMatrix") 34 | line_shader_info.push_constant("VEC4", "color") 35 | line_shader_info.vertex_in(0, "VEC2", "pos") 36 | line_shader_info.vertex_in(1, "VEC2", "uvs") 37 | 38 | line_shader_interface = GPUStageInterfaceInfo("line_shader") 39 | line_shader_interface.smooth("VEC2", "frag_uvs") 40 | line_shader_info.vertex_out(line_shader_interface) 41 | 42 | line_shader_info.fragment_out(0, "VEC4", "fragColor") 43 | 44 | line_shader_info.vertex_source(vert_code) 45 | line_shader_info.fragment_source(frag_code) 46 | 47 | line_shader = gpu.shader.create_from_info(line_shader_info) 48 | 49 | def draw_line(from_pos: V, to_pos: V, color: V, width=1): 50 | """Draw a beautiful antialiased line""" 51 | 52 | # Calculate the tangent 53 | normal = V(to_pos - from_pos) 54 | normal.normalize() 55 | tangent = V((normal.y, -normal.x)) 56 | tangent *= width * 1.9 57 | 58 | # The four corners are the ends shifted along the tangent of the line 59 | coords = [from_pos + tangent, from_pos - tangent, to_pos + tangent, to_pos - tangent] 60 | indices = [(0, 1, 2), (1, 2, 3)] 61 | uvs = [(0, 0), (0, 1), (1, 0), (1, 1)] 62 | 63 | batch: GPUBatch = batch_for_shader(line_shader, "TRIS", {"pos": coords, "uvs": uvs}, indices=indices) 64 | line_shader.bind() 65 | 66 | gpu.state.blend_set("ALPHA") 67 | line_shader.uniform_float("color", color) 68 | batch.draw(line_shader) 69 | -------------------------------------------------------------------------------- /node_pie/npie_helpers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | from typing import TYPE_CHECKING 4 | 5 | from bpy.types import AddonPreferences, Context, Node, NodeTree 6 | from mathutils import Vector as V 7 | 8 | from .. import __package__ as base_package 9 | from .npie_constants import NODE_DEF_DIR, NODE_DEF_EXAMPLE_PREFIX 10 | 11 | if TYPE_CHECKING: 12 | from .npie_prefs import NodePiePrefs 13 | else: 14 | NodePiePrefs = AddonPreferences 15 | 16 | 17 | def get_node_location(node: Node) -> V: 18 | """Get the actual location of a node, taking parent frame nodes into account""" 19 | location = node.location.copy() 20 | while node.parent: 21 | node = node.parent 22 | location += node.location 23 | return location 24 | 25 | 26 | def get_node_tree(context: Context) -> NodeTree: 27 | """Get the node tree currently being edited""" 28 | return context.space_data.edit_tree 29 | 30 | 31 | def get_prefs(context) -> NodePiePrefs: 32 | """Return the addon preferences""" 33 | return context.preferences.addons[base_package].preferences 34 | 35 | 36 | def lerp(fac, a, b) -> float: 37 | """Linear interpolation (mix) between two values""" 38 | return (fac * b) + ((1 - fac) * a) 39 | 40 | 41 | def inv_lerp(fac, a, b) -> float: 42 | """Inverse Linar Interpolation, get the fraction between a and b on which fac resides.""" 43 | return (fac - a) / (b - a) 44 | 45 | 46 | def vec_divide(a, b) -> V: 47 | """Elementwise divide for two vectors""" 48 | return V(e1 / e2 if e2 != 0 else 0 for e1, e2 in zip(a, b)) 49 | 50 | 51 | def vec_min(a, b) -> V: 52 | """Elementwise minimum for two vectors""" 53 | return V(min(e) for e in zip(a, b)) 54 | 55 | 56 | def vec_max(a, b) -> V: 57 | """Elementwise maximum for two vectors""" 58 | return V(max(e) for e in zip(a, b)) 59 | 60 | 61 | def map_range(val, from_min=0, from_max=1, to_min=0, to_max=2): 62 | """Map a value from one input range to another. Works in the same way as the map range node in blender. 63 | succinct formula from: https://stackoverflow.com/a/45389903""" 64 | return (val - from_min) / (from_max - from_min) * (to_max - to_min) + to_min 65 | 66 | 67 | class JSONWithCommentsDecoder(json.JSONDecoder): 68 | 69 | match_trailing_commas: re.Pattern = re.compile(r",(?=\s*?[\}\]])", re.MULTILINE) 70 | 71 | def __init__(self, **kw): 72 | super().__init__(**kw) 73 | 74 | def decode(self, s: str): 75 | # Remove comments 76 | s = "\n".join(l if not l.lstrip().startswith("//") else "" for l in s.split("\n")) 77 | # Remove trailing commas 78 | s = self.match_trailing_commas.sub("", s) 79 | return super().decode(s) 80 | 81 | 82 | def get_all_def_files(): 83 | files = [] 84 | for file in NODE_DEF_DIR.rglob("*"): 85 | if file.parent.name == "sockets": 86 | continue 87 | if file.is_file() and file.suffix == ".jsonc" and not file.name.startswith(NODE_DEF_EXAMPLE_PREFIX): 88 | files.append(file) 89 | return files 90 | 91 | 92 | class Rectangle: 93 | """Helper class to represent a rectangle""" 94 | 95 | __slots__ = ["min", "max"] 96 | 97 | def __init__(self, min_co=(0, 0), max_co=(0, 0)): 98 | min_co = V(min_co) 99 | max_co = V(max_co) 100 | 101 | self.min = min_co 102 | self.max = max_co 103 | 104 | # alternate getter syntax 105 | minx = property(fget=lambda self: self.min.x) 106 | miny = property(fget=lambda self: self.min.y) 107 | maxx = property(fget=lambda self: self.max.x) 108 | maxy = property(fget=lambda self: self.max.y) 109 | 110 | @property 111 | def coords(self): 112 | """Return coordinates for drawing""" 113 | coords = [ 114 | (self.minx, self.miny), 115 | (self.maxx, self.miny), 116 | (self.maxx, self.maxy), 117 | (self.minx, self.maxy), 118 | ] 119 | return coords 120 | 121 | @property 122 | def size(self): 123 | return self.max - self.min 124 | 125 | # FIXME: This can just be changed to using vec_mean of the min and max 126 | @property 127 | def center(self): 128 | return self.min + vec_divide(self.max - self.min, V((2, 2))) 129 | 130 | # return the actual min/max values. Needed because the class does not check 131 | # if the min and max values given are actually min and max at init. 132 | # I could fix it, but a bunch of stuff is built on it already, and I can't really be bothered 133 | @property 134 | def true_min(self): 135 | return vec_min(self.min, self.max) 136 | 137 | @property 138 | def true_max(self): 139 | return vec_max(self.min, self.max) 140 | 141 | def __str__(self): 142 | return f"Rectangle(V({self.minx}, {self.miny}), V({self.maxx}, {self.maxy}))" 143 | 144 | def __repr__(self): 145 | return self.__str__() 146 | 147 | def __mul__(self, value): 148 | if not isinstance(value, V): 149 | value = V((value, value)) 150 | return Rectangle(self.min * value, self.max * value) 151 | 152 | def __add__(self, value): 153 | if not isinstance(value, V): 154 | value = V((value, value)) 155 | return Rectangle(self.min + value, self.max + value) 156 | 157 | def isinside(self, point) -> bool: 158 | """Check if a point is inside this rectangle""" 159 | point = point 160 | min = self.true_min 161 | max = self.true_max 162 | return min.x <= point[0] <= max.x and min.y <= point[1] <= max.y 163 | 164 | def as_lines(self, individual=False): 165 | """Return a list of lines that make up this rectangle""" 166 | lines = [] 167 | add = lines.append if individual else lines.extend 168 | coords = self.coords 169 | for i, coord in enumerate(coords): 170 | add((coord, coords[i - 1])) 171 | return lines 172 | 173 | def crop(self, rectangle): 174 | """Crop this rectangle to the inside of another one""" 175 | self.min = vec_max(self.min, rectangle.min) 176 | self.max = vec_min(self.max, rectangle.max) 177 | # prevent min/max overspilling on other side 178 | self.min = vec_min(self.min, rectangle.max) 179 | self.max = vec_max(self.max, rectangle.min) 180 | -------------------------------------------------------------------------------- /node_pie/npie_keymap.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Context, KeyMap 3 | 4 | from .npie_btypes import BOperator 5 | from .operators.op_call_link_drag import NPIE_OT_call_link_drag 6 | from .operators.op_insert_node_pie import NPIE_OT_insert_node_pie 7 | 8 | addon_keymaps: list[tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = [] 9 | 10 | 11 | def get_operator_keymap_items(keymap: KeyMap, operator_idname: str) -> list[KeyMap]: 12 | return [kmi for kmi in keymap.keymap_items if kmi.idname == operator_idname] 13 | 14 | 15 | def register(): 16 | addon_keymaps.clear() 17 | 18 | wm = bpy.context.window_manager 19 | kc = wm.keyconfigs.addon 20 | if kc: 21 | km = kc.keymaps.new(name="View2D") 22 | 23 | kmi = km.keymap_items.new( 24 | NPIE_OT_call_link_drag.bl_idname, 25 | type="LEFTMOUSE", 26 | value="PRESS", 27 | ctrl=True, 28 | ) 29 | addon_keymaps.append((km, kmi)) 30 | kmi = km.keymap_items.new( 31 | NPIE_OT_call_link_drag.bl_idname, 32 | type="A", 33 | value="PRESS", 34 | ctrl=True, 35 | ) 36 | addon_keymaps.append((km, kmi)) 37 | kmi = km.keymap_items.new( 38 | NPIE_OT_insert_node_pie.bl_idname, 39 | type="LEFTMOUSE", 40 | value="CLICK_DRAG", 41 | ctrl=True, 42 | alt=True, 43 | ) 44 | addon_keymaps.append((km, kmi)) 45 | 46 | 47 | def unregister(): 48 | for km, kmi in addon_keymaps: 49 | km.keymap_items.remove(kmi) 50 | addon_keymaps.clear() 51 | 52 | 53 | def get_keymap() -> KeyMap: 54 | return bpy.context.window_manager.keyconfigs.user.keymaps["View2D"] 55 | 56 | 57 | def draw_keymap_item(kmi: bpy.types.KeyMapItem, layout: bpy.types.UILayout): 58 | """Draw a keymap item in a prettier way than the default""" 59 | row = layout.row(align=True) 60 | map_type = kmi.map_type 61 | row.prop(kmi, "map_type", text="") 62 | if map_type == "KEYBOARD": 63 | row.prop(kmi, "type", text="", full_event=True) 64 | elif map_type == "MOUSE": 65 | row.prop(kmi, "type", text="", full_event=True) 66 | elif map_type == "NDOF": 67 | row.prop(kmi, "type", text="", full_event=True) 68 | elif map_type == "TWEAK": 69 | subrow = row.row() 70 | subrow.prop(kmi, "type", text="") 71 | subrow.prop(kmi, "value", text="") 72 | elif map_type == "TIMER": 73 | row.prop(kmi, "type", text="") 74 | else: 75 | row.label() 76 | 77 | box = layout 78 | 79 | if map_type not in {"TEXTINPUT", "TIMER"}: 80 | sub = box.column() 81 | subrow = sub.row(align=True) 82 | 83 | if map_type == "KEYBOARD": 84 | subrow.prop(kmi, "type", text="", event=True) 85 | subrow.prop(kmi, "value", text="") 86 | subrow_repeat = subrow.row(align=True) 87 | subrow_repeat.active = kmi.value in {"ANY", "PRESS"} 88 | subrow_repeat.prop(kmi, "repeat", text="", icon="FILE_REFRESH") 89 | elif map_type in {"MOUSE", "NDOF"}: 90 | subrow.prop(kmi, "type", text="") 91 | subrow.prop(kmi, "value", text="") 92 | 93 | subrow = sub.row(align=True) 94 | subrow.scale_x = 0.75 95 | subrow.prop(kmi, "any", toggle=True) 96 | subrow.prop(kmi, "shift_ui", toggle=True) 97 | subrow.prop(kmi, "ctrl_ui", toggle=True) 98 | subrow.prop(kmi, "alt_ui", toggle=True) 99 | subrow.prop(kmi, "oskey_ui", text="Cmd", toggle=True) 100 | subrow.prop(kmi, "key_modifier", text="", event=True) 101 | 102 | 103 | @BOperator("node_pie") 104 | class NPIE_OT_edit_keymap_item(BOperator.type): 105 | 106 | index: bpy.props.IntProperty() 107 | 108 | operator: bpy.props.StringProperty() 109 | 110 | def invoke(self, context: bpy.types.Context, event: bpy.types.Event): 111 | return self.call_popup(width=400) 112 | 113 | def draw(self, context: Context): 114 | km = get_keymap() 115 | kmi = get_operator_keymap_items(km, self.operator)[self.index] 116 | layout = self.layout 117 | layout.scale_y = 1.2 118 | row = layout.row(align=True) 119 | row.label(text="Edit keybind:") 120 | draw_keymap_item(kmi, layout) 121 | 122 | 123 | @BOperator("node_pie") 124 | class NPIE_OT_remove_keymap_item(BOperator.type): 125 | 126 | index: bpy.props.IntProperty() 127 | 128 | operator: bpy.props.StringProperty() 129 | 130 | def execute(self, context): 131 | km = get_keymap() 132 | kmi = get_operator_keymap_items(km, self.operator)[self.index] 133 | km.keymap_items.remove(kmi) 134 | 135 | 136 | @BOperator("node_pie") 137 | class NPIE_OT_new_keymap_item(BOperator.type): 138 | """Add a new keymap item for calling the node pie menu""" 139 | 140 | operator: bpy.props.StringProperty() 141 | 142 | type: bpy.props.StringProperty(default="LEFTMOUSE") 143 | 144 | value: bpy.props.StringProperty(default="PRESS") 145 | 146 | ctrl: bpy.props.BoolProperty() 147 | shift: bpy.props.BoolProperty() 148 | alt: bpy.props.BoolProperty() 149 | 150 | def execute(self, context): 151 | km = get_keymap() 152 | km.keymap_items.new( 153 | self.operator, 154 | self.type, 155 | self.value, 156 | ctrl=self.ctrl, 157 | shift=self.shift, 158 | alt=self.alt, 159 | ) 160 | return {"FINISHED"} 161 | -------------------------------------------------------------------------------- /node_pie/npie_menus.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import UILayout 3 | 4 | from .npie_btypes import BMenu 5 | from .npie_helpers import get_all_def_files, get_prefs 6 | from .operators.op_check_missing_nodes import NPIE_OT_check_missing_nodes 7 | from .operators.op_generate_socket_types_file import NPIE_OT_generate_socket_types_file 8 | 9 | 10 | @BMenu("Node Pie Utilities") 11 | class NPIE_MT_node_pie_utilities(BMenu.type): 12 | 13 | def draw(self, context): 14 | layout: UILayout = self.layout 15 | op = layout.operator("node_pie.open_definition_file", text="Open example definition file") 16 | op.example = True 17 | 18 | for file in get_all_def_files(): 19 | if file.name == f"{context.space_data.tree_type}.jsonc": 20 | word = "Open" 21 | break 22 | else: 23 | word = "Create" 24 | op = layout.operator("node_pie.open_definition_file", text=f"{word} definition file for this node tree type") 25 | op.example = False 26 | layout.operator("node_pie.copy_nodes_as_json") 27 | NPIE_OT_generate_socket_types_file.draw_button(layout) 28 | NPIE_OT_check_missing_nodes.draw_button(layout) 29 | 30 | 31 | def context_menu_draw(self, context): 32 | prefs = get_prefs(context) 33 | if not prefs.npie_dev_extras: 34 | return 35 | layout: UILayout = self.layout 36 | layout.separator() 37 | layout.menu(NPIE_MT_node_pie_utilities.bl_idname, icon="NODE") 38 | 39 | 40 | def register(): 41 | bpy.types.NODE_MT_context_menu.append(context_menu_draw) 42 | 43 | 44 | def unregister(): 45 | bpy.types.NODE_MT_context_menu.remove(context_menu_draw) 46 | -------------------------------------------------------------------------------- /node_pie/npie_node_def_file.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass, field 3 | from inspect import isclass 4 | from pathlib import Path 5 | from typing import Any 6 | 7 | import bpy 8 | from bpy.types import Context 9 | 10 | from .npie_constants import NODE_DEF_BUILTIN, NODE_DEF_USER 11 | from .npie_helpers import JSONWithCommentsDecoder, get_all_def_files 12 | 13 | 14 | class PollCondition: 15 | """Represents a condition that can be evaluated to determine whether to show a node or not.""" 16 | 17 | def __init__(self, context_path: str, operand: str, value: Any = None): 18 | supported_operands = {"bool", "equals", "in", "not_equals"} 19 | if operand not in supported_operands: 20 | raise ValueError(f"Operand for item {self} '{operand}' is not in {supported_operands}") 21 | 22 | self.context_path = context_path 23 | self.operand = operand 24 | self.value = value 25 | 26 | def evaluate(self, context: Context): 27 | attr_path = f"context.{self.context_path}" 28 | result = eval(attr_path) 29 | if self.operand == "bool" and bool(result): 30 | return True 31 | elif self.operand == "equals" and self.value == result: 32 | return True 33 | elif self.operand == "not_equals" and self.value != result: 34 | return True 35 | elif self.operand == "in" and self.value in result: 36 | return True 37 | return False 38 | 39 | 40 | @dataclass 41 | class NodeItem: 42 | """An imitator of the built in blender NodeItem class, that implements the necessary settings""" 43 | 44 | label: str 45 | idname: str 46 | settings: dict = field(default_factory=dict) 47 | variants: dict = field(default_factory=dict) 48 | poll_conditions: list[PollCondition] = field(default_factory=list) 49 | color: str = "" 50 | description: str = "" 51 | category = None 52 | 53 | def poll(self, context: Context): 54 | if not self.poll_conditions: 55 | return True 56 | for condition in self.poll_conditions: 57 | if condition.evaluate(context): 58 | return True 59 | return False 60 | 61 | 62 | @dataclass 63 | class NodeCategory: 64 | """An imitator of the built in blender NodeCategory class, that implements the necessary settings""" 65 | 66 | label: str 67 | nodes: list[NodeItem] 68 | color: str 69 | icon: str = "" 70 | children: list = None 71 | idname: str = "" 72 | poll_conditions: list[PollCondition] = field(default_factory=list) 73 | 74 | poll = NodeItem.poll 75 | # def poll(self, context: Context): 76 | # if not self.poll_conditions: 77 | # return True 78 | # for condition in self.poll_conditions: 79 | # if condition.evaluate(context): 80 | # return True 81 | # return False 82 | 83 | def items(self, context) -> list[NodeItem]: 84 | return self.nodes 85 | 86 | 87 | @dataclass 88 | class Separator: 89 | label: str = "" 90 | poll_conditions: list[PollCondition] = field(default_factory=list) 91 | 92 | poll = NodeItem.poll 93 | 94 | 95 | @dataclass 96 | class NodeOperator: 97 | idname: str 98 | label: str = "" 99 | settings: dict = field(default_factory=dict) 100 | color: str = "" 101 | 102 | 103 | def create_defaults(data: dict): 104 | # Add default values in case they are missing from the file 105 | default_layout = {"top": [[]], "bottom": [[]], "left": [[]], "right": [[]]} 106 | data["layout"] = data.get("layout", default_layout) 107 | default_layout.update(data["layout"]) 108 | data["layout"] = default_layout 109 | data["poll_types"] = data.get("poll_types", {}) 110 | data["categories"] = data.get("categories", {}) 111 | return data 112 | 113 | 114 | def merge_configs(base: dict, additions: dict, removals: dict = {}): 115 | # Add default values in case they are missing from the file 116 | base = create_defaults(base) 117 | additions = create_defaults(additions) 118 | removals = create_defaults(removals) 119 | 120 | # REMOVALS 121 | # Process layout removals 122 | orig_layout = base["layout"] 123 | remove_layout = removals["layout"] 124 | for area_name, rem_area in remove_layout.items(): 125 | for orig_column, rem_column in zip(orig_layout[area_name], rem_area): 126 | for cat_id in rem_column: 127 | orig_column.remove(cat_id) 128 | 129 | # Process category removals 130 | orig_categories = base["categories"] 131 | remove_categories = removals["categories"] 132 | for rem_cat_name, rem_cat in remove_categories.items(): 133 | if nodes := rem_cat.get("nodes", []): 134 | orig_nodes = orig_categories[rem_cat_name]["nodes"] 135 | for rem_node in nodes: 136 | for i, orig_node in enumerate(orig_nodes.copy()): 137 | if rem_node.get("separator") or orig_node.get("separator"): 138 | continue 139 | if rem_node["identifier"] == orig_node["identifier"]: 140 | orig_nodes.remove(orig_node) 141 | break 142 | pass 143 | else: 144 | del orig_categories[rem_cat_name] 145 | 146 | # Process full node removals 147 | remove_nodes = removals.get("nodes", []) 148 | for rem_node in remove_nodes: 149 | for orig_category in orig_categories.values(): 150 | for orig_node in orig_category["nodes"].copy(): 151 | if rem_node == orig_node.get("identifier"): 152 | orig_category["nodes"].remove(orig_node) 153 | break 154 | 155 | # ADDITIONS 156 | # Merge layout 157 | for orig_area_name, orig_columns in base["layout"].items(): 158 | new_columns = additions["layout"].get(orig_area_name) 159 | if not new_columns: 160 | continue 161 | for i, new_column in enumerate(new_columns): 162 | new_column = new_column.copy() 163 | for new_row in new_column: 164 | orig_columns = base["layout"][orig_area_name] 165 | if i > len(orig_columns) - 1: 166 | orig_columns.append([new_row]) 167 | else: 168 | orig_columns[i].append(new_row) 169 | 170 | # Merge poll types 171 | poll_types: dict = base["poll_types"] 172 | poll_types.update(additions["poll_types"]) 173 | 174 | # Merge in the new nodes 175 | for orig_cat_name, orig_cat in base["categories"].items(): 176 | new_cat = additions["categories"].get(orig_cat_name) 177 | if new_cat: 178 | # Insert the node after the specified one. 179 | idx = -1 180 | for new_node in new_cat["nodes"]: 181 | if name := new_node.get("after_node"): 182 | if name == "top": 183 | idx = 0 184 | elif name == "bottom": 185 | idx = -1 186 | else: 187 | names = [n.get("identifier") for n in orig_cat["nodes"]] 188 | idx = names.index(name) + 1 189 | elif name := new_node.get("before_node"): 190 | names = [n.get("identifier") for n in orig_cat["nodes"]] 191 | idx = names.index(name) 192 | 193 | if idx == -1: 194 | orig_cat["nodes"].append(new_node) 195 | else: 196 | orig_cat["nodes"].insert(idx, new_node) 197 | 198 | # Add new categories 199 | new_cats = additions["categories"].keys() - base["categories"].keys() 200 | for new_cat in new_cats: 201 | base["categories"][new_cat] = additions["categories"][new_cat] 202 | return base 203 | 204 | 205 | modified_times = {} 206 | 207 | 208 | def load_custom_nodes_info(tree_identifier: str, context) -> tuple[dict[str, NodeCategory], dict]: 209 | categories = {} 210 | layout = {} 211 | 212 | all_files = get_all_def_files() 213 | 214 | # Different render engines can use different nodes in the default shader editor, account for that. 215 | if tree_identifier == "ShaderNodeTree": 216 | for file in all_files: 217 | with open(file, "r") as f: 218 | data = json.load(f, cls=JSONWithCommentsDecoder) 219 | if data.get("render_engine") == context.scene.render.engine: 220 | tree_identifier = file.name 221 | break 222 | else: 223 | # Auto generate if not blender render engine 224 | if context.scene.render.engine not in { 225 | "BLENDER_EEVEE", 226 | "BLENDER_EEVEE_NEXT", 227 | "CYCLES", 228 | "BLENDER_WORKBENCH", 229 | }: 230 | return {}, {} 231 | 232 | def get_def_files(dir: Path) -> Path: 233 | files = [] 234 | for file in dir.rglob("*"): 235 | if file.is_file() and file.suffix == ".jsonc" and file.name.startswith(f"{tree_identifier}"): 236 | files.append(file) 237 | return files 238 | 239 | # Get files 240 | files = get_def_files(NODE_DEF_USER) 241 | names = {f.name for f in files} 242 | files += [f for f in get_def_files(NODE_DEF_BUILTIN) if f.name not in names] 243 | 244 | if not files: 245 | return {}, {} 246 | 247 | # Sort the files from first version to latest version so that they are applied in the correct order 248 | def sort(f): 249 | with open(f, "r") as file: 250 | fdata = json.load(file, cls=JSONWithCommentsDecoder) 251 | return fdata.get("blender_version", [0, 0, 0]) 252 | 253 | files.sort(key=sort) 254 | 255 | with open(files[0], "r") as f: 256 | data = json.load(f, cls=JSONWithCommentsDecoder) 257 | 258 | # Merge in imports 259 | if imports := data.get("imports"): 260 | for import_name in imports: 261 | for file in all_files: 262 | if file.stem == import_name: 263 | with open(file, "r") as f: 264 | new_data = json.load(f, cls=JSONWithCommentsDecoder) 265 | merge_configs(data, new_data) 266 | break 267 | else: 268 | raise ValueError(f"file {import_name}.jsonc not found") 269 | 270 | # Merge in nodes from newer versions 271 | for file in files: 272 | with open(file, "r") as f: 273 | new_data = json.load(f, cls=JSONWithCommentsDecoder) 274 | 275 | if tuple(new_data.get("blender_version", [0, 0, 0])) > bpy.app.version: 276 | continue 277 | 278 | merge_configs(data, new_data.get("additions", {}), new_data.get("removals", {})) 279 | 280 | layout = data["layout"] 281 | poll_types = data.get("poll_types", {}) 282 | 283 | # Get all node definition classes so that the labels can be auto generated 284 | bl_node_types = {n.bl_idname: n for n in bpy.types.Node.__subclasses__() if hasattr(n, "bl_idname")} 285 | types = {getattr(bpy.types, t) for t in dir(bpy.types)} 286 | for t in types: 287 | if isclass(t) and issubclass(t, bpy.types.Node): 288 | bl_node_types[t.bl_rna.identifier] = t 289 | 290 | not_found = [] 291 | 292 | for cat_idname, cat in data["categories"].items(): 293 | items = [] 294 | for node in cat["nodes"]: 295 | 296 | # Create poll conditions 297 | poll_conditions = [] 298 | if poll_type := node.get("poll_type"): 299 | conditions = node.get("poll_conditions", []) 300 | node["poll_conditions"] = poll_types[poll_type] + conditions 301 | for condition in node.get("poll_conditions", []): 302 | poll_conditions.append(PollCondition(**condition)) 303 | 304 | if node.get("separator"): 305 | items.append(Separator(label=node.get("label", ""), poll_conditions=poll_conditions)) 306 | continue 307 | if node.get("operator"): 308 | items.append( 309 | NodeOperator( 310 | node["operator"], 311 | label=node.get("label", ""), 312 | settings=node.get("settings", {}), 313 | ) 314 | ) 315 | continue 316 | idname = node["identifier"] 317 | 318 | # Get an auto generated label, if one is not provided 319 | bl_node = bl_node_types.get(idname) 320 | label = node.get("label") 321 | if not label: 322 | if bl_node: 323 | label = bl_node.bl_rna.name if bl_node.bl_rna.name != "Node" else bl_node.bl_label 324 | 325 | if not label and not bl_node: 326 | not_found.append(idname) 327 | continue 328 | description = bl_node.bl_rna.description if bl_node else "" 329 | item = NodeItem(label, idname, color=node.get("color", ""), description=description) 330 | item.settings = node.get("settings", {}) 331 | item.variants: dict[str, dict] = node.get("variants", {}) 332 | item.poll_conditions = poll_conditions 333 | 334 | for name, variant in item.variants.items(): 335 | if name != "separator": 336 | all_settings = item.settings.copy() 337 | all_settings.update(variant) 338 | item.variants[name] = all_settings 339 | items.append(item) 340 | 341 | if not cat.get("label"): 342 | raise ValueError(f"No label found for category '{cat_idname}'") 343 | category = NodeCategory( 344 | cat["label"], 345 | items, 346 | color=cat.get("color", ""), 347 | idname=cat_idname, 348 | icon=cat.get("icon", ""), 349 | ) 350 | for condition in cat.get("poll_conditions", {}): 351 | category.poll_conditions.append(PollCondition(**condition)) 352 | categories[cat_idname] = category 353 | for nodeitem in category.nodes: 354 | nodeitem.category = category 355 | 356 | if not_found: 357 | raise ValueError(f"No label found for node(s) '{not_found}'") 358 | return categories, layout 359 | -------------------------------------------------------------------------------- /node_pie/npie_node_info.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import bpy 4 | 5 | from .npie_constants import NODE_DEF_SOCKETS 6 | from .npie_helpers import JSONWithCommentsDecoder 7 | from .npie_node_def_file import NodeItem 8 | 9 | # Convert from node socket types to node enum names 10 | # Switch and compare nodes have special cases that need to be dealt with individually 11 | COMPARE_TYPES = { 12 | "Float": "FLOAT", 13 | "Int": "INT", 14 | "Vector": "VECTOR", 15 | "String": "STRING", 16 | "Color": "RGBA", 17 | } 18 | 19 | SWITCH_TYPES = { 20 | "Bool": "BOOLEAN", 21 | "Object": "OBJECT", 22 | "Collection": "COLLECTION", 23 | "Image": "IMAGE", 24 | "Geometry": "GEOMETRY", 25 | "Rotation": "ROTATION", 26 | "Menu": "MENU", 27 | "Material": "MATERIAL", 28 | } 29 | SWITCH_TYPES.update(COMPARE_TYPES) 30 | if bpy.app.version >= (4, 2): 31 | SWITCH_TYPES["Matrix"] = "MATRIX" 32 | 33 | 34 | def add_socket_names(names_dict): 35 | return {"NodeSocket" + k: v for k, v in names_dict.items()} 36 | 37 | 38 | # All other nodes then have a list of enum types associated with each socket type 39 | ALL_TYPES = SWITCH_TYPES.copy() 40 | ALL_TYPES.update({"Shader": "SHADER"}) 41 | ALL_TYPES.update({"Texture": "TEXTURE"}) 42 | ALL_TYPES = {k: [v] for k, v in ALL_TYPES.items()} 43 | ALL_TYPES["Vector"].append("FLOAT_VECTOR") 44 | ALL_TYPES["Color"].append("FLOAT_COLOR") 45 | 46 | SWITCH_TYPES = add_socket_names(SWITCH_TYPES) 47 | COMPARE_TYPES = add_socket_names(COMPARE_TYPES) 48 | ALL_TYPES = add_socket_names(ALL_TYPES) 49 | 50 | EXCLUSIVE_SOCKETS = {"Material", "Object", "Collection", "Geometry", "Shader", "String", "Image", "Texture"} 51 | EXCLUSIVE_SOCKETS = {"NodeSocket" + s for s in EXCLUSIVE_SOCKETS} 52 | 53 | 54 | def is_socket_to_node_valid(from_socket_type: str, from_socket_is_output: bool, to_node: NodeItem, socket_data: dict): 55 | """Check if the given socket type has any valid connections to the given node.""" 56 | in_out = "inputs" if from_socket_is_output else "outputs" 57 | valid_types = ( 58 | {from_socket_type} if from_socket_type in EXCLUSIVE_SOCKETS else set(ALL_TYPES.keys()) - EXCLUSIVE_SOCKETS 59 | ) 60 | node_socket_data = socket_data.get(to_node.idname) 61 | if not node_socket_data: 62 | print(f"Socket data not defined for node {to_node.idname}") 63 | return True 64 | if any(t in socket_data[to_node.idname][in_out] for t in valid_types): 65 | return True 66 | return False 67 | 68 | 69 | def get_node_socket_info(tree_type: str, max_bl_version=bpy.app.version): 70 | """Return a dictionary of nodes and their socket types""" 71 | sockets_files = NODE_DEF_SOCKETS.rglob(f"**/{tree_type}*.jsonc") 72 | sockets_files_data = [json.loads(f.read_text(), cls=JSONWithCommentsDecoder) for f in sockets_files] 73 | sockets_files_data.sort(key=lambda data: data["bl_version"]) 74 | 75 | all_socket_data = {} 76 | for data in sockets_files_data: 77 | if tuple(data["bl_version"]) <= tuple(max_bl_version): 78 | all_socket_data.update(data["nodes"]) 79 | 80 | return all_socket_data 81 | -------------------------------------------------------------------------------- /node_pie/npie_prefs.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import BoolProperty, FloatProperty 3 | from bpy.types import KeyMap, KeyMapItem, UILayout 4 | from .npie_btypes import BRegister 5 | 6 | from .. import __package__ as base_package 7 | from .npie_helpers import get_prefs 8 | from .npie_keymap import get_keymap, get_operator_keymap_items 9 | from .npie_ui import draw_inline_prop, draw_section 10 | from .operators.op_call_link_drag import ( 11 | NPIE_OT_call_link_drag, 12 | register_debug_handler, 13 | unregister_debug_handler, 14 | ) 15 | from .operators.op_insert_node_pie import NPIE_OT_insert_node_pie 16 | from .operators.op_show_info import InfoSnippets 17 | 18 | 19 | @BRegister() 20 | class NodePiePrefs(bpy.types.AddonPreferences): 21 | """Node pie""" 22 | 23 | bl_idname: str = base_package 24 | 25 | layout: UILayout 26 | node_pie_enabled: BoolProperty(name="Enable node pie", default=True) 27 | 28 | npie_variable_sizes: BoolProperty( 29 | name="Use variable size", 30 | default=True, 31 | description="Whether to increase the size of node buttons that are used most often", 32 | ) 33 | 34 | npie_normal_size: FloatProperty( 35 | name="Normal size", 36 | default=1, 37 | description="The default size of the nodes buttons", 38 | ) 39 | 40 | npie_max_size: FloatProperty( 41 | name="Max size", 42 | default=1.7, 43 | description="The size of the most popular nodes", 44 | ) 45 | 46 | npie_show_node_groups: BoolProperty( 47 | name="Show node groups", 48 | default=True, 49 | description="Whether to show node groups in the pie menu or not", 50 | ) 51 | 52 | npie_expand_node_groups: BoolProperty( 53 | name="Expand node groups", 54 | default=False, 55 | description="Whether to draw the node groups as a sub menu or as individual buttons", 56 | ) 57 | 58 | npie_color_size: FloatProperty( 59 | name="Color bar size", 60 | default=0.02, 61 | description="Having this value too low can cause the colors to disapear.", 62 | subtype="FACTOR", 63 | min=0, 64 | max=1, 65 | ) 66 | 67 | npie_freeze_popularity: BoolProperty( 68 | name="Freeze popularity", 69 | default=False, 70 | description="Prevent new changes the popularity of nodes.", 71 | ) 72 | 73 | npie_separator_headings: BoolProperty( 74 | name="Subcategory labels", 75 | default=True, 76 | description="Draw the headings of subcategories or just a gap", 77 | ) 78 | 79 | npie_show_variants: BoolProperty( 80 | name="Show variants", 81 | default=True, 82 | description="Draw the variants menus for nodes that support them", 83 | ) 84 | 85 | npie_show_icons: BoolProperty( 86 | name="Show icons", 87 | default=True, 88 | description="Draw icons for categories", 89 | ) 90 | 91 | npie_dev_extras: BoolProperty( 92 | name="Show dev extras", 93 | default=False, 94 | description="Show some operators in the right click menu to make creating custom definition files easier.", 95 | ) 96 | 97 | npie_use_link_dragging: BoolProperty( 98 | name="Enable link dragging", 99 | default=True, 100 | description="Allow automatically connecting the new node to a socket if it is hovered", 101 | ) 102 | 103 | npie_link_drag_disable_invalid: BoolProperty( 104 | name="Disable invalid nodes", 105 | default=True, 106 | description="Grey out nodes that are no able to be connected when using link drag or link insert", 107 | ) 108 | 109 | def draw_debug_update(self, context): 110 | if self.npie_draw_debug_lines: 111 | register_debug_handler() 112 | else: 113 | unregister_debug_handler() 114 | 115 | npie_draw_debug_lines: BoolProperty( 116 | name="Draw debug boxes", 117 | default=False, 118 | description="Draw some debug lines to show the bounding box for clicking a node socket for drag linking.", 119 | update=draw_debug_update, 120 | ) 121 | 122 | npie_socket_separation: FloatProperty( 123 | name="Socket separation", 124 | default=22, 125 | description="The vertical distance between node sockets.\ 126 | This should only be changed if you use a high or low UI scale,\ 127 | and drag linking doesn't work as a result.".replace( 128 | " ", "" 129 | ), 130 | subtype="PIXEL", 131 | ) 132 | 133 | # custom_node_def_directory: StringProperty( 134 | # name="Custom node definition file directory.", 135 | # description="A folder for user created definition files. This is used so that" 136 | # ) 137 | 138 | def draw(self, context): 139 | layout = self.layout 140 | # layout = draw_enabled_button(layout, self, "node_pie_enabled") 141 | prefs = get_prefs(context) 142 | layout = layout.grid_flow(row_major=True, even_columns=True) 143 | # layout.label(text="hahah") 144 | fac = 0.515 145 | 146 | col = draw_section(layout, "General") 147 | col.scale_y = 0.9 148 | draw_inline_prop(col, prefs, "npie_show_icons", factor=fac) 149 | draw_inline_prop(col, prefs, "npie_show_variants", factor=fac) 150 | draw_inline_prop(col, prefs, "npie_separator_headings", factor=fac) 151 | draw_inline_prop(col, prefs, "npie_expand_node_groups", factor=fac) 152 | draw_inline_prop(col, prefs, "npie_dev_extras", factor=fac) 153 | draw_inline_prop(col, prefs, "npie_color_size", factor=fac) 154 | 155 | col = draw_section(layout, "Node Size") 156 | draw_inline_prop(col, prefs, "npie_variable_sizes", factor=fac) 157 | draw_inline_prop(col, prefs, "npie_normal_size", factor=fac) 158 | draw_inline_prop(col, prefs, "npie_max_size", factor=fac) 159 | row = col.row(align=True) 160 | row.scale_y = 1.5 161 | row.prop( 162 | prefs, 163 | "npie_freeze_popularity", 164 | text="Unfreeze popularity" if prefs.npie_freeze_popularity else "Freeze popularity", 165 | icon="FREEZE", 166 | toggle=True, 167 | ) 168 | row.operator("node_pie.reset_popularity", icon="FILE_REFRESH") 169 | 170 | col = draw_section(layout, "On Link Drag") 171 | draw_inline_prop(col, prefs, "npie_use_link_dragging", factor=fac) 172 | if prefs.npie_use_link_dragging: 173 | draw_inline_prop(col, prefs, "npie_link_drag_disable_invalid", factor=fac) 174 | draw_inline_prop(col, prefs, "npie_draw_debug_lines", factor=fac) 175 | if prefs.npie_draw_debug_lines: 176 | draw_inline_prop(col, prefs, "npie_socket_separation", factor=fac) 177 | InfoSnippets.link_drag.draw(col) 178 | 179 | def draw_op_kmis(keymap: KeyMap, operator: str, text: str, properties: dict = {}, default_new: dict = {}): 180 | row = col.row(align=True) 181 | row.scale_y = 0.8 182 | row.label(text=text) 183 | 184 | row = row.row(align=True) 185 | row.alignment = "RIGHT" 186 | op = row.operator("node_pie.new_keymap_item", text="", icon="ADD", emboss=False) 187 | op.operator = operator 188 | for name, arg in default_new.items(): 189 | setattr(op, name, arg) 190 | 191 | kmis = get_operator_keymap_items(keymap, operator) 192 | for i, kmi in enumerate(kmis): 193 | kmi: KeyMapItem 194 | 195 | # Check that properties are the same 196 | matches = True 197 | for key, value in properties.items(): 198 | if getattr(kmi.properties, key) != value: 199 | matches = False 200 | break 201 | if not matches: 202 | continue 203 | 204 | row = col.row(align=True) 205 | row.active = kmi.active 206 | sub = row.row(align=True) 207 | sub.prop(kmi, "active", text="") 208 | sub = row.row(align=True) 209 | sub.scale_x = 0.5 210 | sub.prop(kmi, "type", full_event=True, text="") 211 | sub = row.row(align=True) 212 | sub.enabled = True 213 | 214 | # TODO get this working, currently doesn't save with the keymap 215 | # if hasattr(kmi.properties, "pass_through"): 216 | # sub.prop(kmi.properties, "pass_through", text="", icon="IPO_EASE_IN_OUT", invert_checkbox=True) 217 | 218 | op = sub.operator("node_pie.edit_keymap_item", text="", icon="GREASEPENCIL") 219 | op.index = i 220 | op.operator = operator 221 | # if kmi.is_user_modified: 222 | # op = sub.operator("preferences.keyitem_restore", text="", icon="BACK") 223 | # op.item_id = kmi.id 224 | op = sub.operator("node_pie.remove_keymap_item", text="", icon="X") 225 | op.index = i 226 | op.operator = operator 227 | 228 | col = draw_section(layout, "Keymap") 229 | km = get_keymap() 230 | draw_op_kmis(km, NPIE_OT_call_link_drag.bl_idname, "Pie menu:") 231 | col.separator() 232 | draw_op_kmis(km, NPIE_OT_insert_node_pie.bl_idname, "Link insert:", default_new={"value": "CLICK_DRAG"}) 233 | -------------------------------------------------------------------------------- /node_pie/operators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strike-digital/node_pie/48322b48c80347382c4b8292cb8eaf04610dcfdc/node_pie/operators/__init__.py -------------------------------------------------------------------------------- /node_pie/operators/op_add_node.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import OrderedDict 3 | 4 | import bpy 5 | from bpy.types import Node, NodeSocket, NodeTree 6 | 7 | from ..npie_btypes import BOperator 8 | from ..npie_constants import POPULARITY_FILE, POPULARITY_FILE_VERSION 9 | from ..npie_node_info import ALL_TYPES, COMPARE_TYPES, EXCLUSIVE_SOCKETS, SWITCH_TYPES 10 | from ..npie_ui import NPIE_MT_node_pie, get_popularity_id 11 | 12 | 13 | def set_node_settings(socket: NodeSocket, node: Node, ui: bool = True): 14 | """Set a nodes settings so that the correct type of socket is visible. 15 | If ui is false don't make any changes that might be better for the user.""" 16 | # Make sure that the node has the correct data type 17 | # exclude bool if ui so that bool inputs are put into switch rather than true or false socket 18 | if node.bl_idname == "GeometryNodeSwitch" and not ( 19 | socket.bl_idname.startswith("NodeSocketBool") and ui and socket.is_output 20 | ): 21 | try: 22 | name = next(s for s in SWITCH_TYPES if socket.bl_idname.startswith(s)) 23 | except StopIteration: 24 | return 25 | node.input_type = SWITCH_TYPES[name] 26 | 27 | elif node.bl_idname == "FunctionNodeCompare" and socket.is_output: 28 | try: 29 | name = next(s for s in COMPARE_TYPES if socket.bl_idname.startswith(s)) 30 | except StopIteration: 31 | return 32 | node.data_type = COMPARE_TYPES[name] 33 | 34 | elif hasattr(node, "data_type"): 35 | name = next(s for s in ALL_TYPES if socket.bl_idname.startswith(s)) 36 | for data_type in ALL_TYPES[name]: 37 | try: 38 | node.data_type = data_type 39 | except TypeError: 40 | pass 41 | 42 | 43 | def get_socket(from_socket, to_sockets): 44 | """ 45 | Try to find the best match of from_socket in to_sockets based on socket types. 46 | Is agnostic to whether sockets are inputs or outputs. 47 | """ 48 | if not to_sockets: 49 | return 50 | 51 | socket = None 52 | for s in to_sockets: 53 | if s.bl_idname != from_socket.bl_idname: 54 | if s.bl_idname in EXCLUSIVE_SOCKETS or from_socket.bl_idname in EXCLUSIVE_SOCKETS: 55 | continue 56 | socket = s 57 | break 58 | if not socket: 59 | socket = to_sockets[0] 60 | return socket 61 | 62 | 63 | def handle_node_linking(socket: NodeSocket, node: Node): 64 | """Make the optimal link between a node and a socket, taking into account socket types""" 65 | 66 | if socket.is_output: 67 | inputs = [s for s in node.inputs if s.enabled and not s.hide] 68 | to_socket = get_socket(socket, inputs) 69 | from_socket = socket 70 | else: 71 | outputs = [s for s in node.outputs if s.enabled and not s.hide] 72 | from_socket = get_socket(socket, outputs) 73 | to_socket = socket 74 | 75 | if from_socket and to_socket: 76 | node.id_data.links.new(from_socket, to_socket) 77 | 78 | 79 | @BOperator("node_pie", idname="add_node", undo=True) 80 | class NPIE_OT_add_node(BOperator.type): 81 | """Add a node to the node tree, and increase its popularity by 1""" 82 | 83 | type: bpy.props.StringProperty(name="Type", description="Node type to add", default="FunctionNodeInputVector") 84 | group_name: bpy.props.StringProperty(name="Group name", description="The name of the node group to add") 85 | use_transform: bpy.props.BoolProperty(default=True) 86 | 87 | settings: bpy.props.StringProperty( 88 | name="Settings", 89 | description="Settings to be applied on the newly created node", 90 | default="{}", 91 | options={"SKIP_SAVE"}, 92 | ) 93 | 94 | def execute(self, context): 95 | try: 96 | bpy.ops.node.add_node( 97 | "INVOKE_DEFAULT", 98 | False, 99 | type=self.type, 100 | use_transform=self.use_transform, 101 | ) 102 | except RuntimeError as e: 103 | self.report({"ERROR"}, str(e)) 104 | return {"CANCELLED"} 105 | 106 | node_tree: NodeTree = context.area.spaces.active.path[-1].node_tree 107 | node = node_tree.nodes.active 108 | if self.group_name: 109 | node.node_tree = bpy.data.node_groups[self.group_name] 110 | 111 | # If being added by dragging from a socket 112 | if socket := NPIE_MT_node_pie.from_socket: 113 | set_node_settings(socket, node) 114 | 115 | # Set the settings for the node 116 | settings = eval(self.settings) 117 | for name, value in settings.items(): 118 | name = "node." + name 119 | attr = ".".join(name.split(".")[:-1]) 120 | name = name.split(".")[-1] 121 | setattr(eval(attr), name, value) 122 | 123 | # If being added by dragging from a socket 124 | if socket := NPIE_MT_node_pie.from_socket: 125 | handle_node_linking(socket, node) 126 | 127 | # If being added by dragging from a socket 128 | if sockets := NPIE_MT_node_pie.to_sockets: 129 | for socket in sockets: 130 | handle_node_linking(socket, node) 131 | 132 | with open(POPULARITY_FILE, "r") as f: 133 | if text := f.read(): 134 | try: 135 | data = json.loads(text) 136 | except json.decoder.JSONDecodeError: 137 | data = {} 138 | else: 139 | data = {} 140 | 141 | version = data.get("version", POPULARITY_FILE_VERSION) 142 | 143 | if version[0] > POPULARITY_FILE_VERSION[0]: 144 | self.report({"ERROR"}, "Saved nodes file is from a newer version of the addon") 145 | return {"CANCELLED"} 146 | 147 | trees = data.get("node_trees", {}) 148 | nodes = OrderedDict(trees.get(node_tree.bl_rna.identifier, {})) 149 | key = get_popularity_id(self.type, self.settings) 150 | node = nodes.get(key, {}) 151 | count = node.get("count", 0) 152 | count += 1 153 | 154 | data["version"] = POPULARITY_FILE_VERSION 155 | node["count"] = count 156 | nodes[key] = node 157 | # Sort the nodes in descending order 158 | nodes = OrderedDict(sorted(nodes.items(), key=lambda item: item[1].get("count", 0), reverse=True)) 159 | trees[node_tree.bl_rna.identifier] = nodes 160 | data["node_trees"] = trees 161 | 162 | with open(POPULARITY_FILE, "w") as f: 163 | json.dump(data, f, indent=4) 164 | 165 | return {"PASS_THROUGH"} 166 | -------------------------------------------------------------------------------- /node_pie/operators/op_call_link_drag.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import gpu 3 | from bpy.props import BoolProperty, IntProperty 4 | from bpy.types import Area, Context, Event, Node, NodeSocket 5 | from gpu_extras.batch import batch_for_shader 6 | from gpu_extras.presets import draw_circle_2d 7 | from mathutils import Vector as V 8 | 9 | from ..npie_btypes import BOperator 10 | from ..npie_constants import IS_4_0 11 | from ..npie_drawing import draw_line 12 | from ..npie_helpers import Rectangle, get_node_location, get_prefs 13 | from ..npie_ui import NPIE_MT_node_pie 14 | 15 | location = None 16 | hitbox_size = 10 # The radius in which to register a socket click 17 | 18 | 19 | def view_to_region(area: Area, coords: V) -> V: 20 | """Convert 2d editor to screen space coordinates""" 21 | coords = area.regions[3].view2d.view_to_region(coords[0], coords[1], clip=False) 22 | return V(coords) 23 | 24 | 25 | def region_to_view(area: Area, coords: V) -> V: 26 | """Convert screen space to 2d editor coordinates""" 27 | coords = area.regions[3].view2d.region_to_view(coords[0], coords[1]) 28 | return V(coords) 29 | 30 | 31 | def dpifac(): 32 | prefs = bpy.context.preferences.system 33 | return prefs.dpi / 72 34 | return prefs.dpi * prefs.pixel_size / 72 35 | 36 | 37 | def get_socket_bboxes(node: Node) -> tuple[dict[NodeSocket, V], dict[NodeSocket, Rectangle]]: 38 | """Get the bounding boxes of all inputs and outputs for the given node. 39 | There is no built in way to do this so it's mostly arbitrary numbers that look about right. 40 | This doesn't account for nodes with panels, as they are not currently accessible with the api""" 41 | if not node: 42 | return 43 | 44 | not_vectors = {"Subsurface Radius"} # Who decided to make this one socket different smh 45 | 46 | location = get_node_location(node) 47 | positions = {} 48 | bboxes = {} 49 | 50 | # inputs 51 | inputs = [i for i in node.inputs if not i.hide and i.enabled] 52 | bottom = V((location.x, location.y - node.dimensions.y / dpifac())) 53 | min_offset = V((18, 11)) * dpifac() 54 | max_offset_x = node.width * dpifac() 55 | 56 | if node.type == "REROUTE": 57 | pos = location * dpifac() 58 | positions[node.outputs[0]] = pos 59 | size = V((20, 20)) 60 | bboxes[node.outputs[0]] = Rectangle(pos - size, pos + size) 61 | return positions, bboxes 62 | 63 | for i, input in enumerate(list(inputs)[::-1]): 64 | pos = bottom.copy() 65 | min_offset_y = 0 66 | if i == 0: 67 | pos.y -= 5 68 | if input.type in {"VECTOR", "ROTATION"} and not input.hide_value and not input.is_linked and input.name not in not_vectors: 69 | pos.y += 82 70 | min_offset_y = 65 71 | else: 72 | pos.y += get_prefs(bpy.context).npie_socket_separation 73 | bottom = pos 74 | positions[input] = pos * dpifac() 75 | pos = pos * dpifac() 76 | 77 | min_co = pos - V((min_offset.x, min_offset.y + min_offset_y)) 78 | max_co = pos + V((max_offset_x, min_offset.y)) 79 | bboxes[input] = Rectangle(min_co, max_co) 80 | 81 | # Outputs 82 | top = V((location.x + node.width, location.y)) 83 | outputs = [o for o in node.outputs if not o.hide and o.enabled] 84 | 85 | for i, output in enumerate(list(outputs)[::]): 86 | pos = top.copy() 87 | if i == 0: 88 | pos.y -= 35 89 | else: 90 | pos.y -= 22 91 | top = pos 92 | positions[output] = pos * dpifac() 93 | pos = pos * dpifac() 94 | 95 | min_co = pos - V((max_offset_x, min_offset.y)) 96 | max_co = pos + V((min_offset.x, min_offset.y)) 97 | bboxes[output] = Rectangle(min_co, max_co) 98 | 99 | return positions, bboxes 100 | 101 | 102 | if IS_4_0: 103 | shader: gpu.types.GPUShader = gpu.shader.from_builtin("UNIFORM_COLOR") 104 | else: 105 | shader: gpu.types.GPUShader = gpu.shader.from_builtin("2D_UNIFORM_COLOR") 106 | 107 | 108 | def draw_debug_lines(): 109 | """Draw a circle around the sockets of the active node, and also the last location that the node pie was activated""" 110 | node = bpy.context.active_node 111 | if node: 112 | positions, bboxes = get_socket_bboxes(node) 113 | for socket, bbox in bboxes.items(): 114 | batch = batch_for_shader(shader, "LINES", {"pos": bbox.as_lines()}) 115 | shader.bind() 116 | line_colour = (1, 0, 1, 0.9) 117 | shader.uniform_float("color", line_colour) 118 | batch.draw(shader) 119 | for socket, pos in positions.items(): 120 | draw_circle_2d(pos, (1, 0, 1, 1), 5 * dpifac()) 121 | if location: 122 | draw_circle_2d(location, (0, 1, 1, 1), 5 * dpifac()) 123 | 124 | 125 | handlers = [] 126 | 127 | 128 | def register_debug_handler(): 129 | if get_prefs(bpy.context).npie_draw_debug_lines: 130 | global handlers 131 | handlers.append(bpy.types.SpaceNodeEditor.draw_handler_add(draw_debug_lines, (), "WINDOW", "POST_VIEW")) 132 | 133 | 134 | def unregister_debug_handler(): 135 | unregister() 136 | 137 | 138 | def register(): 139 | bpy.app.timers.register(register_debug_handler) 140 | 141 | 142 | def unregister(): 143 | global handlers 144 | for handler in handlers: 145 | bpy.types.SpaceNodeEditor.draw_handler_remove(handler, "WINDOW") 146 | handlers.clear() 147 | 148 | 149 | @BOperator("node_pie") 150 | class NPIE_OT_call_link_drag(BOperator.type): 151 | """Call the node pie menu""" 152 | 153 | name: IntProperty() 154 | 155 | pass_through: BoolProperty( 156 | name="Enable link drag", 157 | description="Whether to check for link drag, or just pass through to the default pie menu", 158 | default=False, 159 | ) 160 | 161 | @classmethod 162 | def poll(cls, context): 163 | if not context.space_data or context.area.type != "NODE_EDITOR" or not context.space_data.edit_tree: 164 | return False 165 | return True 166 | 167 | def invoke(self, context: Context, event: Event): 168 | self.handler = None 169 | self.socket = None 170 | self.from_pos = V((0, 0)) 171 | self.released = False 172 | 173 | mouse_pos = region_to_view(context.area, self.mouse_region) 174 | global location 175 | location = mouse_pos 176 | # Look for a socket near to the mouse position 177 | for node in context.space_data.edit_tree.nodes: 178 | if node.hide: 179 | continue 180 | positions, bboxes = get_socket_bboxes(node) 181 | for socket, bbox in bboxes.items(): 182 | if bbox.isinside(mouse_pos) and socket.bl_idname != "NodeSocketVirtual": 183 | self.socket = socket 184 | self.from_pos = view_to_region(context.area, positions[socket]) 185 | break 186 | 187 | # if socket clicked 188 | if not self.pass_through and self.socket and get_prefs(context).npie_use_link_dragging: 189 | context.area.tag_redraw() 190 | self.handler = bpy.types.SpaceNodeEditor.draw_handler_add( 191 | self.draw_handler, 192 | tuple([context]), 193 | "WINDOW", 194 | "POST_PIXEL", 195 | ) 196 | handlers.append(self.handler) 197 | return self.start_modal() 198 | else: 199 | NPIE_MT_node_pie.from_socket = self.socket 200 | NPIE_MT_node_pie.to_sockets = [] 201 | bpy.ops.node_pie.call_node_pie("INVOKE_DEFAULT") 202 | return self.FINISHED 203 | 204 | def finish(self): 205 | bpy.types.SpaceNodeEditor.draw_handler_remove(self.handler, "WINDOW") 206 | global handlers 207 | handlers.remove(self.handler) 208 | return self.FINISHED 209 | 210 | def modal(self, context: Context, event: Event): 211 | context.area.tag_redraw() 212 | 213 | if event.type in {"RIGHTMOUSE", "ESC"} and event.value != "RELEASE": 214 | return self.finish() 215 | 216 | elif event.value == "RELEASE" and event.type not in {"CTRL", "ALT", "OSKEY", "SHIFT"}: 217 | NPIE_MT_node_pie.from_socket = self.socket 218 | NPIE_MT_node_pie.to_sockets = [] 219 | bpy.ops.node_pie.call_node_pie("INVOKE_DEFAULT", reset_args=False) 220 | return self.finish() 221 | 222 | return self.RUNNING_MODAL 223 | 224 | def draw_handler(self, context: Context): 225 | to_pos = self.mouse_region 226 | color = self.socket.draw_color(context, self.socket.node) 227 | draw_line(self.from_pos, to_pos, color) 228 | -------------------------------------------------------------------------------- /node_pie/operators/op_call_node_pie.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from ..npie_btypes import BOperator 4 | from ..npie_node_def_file import NodeItem, load_custom_nodes_info 5 | from ..npie_ui import NPIE_MT_node_pie, get_variants_menu, unregister_variants_menus 6 | 7 | 8 | @BOperator("node_pie") 9 | class NPIE_OT_call_node_pie(BOperator.type): 10 | """Call the node pie menu""" 11 | 12 | reset_args: bpy.props.BoolProperty(default=True) 13 | 14 | @classmethod 15 | def poll(cls, context): 16 | if not context.space_data or context.area.type != "NODE_EDITOR": 17 | return False 18 | return True 19 | 20 | def execute(self, context): 21 | unregister_variants_menus() 22 | 23 | # The variants menus can't be registered in a draw function, so add them here beforehand 24 | categories, cat_layout = load_custom_nodes_info(context.area.spaces.active.tree_type, context) 25 | has_node_file = categories != {} 26 | if has_node_file: 27 | for cat_name, category in categories.items(): 28 | for node in category.nodes: 29 | if isinstance(node, NodeItem) and node.variants: 30 | get_variants_menu(node) 31 | 32 | if self.reset_args: 33 | NPIE_MT_node_pie.from_socket = None 34 | NPIE_MT_node_pie.to_socket = [] 35 | 36 | bpy.ops.wm.call_menu_pie("INVOKE_DEFAULT", name=NPIE_MT_node_pie.__name__) 37 | -------------------------------------------------------------------------------- /node_pie/operators/op_check_missing_nodes.py: -------------------------------------------------------------------------------- 1 | from inspect import isclass 2 | 3 | import bpy 4 | from bpy.types import NodeTree 5 | 6 | from ..npie_btypes import BOperator 7 | from ..npie_node_def_file import Separator, load_custom_nodes_info 8 | from .op_call_link_drag import region_to_view 9 | 10 | EXCLUDED_NODES = { 11 | "GeometryNodeRepeatInput", 12 | "GeometryNodeForeachGeometryElementInput", 13 | "GeometryNodeForeachGeometryElementOutput", 14 | "GeometryNodeSimulationOutput", 15 | "GeometryNodeSimulationInput", 16 | "GeometryNodeRepeatOutput", 17 | "GeometryNodeViewer", 18 | "GeometryNodeGroup", 19 | "NodeGroupInput", 20 | "NodeGroupOutput", 21 | "ShaderNodeOutputLineStyle", 22 | "ShaderNodeOutputWorld", 23 | "ShaderNodeGroup", 24 | "CompositorNodeGroup", 25 | } 26 | 27 | 28 | @BOperator(label="Check for missing nodes") 29 | class NPIE_OT_check_missing_nodes(BOperator.type): 30 | """Check for any nodes that aren't included in the node pie for this node tree type, and add them to the tree.""" 31 | 32 | def execute(self, context): 33 | node_tree: NodeTree = context.space_data.edit_tree 34 | 35 | # Get a list of nodes already in the pie menu 36 | categories, cat_layout = load_custom_nodes_info(context.area.spaces.active.tree_type, context) 37 | nodes: set[str] = set() 38 | for cat in categories.values(): 39 | for node in cat.nodes: 40 | if isinstance(node, Separator): 41 | continue 42 | nodes.add(node.idname) 43 | 44 | # Get a list of node types for this node tree 45 | bpy_nodes: set[str] = set() 46 | for bpy_type in dir(bpy.types): 47 | bpy_type = getattr(bpy.types, bpy_type) 48 | if not isclass(bpy_type) or not issubclass(bpy_type, bpy.types.Node): 49 | continue 50 | if bpy_type.bl_rna.identifier in EXCLUDED_NODES: 51 | continue 52 | try: 53 | node = node_tree.nodes.new(bpy_type.bl_rna.identifier) 54 | except RuntimeError: 55 | continue 56 | node_tree.nodes.remove(node) 57 | bpy_nodes.add(bpy_type.bl_rna.identifier) 58 | 59 | # Add missing nodes 60 | print("Missing nodes:") 61 | unused_nodes = bpy_nodes - nodes 62 | position = region_to_view(context.area, self.mouse_region) 63 | for node_type in unused_nodes: 64 | node = node_tree.nodes.new(node_type) 65 | node.location = position 66 | position.x += node.width + 20 67 | print(node_type) -------------------------------------------------------------------------------- /node_pie/operators/op_copy_nodes_as_json.py: -------------------------------------------------------------------------------- 1 | from ..npie_btypes import BOperator 2 | 3 | 4 | @BOperator("node_pie") 5 | class NPIE_OT_copy_nodes_as_json(BOperator.type): 6 | """Copy the selected nodes in the correct format to be pasted into the node pie definition file.""" 7 | 8 | @classmethod 9 | def poll(cls, context): 10 | if not context.space_data or context.area.type != "NODE_EDITOR": 11 | return False 12 | if not context.selected_nodes: 13 | return False 14 | return True 15 | 16 | def execute(self, context): 17 | items = [] 18 | for node in context.selected_nodes: 19 | data_item = {"identifier": node.bl_idname} 20 | items.append(str(data_item).replace("'", '"')) 21 | # items.append(json.dumps(data_item, indent=2)) 22 | items = ",\n".join(items) 23 | context.window_manager.clipboard = items 24 | print() 25 | print("Nodes to copy:") 26 | print(items) 27 | print() 28 | num = len(context.selected_nodes) 29 | self.report({"INFO"}, message=f"Copied {num} node{'' if num == 1 else 's'}") 30 | return {"FINISHED"} -------------------------------------------------------------------------------- /node_pie/operators/op_generate_socket_types_file.py: -------------------------------------------------------------------------------- 1 | import json 2 | import webbrowser 3 | from dataclasses import dataclass 4 | from pathlib import Path 5 | 6 | import bpy 7 | from bpy.types import Context, NodeTree 8 | 9 | from ..npie_btypes import BOperator 10 | from ..npie_constants import NODE_DEF_SOCKETS 11 | from ..npie_node_def_file import NodeItem, load_custom_nodes_info 12 | from ..npie_node_info import ALL_TYPES, get_node_socket_info 13 | from . import op_add_node 14 | from .op_call_link_drag import NPIE_OT_call_link_drag 15 | 16 | 17 | @dataclass 18 | class DummySocket: 19 | bl_idname: str 20 | is_output: bool 21 | 22 | 23 | def generate_node_socket_info(context: Context, tree_type: str, directory: Path): 24 | """Generate a socket info file for the given node tree. 25 | This contains the type of each socket for each node, 26 | and is used to tell if a node should be greyed out during link dragging.""" 27 | categories, layout = load_custom_nodes_info(context.area.spaces.active.tree_type, context) 28 | all_nodes: list[NodeItem] = [] 29 | for cat in categories.values(): 30 | for node_type in cat.nodes: 31 | if isinstance(node_type, NodeItem): 32 | all_nodes.append(node_type) 33 | 34 | data = {} 35 | data["bl_version"] = bpy.app.version[:2] 36 | all_node_data = {} 37 | node_tree: NodeTree = context.space_data.edit_tree 38 | for node_type in all_nodes: 39 | node = node_tree.nodes.new(node_type.idname) 40 | node_data = {} 41 | inputs = set() 42 | outputs = set() 43 | for socket_type in ALL_TYPES: 44 | op_add_node.set_node_settings(DummySocket(socket_type, True), node, ui=False) 45 | for socket in node.inputs: 46 | inputs.add(socket.bl_idname) 47 | for socket in node.outputs: 48 | outputs.add(socket.bl_idname) 49 | 50 | node_data["inputs"] = list(inputs) 51 | node_data["outputs"] = list(outputs) 52 | all_node_data[node_type.idname] = node_data 53 | node_tree.nodes.remove(node) 54 | 55 | # Remove nodes that have already been defined in a previous socket file 56 | version = list(bpy.app.version) 57 | version[1] -= 1 58 | prev_data = get_node_socket_info(tree_type, max_bl_version=version) 59 | if prev_data: 60 | for name, sockets in prev_data.items(): 61 | if name not in all_node_data.keys(): 62 | continue 63 | node_data = all_node_data[name] 64 | if set(sockets["inputs"]) != set(node_data["inputs"]): 65 | continue 66 | if set(sockets["outputs"]) != set(node_data["outputs"]): 67 | continue 68 | 69 | del all_node_data[name] 70 | 71 | if not all_node_data: 72 | return None 73 | 74 | data["nodes"] = all_node_data 75 | data_str = json.dumps(data, indent=2) 76 | data_str = ( 77 | "// This is a list of the socket types of all nodes" 78 | + "\n// used for telling whether a node is valid during link drag." 79 | + "\n// It contains all of the new and updated nodes in this blender version." 80 | + "\n// It can be auto generated using the 'generate socket types file' operator\n" 81 | + data_str 82 | ) 83 | 84 | version_str = "_".join(str(i) for i in bpy.app.version[:2]) 85 | path = directory / f"{tree_type}_sockets_{version_str}.jsonc" 86 | with open(path, "w") as f: 87 | f.write(data_str) 88 | return path 89 | 90 | 91 | @BOperator("node_pie") 92 | class NPIE_OT_generate_socket_types_file(BOperator.type): 93 | """Generate a socket types file for this node tree type. 94 | This is used to disable node items that are not compatible during link dragging or inserting.""" 95 | 96 | poll = NPIE_OT_call_link_drag.poll 97 | 98 | def invoke(self, context, event): 99 | node_tree: NodeTree = context.space_data.edit_tree 100 | self.tree_type = node_tree.bl_rna.identifier 101 | self.to_path = generate_node_socket_info(context, self.tree_type, NODE_DEF_SOCKETS) 102 | if not self.to_path: 103 | self.report({"INFO"}, "No new socket information in this blender version") 104 | return self.FINISHED 105 | return self.call_popup_confirm(width=500) 106 | 107 | def draw(self, context): 108 | layout = self.layout 109 | layout.label(text=f"Successfully generated socket types for node tree {self.tree_type}") 110 | layout.label(text="Open file?") 111 | 112 | def execute(self, context): 113 | # Open the newly generated file in the default text editor. 114 | webbrowser.open(self.to_path) 115 | -------------------------------------------------------------------------------- /node_pie/operators/op_insert_node_pie.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Context, Event, NodeTree 3 | from ..npie_ui import NPIE_MT_node_pie 4 | from ..npie_btypes import BOperator 5 | 6 | 7 | @BOperator("node_pie", undo=True) 8 | class NPIE_OT_insert_node_pie(BOperator.type): 9 | """Allow user to draw over a node link, call the node pie and insert the chosen node into the link""" 10 | 11 | @classmethod 12 | def poll(cls, context): 13 | if not context.space_data or context.area.type != "NODE_EDITOR": 14 | return False 15 | return True 16 | 17 | def invoke(self, context, event): 18 | self.quit = False 19 | self.start_names = {n.name for n in context.space_data.edit_tree.nodes} 20 | 21 | # Call the reroute operator as a quick way to get which links have been dragged over 22 | bpy.ops.node.add_reroute("INVOKE_DEFAULT") 23 | self.cursor.set_icon(self.cursor.PICK_AREA) 24 | return self.start_modal() 25 | 26 | def modal(self, context: Context, event: Event): 27 | node_tree: NodeTree = context.space_data.edit_tree 28 | self.cursor.set_icon(self.cursor.DOT) 29 | 30 | if event.type in {"RIGHTMOUSE", "ESC"} or self.quit: 31 | nodes = node_tree.nodes 32 | end_names = {n.name for n in nodes} 33 | new_nodes = list(end_names - self.start_names) 34 | if new_nodes: 35 | # Remove the newly created reroutes 36 | for new_node in new_nodes[::-1]: 37 | new_node = nodes[new_node] 38 | from_socket = new_node.inputs[0].links[0].from_socket 39 | to_sockets = [l.to_socket for l in new_node.outputs[0].links] 40 | 41 | for s in to_sockets: 42 | node_tree.links.new(from_socket, s) 43 | nodes.remove(new_node) 44 | 45 | # Call the node pie 46 | NPIE_MT_node_pie.from_socket = from_socket 47 | NPIE_MT_node_pie.to_sockets = to_sockets 48 | bpy.ops.node_pie.call_node_pie("INVOKE_DEFAULT", reset_args=False) 49 | 50 | self.cursor.reset_icon() 51 | return self.FINISHED 52 | 53 | elif event.type == "LEFTMOUSE" and event.value == "RELEASE": 54 | self.quit = True 55 | return self.PASS_THROUGH 56 | 57 | return self.PASS_THROUGH -------------------------------------------------------------------------------- /node_pie/operators/op_open_definition_file.py: -------------------------------------------------------------------------------- 1 | from bpy.props import BoolProperty 2 | from ..npie_btypes import BOperator 3 | from ..npie_constants import NODE_DEF_BASE_FILE, NODE_DEF_DIR, NODE_DEF_EXAMPLE_FILE 4 | from ..npie_helpers import get_all_def_files 5 | 6 | import shutil 7 | import webbrowser 8 | 9 | 10 | @BOperator("node_pie") 11 | class NPIE_OT_open_definition_file(BOperator.type): 12 | """Open the node pie definition file for this node tree""" 13 | 14 | example: BoolProperty() 15 | 16 | @classmethod 17 | def poll(cls, context): 18 | if not context.space_data or context.area.type != "NODE_EDITOR": 19 | return False 20 | return True 21 | 22 | def execute(self, context): 23 | if self.example: 24 | file = NODE_DEF_EXAMPLE_FILE 25 | else: 26 | files = get_all_def_files() 27 | for file in files: 28 | if file.name == f"{context.space_data.tree_type}.jsonc": 29 | break 30 | else: 31 | file = NODE_DEF_DIR / "user" / f"{context.space_data.tree_type}.jsonc" 32 | if not file.exists(): 33 | shutil.copyfile(NODE_DEF_BASE_FILE, file) 34 | 35 | webbrowser.open(file) 36 | return {"FINISHED"} -------------------------------------------------------------------------------- /node_pie/operators/op_reset_popularity.py: -------------------------------------------------------------------------------- 1 | from bpy.types import UILayout 2 | from ..npie_btypes import BOperator 3 | from ..npie_constants import POPULARITY_FILE 4 | 5 | 6 | @BOperator("node_pie") 7 | class NPIE_OT_reset_popularity(BOperator.type): 8 | """Reset the popularity of all nodes back to zero""" 9 | 10 | def invoke(self, context, event): 11 | return context.window_manager.invoke_props_dialog(self) 12 | 13 | def draw(self, context): 14 | layout: UILayout = self.layout 15 | box = layout.box() 16 | box.alert = True 17 | col = box.column(align=True) 18 | col.scale_y = .8 19 | row = col.row(align=True) 20 | row.alignment = "CENTER" 21 | row.label(text="Warning!") 22 | row = col.row(align=True) 23 | row.alignment = "CENTER" 24 | row.label(text="This will reset the popularity of all nodes back to zero.") 25 | row = col.row(align=True) 26 | row.alignment = "CENTER" 27 | row.label(text="This cannot be undone.") 28 | row = col.row(align=True) 29 | row.alignment = "CENTER" 30 | row.label(text="Continue anyway?") 31 | 32 | def execute(self, context): 33 | with open(POPULARITY_FILE, "w"): 34 | pass 35 | self.report({"INFO"}, "Node popularity successfully reset") 36 | return {"FINISHED"} -------------------------------------------------------------------------------- /node_pie/operators/op_show_info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from bpy.props import IntProperty, BoolProperty, StringProperty 4 | from bpy.types import Context, UILayout 5 | from ..npie_btypes import BOperator 6 | import blf 7 | 8 | 9 | def wrap_text( 10 | context: Context, 11 | text: str, 12 | layout: UILayout, 13 | centered: bool = False, 14 | width=0, 15 | splitter=None, 16 | ) -> list[str]: 17 | """Take a string and draw it over multiple lines so that it is never concatenated.""" 18 | return_text = [] 19 | row_text = '' 20 | 21 | width = width or context.region.width 22 | system = context.preferences.system 23 | ui_scale = system.ui_scale 24 | width = (4 / (5 * ui_scale)) * width 25 | 26 | # dpi = 72 if system.ui_scale >= 1 else system.dpi 27 | blf.size(0, 11) 28 | 29 | for word in text.split(splitter): 30 | if word == "": 31 | return_text.append(row_text) 32 | row_text = "" 33 | continue 34 | word = f' {word}' 35 | line_len, _ = blf.dimensions(0, row_text + word) 36 | 37 | if line_len <= (width - 16): 38 | row_text += word 39 | else: 40 | return_text.append(row_text) 41 | row_text = word 42 | 43 | if row_text: 44 | return_text.append(row_text) 45 | 46 | for text in return_text: 47 | row = layout.row() 48 | if centered: 49 | row.alignment = "CENTER" 50 | row.label(text=text) 51 | 52 | return return_text 53 | 54 | 55 | @BOperator("npie") 56 | class NPIE_OT_show_info(BOperator.type): 57 | 58 | title: StringProperty() 59 | 60 | message: StringProperty() 61 | 62 | icon: StringProperty() 63 | 64 | show_content: BoolProperty() 65 | 66 | width: IntProperty(default=300) 67 | 68 | def invoke(self, context, event): 69 | return self.call_popup(self.width) 70 | 71 | def draw(self, context): 72 | layout = self.layout 73 | column = layout.column(align=True) 74 | box = column.box().row(align=True) 75 | box.alignment = "CENTER" 76 | offset = "" if self.icon else " " 77 | box.label(text=self.title + offset, icon=self.icon) 78 | 79 | box = column.box().column(align=True) 80 | message = self.message.replace(" ", "").replace("\n", " ") 81 | wrap_text(context, message, box, width=self.width * 1.25, splitter=" ") 82 | # wrap_text(None, context, message, box, width=self.width * 1.25) 83 | 84 | 85 | @dataclass 86 | class InfoSnippet(): 87 | 88 | title: str 89 | message: str 90 | icon: str = "NONE" 91 | 92 | def draw(self, layout: UILayout, icon_override=""): 93 | op = layout.operator(NPIE_OT_show_info.bl_idname, text="", icon=icon_override or "INFO") 94 | op.title = self.title 95 | op.message = self.message 96 | op.icon = self.icon 97 | 98 | 99 | class InfoSnippets(): 100 | 101 | link_drag = InfoSnippet( 102 | "Link drag", 103 | """\ 104 | If you press the pie menu keyboard shortcut over a node socket, the node you select will be automatically \ 105 | connected to that socket.\n 106 | 107 | NOTE: The node socket hitboxes can be innacurate at high or low UI scales, so if you use a UI scale that is \ 108 | either especially high or especially low, you may want to turn on "Draw debug lines", and then adjust the \ 109 | "Socket separation" parameter until they line up again. 110 | The problem will be most obvious with large nodes like the Principled BSDF or the Raycast node. 111 | """, 112 | icon="NODE", 113 | ) 114 | -------------------------------------------------------------------------------- /node_pie/shaders/2D_line_antialiased_frag.glsl: -------------------------------------------------------------------------------- 1 | #ifndef is_compile 2 | uniform vec4 color; 3 | 4 | in vec2 frag_uvs; 5 | out vec4 fragColor; 6 | #endif 7 | 8 | void main() { 9 | 10 | vec4 main_color = color; 11 | // Gradient going from 0 at edges to 1 in middle 12 | main_color.a *= 1 - abs(frag_uvs.y - .5f) * 2; 13 | // Antialias 14 | main_color.a = main_color.a / fwidth(main_color.a); 15 | 16 | // color = vec4(color.a, color.a, color.a, 1); 17 | fragColor = main_color; 18 | // fragColor = blender_srgb_to_framebuffer_space(color); 19 | } -------------------------------------------------------------------------------- /node_pie/shaders/2D_vert.glsl: -------------------------------------------------------------------------------- 1 | // Use a preprocessor to remove the types at compile time. 2 | // I'm just using them for type hinting currently 3 | 4 | # ifndef is_compile 5 | uniform mat4 ModelViewProjectionMatrix; 6 | 7 | in vec2 pos; 8 | in vec2 uvs; 9 | out vec2 frag_uvs; 10 | # endif 11 | 12 | void main() { 13 | frag_uvs = uvs; 14 | gl_Position = ModelViewProjectionMatrix * vec4(pos, 0.0f, 1.0f); 15 | } --------------------------------------------------------------------------------