From 894e7e21c2c4ed05af5278fe9af1959e4f0963a9 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 13 Jun 2026 12:05:10 -0500 Subject: [PATCH 01/15] tools: adjust parsing for new 5.2 docs style --- tools/node_settings_generator/parse_nodes.py | 81 ++++++++++++++------ 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/tools/node_settings_generator/parse_nodes.py b/tools/node_settings_generator/parse_nodes.py index 562d547..3591dd1 100644 --- a/tools/node_settings_generator/parse_nodes.py +++ b/tools/node_settings_generator/parse_nodes.py @@ -1,5 +1,5 @@ import argparse -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, SoupStrainer from threading import Thread, Lock from io import TextIOWrapper import os @@ -39,7 +39,7 @@ class NodeInfo(NamedTuple): BLENDER_3_MAX_VERSION = 6 BLENDER_4_MAX_VERSION = 5 -BLENDER_5_MAX_VERSION = 1 +BLENDER_5_MAX_VERSION = 2 NTP_MIN_VERSION = Version(4, 2) @@ -134,6 +134,58 @@ def download_file(filepath: str, version: Version, local_path: str) -> bool: print(f"Downloaded {file_url} to {local_path}") return True +def get_subclass_links( + current: str, + parent: str, + version: Version, + soup: BeautifulSoup +) -> list: + main_id = f"{current.lower()}-{parent.lower()}" + sections = soup.find_all(id=main_id) + if not sections: + raise ValueError(f"{version.tuple_str()} {current}: " + f"Couldn't find main section with id {main_id}") + + section = sections[0] + + if version < (5, 2): + paragraphs = section.find_all("p") + if len(paragraphs) < 2: + raise ValueError(f"{version.tuple_str()} {current}: " + f"Couldn't find subclass section") + + subclasses_paragraph = paragraphs[1] + if not subclasses_paragraph.text.strip().startswith("subclasses —"): + # No subclasses for this type + process_node(current, section, version) + return [] + + subclass_anchors = subclasses_paragraph.find_all("a") + if not subclass_anchors: + raise ValueError(f"{version.tuple_str()} {current} " + f"No anchors in subclasses paragraph") + + subclass_types = [anchor.get("title") for anchor in subclass_anchors] + return subclass_types + else: + subclass_section_divs = soup.find_all("div", class_="toctree-wrapper compound") + if not subclass_section_divs or len(subclass_section_divs) < 1: + process_node(current, section, version) + return [] + subclass_section = subclass_section_divs[0] + all_links = subclass_section.find_all("ul") + if not all_links or len(all_links) < 1: + raise ValueError(f"{version.tuple_str()} {current}: " + f"Expected one subclass list") + links = all_links[0] + subclass_anchors = links.find_all("a") + if not subclass_anchors: + raise ValueError(f"{version.tuple_str()} {current} " + f"No anchors in subclasses list") + + subclass_types = [f"bpy.types.{anchor.contents[0].split('(')[0]}" + for anchor in subclass_anchors] + return subclass_types def get_subclasses(current: str, parent: str, root_path: str, version: Version) -> None: @@ -149,32 +201,13 @@ def get_subclasses(current: str, parent: str, root_path: str, with open(current_path, "r") as current_file: current_html = current_file.read() - soup = BeautifulSoup(current_html, "html.parser") - main_id = f"{current.lower()}-{parent.lower()}" - sections = soup.find_all(id=main_id) - if not sections: - raise ValueError(f"{version.tuple_str()} {current}: " - f"Couldn't find main section with id {main_id}") - - section = sections[0] - paragraphs = section.find_all("p") - if len(paragraphs) < 2: - raise ValueError(f"{version.tuple_str()} {current}: " - f"Couldn't find subclass section") + only_main = SoupStrainer(id=main_id) - subclasses_paragraph = paragraphs[1] - if not subclasses_paragraph.text.strip().startswith("subclasses —"): - # No subclasses for this type - process_node(current, section, version) - return + soup = BeautifulSoup(current_html, "html.parser", parse_only=only_main) - subclass_anchors = subclasses_paragraph.find_all("a") - if not subclass_anchors: - raise ValueError(f"{version.tuple_str()} {current} " - f"No anchors in subclasses paragraph") + subclass_types = get_subclass_links(current, parent, version, soup) - subclass_types = [anchor.get("title") for anchor in subclass_anchors] threads: list[Thread] = [] for type in subclass_types: if not type: From cece7a662b45306c5d3a629152b2f8d515ed0a12 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:50:49 -0500 Subject: [PATCH 02/15] feat: update for new 5.2 types and attributes --- NodeToPython/export/node_settings.py | 195 ++++++++++++++++++- tools/node_settings_generator/types_utils.py | 18 +- 2 files changed, 207 insertions(+), 6 deletions(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index ec510f9..300f671 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -13,6 +13,7 @@ class ST(Enum): FLOAT = auto() INT = auto() STRING = auto() + VEC = auto() VEC1 = auto() VEC2 = auto() VEC3 = auto() @@ -21,6 +22,7 @@ class ST(Enum): CAPTURE_ATTRIBUTE_ITEMS = auto() CLOSURE_INPUT_ITEMS = auto() CLOSURE_OUTPUT_ITEMS = auto() + CLOSURE_TO_LIST_ITEMS = auto() COLOR_MANAGED_DISPLAY_SETTINGS = auto() COLOR_MANAGED_VIEW_SETTINGS = auto() COLOR_RAMP = auto() @@ -32,6 +34,8 @@ class ST(Enum): EVALUATE_CLOSURE_INPUT_ITEMS = auto() EVALUATE_CLOSURE_OUTPUT_ITEMS = auto() FIELD_TO_GRID_ITEMS = auto() + FIELD_TO_LIST_ITEM = auto() + FIELD_TO_LIST_ITEMS = auto() FOREACH_GEO_ELEMENT_GENERATION_ITEMS = auto() FOREACH_GEO_ELEMENT_INPUT_ITEMS = auto() FOREACH_GEO_ELEMENT_MAIN_ITEMS = auto() @@ -64,12 +68,12 @@ class NTPNodeSetting(NamedTuple): name_: str st_: ST min_version_: tuple = (4, 2, 0) - max_version_: tuple = (5, 2, 0) + max_version_: tuple = (5, 3, 0) class NodeInfo(NamedTuple): attributes_: list[NTPNodeSetting] min_version_: tuple = (4, 2, 0) - max_version_: tuple = (5, 2, 0) + max_version_: tuple = (5, 3, 0) node_settings : dict[str, NodeInfo] = { 'CompositorNodeAlphaOver' : NodeInfo( @@ -95,6 +99,11 @@ class NodeInfo(NamedTuple): ] ), + 'CompositorNodeBlankImage' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'CompositorNodeBlur' : NodeInfo( [ NTPNodeSetting("aspect_correction", ST.ENUM, max_version_=(4, 5, 0)), @@ -335,6 +344,8 @@ class NodeInfo(NamedTuple): NTPNodeSetting("frame_duration", ST.INT), NTPNodeSetting("frame_offset", ST.INT), NTPNodeSetting("frame_start", ST.INT), + NTPNodeSetting("has_layers", ST.BOOL, min_version_=(5, 2, 0)), + NTPNodeSetting("has_views", ST.BOOL, min_version_=(5, 2, 0)), NTPNodeSetting("image", ST.IMAGE), NTPNodeSetting("layer", ST.ENUM), NTPNodeSetting("layer_name", ST.ENUM), @@ -519,6 +530,8 @@ class NodeInfo(NamedTuple): NTPNodeSetting("frame_duration", ST.INT), NTPNodeSetting("frame_offset", ST.INT), NTPNodeSetting("frame_start", ST.INT), + NTPNodeSetting("has_layers", ST.BOOL, min_version_=(5, 2, 0)), + NTPNodeSetting("has_views", ST.BOOL, min_version_=(5, 2, 0)), NTPNodeSetting("image", ST.IMAGE), NTPNodeSetting("layer", ST.ENUM), NTPNodeSetting("use_auto_refresh", ST.BOOL), @@ -702,6 +715,7 @@ class NodeInfo(NamedTuple): NTPNodeSetting("format", ST.IMAGE_FORMAT_SETTINGS), NTPNodeSetting("layer_slots", ST.LAYER_SLOTS, max_version_=(5, 0, 0)), NTPNodeSetting("save_as_render", ST.BOOL, min_version_=(4, 3, 0)), + NTPNodeSetting("use_file_extension", ST.BOOL, min_version_=(5, 2, 0)), ] ), @@ -835,6 +849,11 @@ class NodeInfo(NamedTuple): ] ), + 'CompositorNodeStringToImage' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'CompositorNodeSunBeams' : NodeInfo( [ NTPNodeSetting("ray_length", ST.FLOAT, max_version_=(4, 5, 0)), @@ -1040,7 +1059,8 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputColor' : NodeInfo( [ - NTPNodeSetting("value", ST.VEC4), + NTPNodeSetting("value", ST.VEC4, max_version_=(5, 2, 0)), + NTPNodeSetting("value", ST.VEC, min_version_=(5, 2, 0)), ] ), @@ -1050,6 +1070,21 @@ class NodeInfo(NamedTuple): ] ), + 'FunctionNodeInputIntVector' : NodeInfo( + [ + NTPNodeSetting("vector", ST.VEC), + NTPNodeSetting("vector_dimensions", ST.INT), + ], + min_version_ = (5, 2, 0) + ), + + 'FunctionNodeInputMenu' : NodeInfo( + [ + NTPNodeSetting("value", ST.STRING), + ], + min_version_ = (5, 2, 0) + ), + 'FunctionNodeInputRotation' : NodeInfo( [ NTPNodeSetting("rotation_euler", ST.EULER), @@ -1063,12 +1098,15 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputString' : NodeInfo( [ NTPNodeSetting("string", ST.STRING), + NTPNodeSetting("textbox_state", ST.TEXT, min_version_=(5, 2, 0)), ] ), 'FunctionNodeInputVector' : NodeInfo( [ - NTPNodeSetting("vector", ST.VEC3), + NTPNodeSetting("vector", ST.VEC3, max_version_=(5, 2, 0)), + NTPNodeSetting("vector", ST.VEC, min_version_=(5, 2, 0)), + NTPNodeSetting("vector_dimensions", ST.INT, min_version_=(5, 2, 0)), ] ), @@ -1126,6 +1164,11 @@ class NodeInfo(NamedTuple): [] ), + 'FunctionNodeReverseString' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'FunctionNodeRotateEuler' : NodeInfo( [ NTPNodeSetting("rotation_type", ST.ENUM), @@ -1169,10 +1212,20 @@ class NodeInfo(NamedTuple): [] ), + 'FunctionNodeSetStringCase' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'FunctionNodeSliceString' : NodeInfo( [] ), + 'FunctionNodeSplitString' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'FunctionNodeStringLength' : NodeInfo( [] ), @@ -1196,6 +1249,11 @@ class NodeInfo(NamedTuple): [] ), + 'FunctionNodeTrimString' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'FunctionNodeValueToString' : NodeInfo( [ NTPNodeSetting("data_type", ST.ENUM, min_version_=(4, 3, 0)), @@ -1209,6 +1267,11 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeApplySimulatedData' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeAttributeDomainSize' : NodeInfo( [ NTPNodeSetting("component", ST.ENUM), @@ -1274,6 +1337,29 @@ class NodeInfo(NamedTuple): max_version_ = (5, 0, 0) ), + 'GeometryNodeClosureToList' : NodeInfo( + [ + NTPNodeSetting("active_index", ST.INT), + NTPNodeSetting("list_items", ST.CLOSURE_TO_LIST_ITEMS), + ], + min_version_ = (5, 2, 0) + ), + + 'GeometryNodeClusterByConnected' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + + 'GeometryNodeClusterByDistance' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + + 'GeometryNodeCollectionChildren' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeCollectionInfo' : NodeInfo( [ NTPNodeSetting("transform_space", ST.ENUM), @@ -1525,6 +1611,8 @@ class NodeInfo(NamedTuple): 'GeometryNodeFieldToList' : NodeInfo( [ NTPNodeSetting("active_index", ST.INT), + NTPNodeSetting("active_item", ST.FIELD_TO_LIST_ITEM), + NTPNodeSetting("list_items", ST.FIELD_TO_LIST_ITEMS), ], min_version_ = (5, 1, 0) ), @@ -1549,6 +1637,13 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeFilterList' : NodeInfo( + [ + NTPNodeSetting("socket_type", ST.ENUM), + ], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeFlipFaces' : NodeInfo( [] ), @@ -1576,11 +1671,21 @@ class NodeInfo(NamedTuple): [] ), + 'GeometryNodeGetAttributeNames' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeGetGeometryBundle' : NodeInfo( [], min_version_ = (5, 1, 0) ), + 'GeometryNodeGetGeometryComponent' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeGetNamedGrid' : NodeInfo( [ NTPNodeSetting("data_type", ST.ENUM), @@ -1790,6 +1895,13 @@ class NodeInfo(NamedTuple): [] ), + 'GeometryNodeInputFont' : NodeInfo( + [ + NTPNodeSetting("font", ST.FONT), + ], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeInputID' : NodeInfo( [] ), @@ -1809,6 +1921,11 @@ class NodeInfo(NamedTuple): min_version_ = (4, 5, 0) ), + 'GeometryNodeInputInstanceReference' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeInputInstanceRotation' : NodeInfo( [] ), @@ -1993,6 +2110,16 @@ class NodeInfo(NamedTuple): min_version_ = (4, 3, 0) ), + 'GeometryNodeMergePoints' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + + 'GeometryNodeMeshBevel' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeMeshBoolean' : NodeInfo( [ NTPNodeSetting("operation", ST.ENUM), @@ -2136,6 +2263,11 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeRenameAttribute' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeRepeatInput' : NodeInfo( [] ), @@ -2244,6 +2376,11 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeSampleSoundFrequencies' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeSampleUVSurface' : NodeInfo( [ NTPNodeSetting("data_type", ST.ENUM), @@ -2370,6 +2507,16 @@ class NodeInfo(NamedTuple): min_version_ = (4, 5, 0) ), + 'GeometryNodeSetNURBSOrder' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + + 'GeometryNodeSetNURBSWeight' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeSetPointRadius' : NodeInfo( [] ), @@ -2409,6 +2556,13 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeSortList' : NodeInfo( + [ + NTPNodeSetting("socket_type", ST.ENUM), + ], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeSplineLength' : NodeInfo( [] ), @@ -2475,6 +2629,11 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeTagFilter' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeTool3DCursor' : NodeInfo( [] ), @@ -2508,6 +2667,11 @@ class NodeInfo(NamedTuple): ] ), + 'GeometryNodeTransferAttributes' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'GeometryNodeTransform' : NodeInfo( [ NTPNodeSetting("mode", ST.ENUM, max_version_=(5, 0, 0)), @@ -2581,6 +2745,11 @@ class NodeInfo(NamedTuple): min_version_ = (4, 3, 0) ), + 'GeometryNodeXPBDSolver' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'NodeClosureInput' : NodeInfo( [], min_version_ = (5, 0, 0) @@ -2640,6 +2809,11 @@ class NodeInfo(NamedTuple): min_version_ = (5, 1, 0) ), + 'NodeGetNestedBundlePaths' : NodeInfo( + [], + min_version_ = (5, 2, 0) + ), + 'NodeGroup' : NodeInfo( [ NTPNodeSetting("node_tree", ST.NODE_TREE), @@ -2656,6 +2830,14 @@ class NodeInfo(NamedTuple): ] ), + 'NodeImplicitConversion' : NodeInfo( + [ + NTPNodeSetting("data_type", ST.ENUM), + NTPNodeSetting("socket_idname", ST.STRING), + ], + min_version_ = (5, 2, 0) + ), + 'NodeJoinBundle' : NodeInfo( [], min_version_ = (5, 0, 0) @@ -2952,6 +3134,7 @@ class NodeInfo(NamedTuple): 'ShaderNodeNormalMap' : NodeInfo( [ + NTPNodeSetting("base", ST.ENUM, min_version_=(5, 2, 0)), NTPNodeSetting("convention", ST.ENUM, min_version_=(5, 1, 0)), NTPNodeSetting("space", ST.ENUM), NTPNodeSetting("uv_map", ST.STRING), @@ -3030,6 +3213,7 @@ class NodeInfo(NamedTuple): 'ShaderNodeRaycast' : NodeInfo( [ + NTPNodeSetting("active_index", ST.INT, min_version_=(5, 2, 0)), NTPNodeSetting("only_local", ST.BOOL), ], min_version_ = (5, 1, 0) @@ -3188,7 +3372,8 @@ class NodeInfo(NamedTuple): NTPNodeSetting("ground_albedo", ST.FLOAT), NTPNodeSetting("ozone_density", ST.FLOAT), NTPNodeSetting("sky_type", ST.ENUM), - NTPNodeSetting("sun_direction", ST.VEC3), + NTPNodeSetting("sun_direction", ST.VEC3, max_version_=(5, 2, 0)), + NTPNodeSetting("sun_direction", ST.VEC, min_version_=(5, 2, 0)), NTPNodeSetting("sun_disc", ST.BOOL), NTPNodeSetting("sun_elevation", ST.FLOAT), NTPNodeSetting("sun_intensity", ST.FLOAT), diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index eb57ac2..49daae2 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -13,6 +13,7 @@ class ST(Enum): FLOAT = auto() INT = auto() STRING = auto() + VEC = auto() VEC1 = auto() VEC2 = auto() VEC3 = auto() @@ -23,6 +24,7 @@ class ST(Enum): CAPTURE_ATTRIBUTE_ITEMS = auto() CLOSURE_INPUT_ITEMS = auto() CLOSURE_OUTPUT_ITEMS = auto() + CLOSURE_TO_LIST_ITEMS = auto() COLOR_MANAGED_DISPLAY_SETTINGS = auto() COLOR_MANAGED_VIEW_SETTINGS = auto() COLOR_RAMP = auto() @@ -34,6 +36,8 @@ class ST(Enum): EVALUATE_CLOSURE_INPUT_ITEMS = auto() EVALUATE_CLOSURE_OUTPUT_ITEMS = auto() FIELD_TO_GRID_ITEMS = auto() + FIELD_TO_LIST_ITEM = auto() + FIELD_TO_LIST_ITEMS = auto() FOREACH_GEO_ELEMENT_GENERATION_ITEMS = auto() FOREACH_GEO_ELEMENT_INPUT_ITEMS = auto() FOREACH_GEO_ELEMENT_MAIN_ITEMS = auto() @@ -83,6 +87,7 @@ class ST(Enum): ST.EVALUATE_CLOSURE_INPUT_ITEMS, ST.EVALUATE_CLOSURE_OUTPUT_ITEMS, ST.FIELD_TO_GRID_ITEMS, + ST.FIELD_TO_LIST_ITEMS, ST.FILE_SLOTS, ST.FOREACH_GEO_ELEMENT_GENERATION_ITEMS, ST.FOREACH_GEO_ELEMENT_INPUT_ITEMS, @@ -101,7 +106,11 @@ class ST(Enum): doc_to_NTP_type_dict : dict[str, ST | None] = { "" : None, + "bpy_prop_array[int]" : ST.VEC, + "bpy_prop_array[float]" : ST.VEC, "bpy_prop_collection of CryptomatteEntry": ST.CRYPTOMATTE_ENTRIES, + "bpy_prop_collection[CryptomatteEntry]" : ST.CRYPTOMATTE_ENTRIES, + "bool" : ST.BOOL, "boolean" : ST.BOOL, "Collection" : ST.COLLECTION, "ColorManagedDisplaySettings" : ST.COLOR_MANAGED_DISPLAY_SETTINGS, @@ -118,15 +127,20 @@ class ST(Enum): "float array of 2" : ST.VEC2, "float array of 3" : ST.VEC3, "float array of 4" : ST.VEC4, + "GeometryNodeClosureToListItems" : ST.CLOSURE_TO_LIST_ITEMS, "GeometryNodeFieldToGridItems" : ST.FIELD_TO_GRID_ITEMS, + "GeometryNodeFieldToListItem" : ST.FIELD_TO_LIST_ITEM, + "GeometryNodeFieldToListItems" : ST.FIELD_TO_LIST_ITEMS, "Image" : ST.IMAGE, "ImageFormatSettings" : ST.IMAGE_FORMAT_SETTINGS, "ImageUser" : ST.IMAGE_USER, "int" : ST.INT, + "Literal" : ST.ENUM, "Mask" : ST.MASK, "Material" : ST.MATERIAL, "mathutils.Color" : ST.COLOR, - "mathutils.Euler" : ST.EULER, #TODO + "mathutils.Euler" : ST.EULER, + "mathutils.Vector" : ST.VEC, "mathutils.Vector of 3" : ST.VEC3, "MovieClip" : ST.MOVIE_CLIP, "Node" : None, # (<4.2) Always used with zone inputs, need to make sure @@ -157,7 +171,9 @@ class ST(Enum): "PropertyGroup" : None, #Always read-only "RepeatItem" : None, #Always set with index "Scene" : ST.SCENE, + "set[Literal" : ST.ENUM_SET, "SimulationStateItem" : None, #Always set with index + "str" : ST.STRING, "string" : ST.STRING, "TexMapping" : None, #Always read-only "Text" : ST.TEXT, From 2e6f4123138b46383806205ab6c71c90f9a06470 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:31:00 -0500 Subject: [PATCH 03/15] fix: get rid of unnecessary read-only types --- NodeToPython/export/node_settings.py | 6 ------ tools/node_settings_generator/types_utils.py | 6 ++---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 300f671..8f04734 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -30,11 +30,9 @@ class ST(Enum): COMPOSITOR_FILE_OUTPUT_ITEMS = auto() CURVE_MAPPING = auto() ENUM_DEFINITION = auto() - ENUM_ITEM = auto() EVALUATE_CLOSURE_INPUT_ITEMS = auto() EVALUATE_CLOSURE_OUTPUT_ITEMS = auto() FIELD_TO_GRID_ITEMS = auto() - FIELD_TO_LIST_ITEM = auto() FIELD_TO_LIST_ITEMS = auto() FOREACH_GEO_ELEMENT_GENERATION_ITEMS = auto() FOREACH_GEO_ELEMENT_INPUT_ITEMS = auto() @@ -1610,8 +1608,6 @@ class NodeInfo(NamedTuple): 'GeometryNodeFieldToList' : NodeInfo( [ - NTPNodeSetting("active_index", ST.INT), - NTPNodeSetting("active_item", ST.FIELD_TO_LIST_ITEM), NTPNodeSetting("list_items", ST.FIELD_TO_LIST_ITEMS), ], min_version_ = (5, 1, 0) @@ -2090,8 +2086,6 @@ class NodeInfo(NamedTuple): 'GeometryNodeMenuSwitch' : NodeInfo( [ - NTPNodeSetting("active_index", ST.INT), - NTPNodeSetting("active_item", ST.ENUM_ITEM), NTPNodeSetting("data_type", ST.ENUM), NTPNodeSetting("enum_items", ST.MENU_SWITCH_ITEMS), ] diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index 49daae2..664c15d 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -32,11 +32,9 @@ class ST(Enum): COMPOSITOR_FILE_OUTPUT_ITEMS = auto() CURVE_MAPPING = auto() ENUM_DEFINITION = auto() - ENUM_ITEM = auto() EVALUATE_CLOSURE_INPUT_ITEMS = auto() EVALUATE_CLOSURE_OUTPUT_ITEMS = auto() FIELD_TO_GRID_ITEMS = auto() - FIELD_TO_LIST_ITEM = auto() FIELD_TO_LIST_ITEMS = auto() FOREACH_GEO_ELEMENT_GENERATION_ITEMS = auto() FOREACH_GEO_ELEMENT_INPUT_ITEMS = auto() @@ -129,7 +127,7 @@ class ST(Enum): "float array of 4" : ST.VEC4, "GeometryNodeClosureToListItems" : ST.CLOSURE_TO_LIST_ITEMS, "GeometryNodeFieldToGridItems" : ST.FIELD_TO_GRID_ITEMS, - "GeometryNodeFieldToListItem" : ST.FIELD_TO_LIST_ITEM, + "GeometryNodeFieldToListItem" : None, # Always read-only "GeometryNodeFieldToListItems" : ST.FIELD_TO_LIST_ITEMS, "Image" : ST.IMAGE, "ImageFormatSettings" : ST.IMAGE_FORMAT_SETTINGS, @@ -150,7 +148,7 @@ class ST(Enum): "NodeCombineBundleItems" : ST.COMBINE_BUNDLE_ITEMS, "NodeCompositorFileOutputItems" : ST.COMPOSITOR_FILE_OUTPUT_ITEMS, "NodeEnumDefinition" : ST.ENUM_DEFINITION, - "NodeEnumItem" : ST.ENUM_ITEM, + "NodeEnumItem" : None, #Always read-only "NodeEvaluateClosureInputItems" : ST.EVALUATE_CLOSURE_OUTPUT_ITEMS, "NodeEvaluateClosureOutputItems" : ST.EVALUATE_CLOSURE_OUTPUT_ITEMS, "NodeFunctionFormatStringItems" : ST.FORMAT_STRING_ITEMS, From 7cb274a4e11f2ec666dac12bda96ddc44e469b27 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:37:35 -0500 Subject: [PATCH 04/15] feat: update max Blender version to 5.3, NTP version to 4.2 --- NodeToPython/blender_manifest.toml | 2 +- NodeToPython/export/ntp_operator.py | 2 +- docs/README.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NodeToPython/blender_manifest.toml b/NodeToPython/blender_manifest.toml index ed9e910..e673134 100644 --- a/NodeToPython/blender_manifest.toml +++ b/NodeToPython/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "node_to_python" -version = "4.1.1" +version = "4.2.0" name = "Node To Python" tagline = "Turn node groups into Python code" maintainer = "Brendan Parmer " diff --git a/NodeToPython/export/ntp_operator.py b/NodeToPython/export/ntp_operator.py index d93eec6..caadd2b 100644 --- a/NodeToPython/export/ntp_operator.py +++ b/NodeToPython/export/ntp_operator.py @@ -27,7 +27,7 @@ } MIN_BLENDER_VERSION = (4, 2, 0) -MAX_BLENDER_VERSION = (5, 2, 0) +MAX_BLENDER_VERSION = (5, 3, 0) class NodeTreeInfo(): def __init__(self): diff --git a/docs/README.md b/docs/README.md index 572a404..b53e94c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ Node To Python automatically handles node layout, default values, subgroups, nam Blender's node-based editors are powerful, yet accessible tools, and I wanted to make scripting them easier for add-on creators. Combining Python with node based setups allows you to do things that would otherwise be tedious or impossible. ## Supported Versions -NodeToPython v4.1.1 is supported for Blender 4.2 - 5.1 on Windows, macOS, and Linux +NodeToPython v4.2.0 is supported for Blender 4.2 - 5.2 on Windows, macOS, and Linux * Some work is required to update NodeToPython for each Blender version, so experimental versions may not work properly and may even crash. * New nodes, updated settings, and new node tree features require development to support. * NodeToPython updates for new Blender versions usually doesn't start until the Beta phase of the release cycle @@ -44,7 +44,7 @@ In the options panel, select either **Script** or **Add-on**. When submitting an issue, please include -* Your version of Blender (4.2 - 5.1) +* Your version of Blender (4.2 - 5.2) * Your operating system (Windows, MacOS, or Linux) * Steps to reproduce the issue or a description of what you were trying to accomplish. Providing a test blend file is especially helpful From 7bf93d7fd1e413bd41be211b9871af2c96e9dc3c Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:39:48 -0500 Subject: [PATCH 05/15] tools: look for readonly string in attribute description --- NodeToPython/export/node_settings.py | 9 ++++----- tools/node_settings_generator/parse_nodes.py | 4 +++- tools/node_settings_generator/types_utils.py | 10 +++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 8f04734..1f31481 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -60,6 +60,7 @@ class ST(Enum): PARTICLE_SYSTEM = auto() SCENE = auto() TEXT = auto() + TEXTBOX_STATE = auto() TEXTURE = auto() class NTPNodeSetting(NamedTuple): @@ -342,8 +343,6 @@ class NodeInfo(NamedTuple): NTPNodeSetting("frame_duration", ST.INT), NTPNodeSetting("frame_offset", ST.INT), NTPNodeSetting("frame_start", ST.INT), - NTPNodeSetting("has_layers", ST.BOOL, min_version_=(5, 2, 0)), - NTPNodeSetting("has_views", ST.BOOL, min_version_=(5, 2, 0)), NTPNodeSetting("image", ST.IMAGE), NTPNodeSetting("layer", ST.ENUM), NTPNodeSetting("layer_name", ST.ENUM), @@ -528,8 +527,6 @@ class NodeInfo(NamedTuple): NTPNodeSetting("frame_duration", ST.INT), NTPNodeSetting("frame_offset", ST.INT), NTPNodeSetting("frame_start", ST.INT), - NTPNodeSetting("has_layers", ST.BOOL, min_version_=(5, 2, 0)), - NTPNodeSetting("has_views", ST.BOOL, min_version_=(5, 2, 0)), NTPNodeSetting("image", ST.IMAGE), NTPNodeSetting("layer", ST.ENUM), NTPNodeSetting("use_auto_refresh", ST.BOOL), @@ -1096,7 +1093,7 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputString' : NodeInfo( [ NTPNodeSetting("string", ST.STRING), - NTPNodeSetting("textbox_state", ST.TEXT, min_version_=(5, 2, 0)), + NTPNodeSetting("textbox_state", ST.TEXTBOX_STATE, min_version_=(5, 2, 0)), ] ), @@ -1608,6 +1605,7 @@ class NodeInfo(NamedTuple): 'GeometryNodeFieldToList' : NodeInfo( [ + NTPNodeSetting("active_index", ST.INT), NTPNodeSetting("list_items", ST.FIELD_TO_LIST_ITEMS), ], min_version_ = (5, 1, 0) @@ -2086,6 +2084,7 @@ class NodeInfo(NamedTuple): 'GeometryNodeMenuSwitch' : NodeInfo( [ + NTPNodeSetting("active_index", ST.INT), NTPNodeSetting("data_type", ST.ENUM), NTPNodeSetting("enum_items", ST.MENU_SWITCH_ITEMS), ] diff --git a/tools/node_settings_generator/parse_nodes.py b/tools/node_settings_generator/parse_nodes.py index 3591dd1..590bcb1 100644 --- a/tools/node_settings_generator/parse_nodes.py +++ b/tools/node_settings_generator/parse_nodes.py @@ -66,6 +66,8 @@ def process_attr(attr, section, node: str, version: Version) -> None: f"was marked deprecated, returning\n") return + readonly = "readonly" in str(attr) + # Get type type_section = attr.find("dd", class_="field-odd") if not type_section: @@ -80,7 +82,7 @@ def process_attr(attr, section, node: str, version: Version) -> None: else: types_dict[first_word].add(type_text) - ntp_type = types_utils.get_NTP_type(type_text) + ntp_type = types_utils.get_NTP_type(type_text, readonly) if ntp_type is None: # Read-only attribute, don't add to attribute list log(f"WARNING: {version.tuple_str()} {node}.{name}'s " diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index 664c15d..aa0eaa0 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -66,6 +66,7 @@ class ST(Enum): PARTICLE_SYSTEM = auto() SCENE = auto() TEXT = auto() + TEXTBOX_STATE = auto() TEXTURE = auto() #types expected to be marked as read-only @@ -74,6 +75,7 @@ class ST(Enum): ST.CAPTURE_ATTRIBUTE_ITEMS, ST.CLOSURE_INPUT_ITEMS, ST.CLOSURE_OUTPUT_ITEMS, + ST.CLOSURE_TO_LIST_ITEMS, ST.COLOR_MANAGED_DISPLAY_SETTINGS, ST.COLOR_MANAGED_VIEW_SETTINGS, ST.COLOR_RAMP, @@ -99,7 +101,8 @@ class ST(Enum): ST.MENU_SWITCH_ITEMS, ST.REPEAT_OUTPUT_ITEMS, ST.SEPARATE_BUNDLE_ITEMS, - ST.SIM_OUTPUT_ITEMS + ST.SIM_OUTPUT_ITEMS, + ST.TEXTBOX_STATE } doc_to_NTP_type_dict : dict[str, ST | None] = { @@ -175,11 +178,12 @@ class ST(Enum): "string" : ST.STRING, "TexMapping" : None, #Always read-only "Text" : ST.TEXT, + "TextboxState" : ST.TEXTBOX_STATE, "Texture" : ST.TEXTURE, "VectorFont" : ST.FONT } -def get_NTP_type(type_str: str) -> ST | None: +def get_NTP_type(type_str: str, readonly: bool) -> ST | None: """ Time complexity isn't great, might be able to optimize with a trie or similar data structure @@ -194,7 +198,7 @@ def get_NTP_type(type_str: str) -> ST | None: result = doc_to_NTP_type_dict[longest_prefix] - is_readonly = "read" in type_str + is_readonly = ("read" in type_str) or readonly if is_readonly and result not in READ_ONLY_TYPES: return None else: From 7e9824d5c4373cc6486621a29fd023c9b904adb1 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:39:48 -0500 Subject: [PATCH 06/15] fix: no longer attempts to change readonly type TextboxState --- NodeToPython/export/node_settings.py | 2 -- tools/node_settings_generator/types_utils.py | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 1f31481..fe5c52e 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -60,7 +60,6 @@ class ST(Enum): PARTICLE_SYSTEM = auto() SCENE = auto() TEXT = auto() - TEXTBOX_STATE = auto() TEXTURE = auto() class NTPNodeSetting(NamedTuple): @@ -1093,7 +1092,6 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputString' : NodeInfo( [ NTPNodeSetting("string", ST.STRING), - NTPNodeSetting("textbox_state", ST.TEXTBOX_STATE, min_version_=(5, 2, 0)), ] ), diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index aa0eaa0..f77ea90 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -66,7 +66,6 @@ class ST(Enum): PARTICLE_SYSTEM = auto() SCENE = auto() TEXT = auto() - TEXTBOX_STATE = auto() TEXTURE = auto() #types expected to be marked as read-only @@ -101,8 +100,7 @@ class ST(Enum): ST.MENU_SWITCH_ITEMS, ST.REPEAT_OUTPUT_ITEMS, ST.SEPARATE_BUNDLE_ITEMS, - ST.SIM_OUTPUT_ITEMS, - ST.TEXTBOX_STATE + ST.SIM_OUTPUT_ITEMS } doc_to_NTP_type_dict : dict[str, ST | None] = { @@ -178,7 +176,7 @@ class ST(Enum): "string" : ST.STRING, "TexMapping" : None, #Always read-only "Text" : ST.TEXT, - "TextboxState" : ST.TEXTBOX_STATE, + "TextboxState" : None, # Always read-only "Texture" : ST.TEXTURE, "VectorFont" : ST.FONT } From 47caf7c51cb273997323d1c0453ffa83819c7129 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:40:33 -0500 Subject: [PATCH 07/15] feat: handle sound sockets/inputs --- NodeToPython/export/node_tree_exporter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index 3fb8de4..ed64179 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -56,6 +56,9 @@ if bpy.app.version >= (5, 1, 0): NO_DEFAULT_SOCKETS.add(bpy.types.NodeTreeInterfaceSocketFont) +if bpy.app.version >= (5, 2, 0): + NO_DEFAULT_SOCKETS.add(bpy.types.NodeTreeInterfaceSocketSound) + #node input sockets that are messy to set default values for DONT_SET_DEFAULTS = { 'NodeSocketGeometry', @@ -1540,6 +1543,10 @@ def _set_input_defaults(self, node: bpy.types.Node) -> None: self._in_file_inputs(input, socket_var, "fonts") default_val = None + elif input.bl_idname == 'NodeSocketSound': + self._in_file_inputs(input, socket_var, "sounds") + default_val = None + else: default_val = getattr(input, "default_value") From 06a42921061a44e790b057787601c8ad915d470f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:40:49 -0500 Subject: [PATCH 08/15] feat: handle generic vector inputs --- NodeToPython/export/node_tree_exporter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index ed64179..2b38bc3 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -742,6 +742,8 @@ def _set_settings_defaults(self, node: bpy.types.Node) -> None: self._write(f"{setting_str} = {str_to_py_str(attr)}") elif st == ST.BOOL or st == ST.INT or st == ST.FLOAT: self._write(f"{setting_str} = {attr}") + elif st == ST.VEC: + self._write(f"{setting_str} = {vec_to_py_str(attr, len(attr))}") elif st == ST.VEC1: self._write(f"{setting_str} = {vec1_to_py_str(attr)}") elif st == ST.VEC2: From 0ee4e50c0edb141765335bf3a1f7a521e825043f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:41:13 -0500 Subject: [PATCH 09/15] feat: handle new list setting types --- NodeToPython/export/node_tree_exporter.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index 2b38bc3..b262146 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -824,6 +824,10 @@ def _set_settings_defaults(self, node: bpy.types.Node) -> None: self._combine_bundle_items(attr, setting_str) elif st == ST.SEPARATE_BUNDLE_ITEMS: self._separate_bundle_items(attr, setting_str) + elif st == ST.CLOSURE_TO_LIST_ITEMS: + self._closure_to_list_items(attr, setting_str) + elif st == ST.FIELD_TO_LIST_ITEMS: + self._field_to_list_items(attr, setting_str) def _set_if_in_blend_file(self, attr, setting_str: str, data_type: str ) -> None: @@ -1434,6 +1438,34 @@ def _color_managed_view_settings(self, look_str = enum_to_py_str(view_settings.look) self._write(f"{view_settings_str}.look = {look_str}") + if bpy.app.version >= (5, 2, 0): + def _closure_to_list_items(self, + closure_to_list_items: bpy.types.GeometryNodeClosureToListItems, + closure_to_list_items_str: str + ) -> None: + self._write(f"{closure_to_list_items_str}.clear()") + for i, item in enumerate(closure_to_list_items): + socket_type = enum_to_py_str(item.socket_type) + name_str = str_to_py_str(item.name) + self._write((f"{closure_to_list_items_str}.new(" + f"{socket_type}, {name_str})")) + + item_str = f"{closure_to_list_items_str}[{i}]" + + structure_type = enum_to_py_str(item.structure_type) + self._write(f"{item_str}.structure_type = {structure_type}") + + def _field_to_list_items(self, + field_to_list_items: bpy.types.GeometryNodeFieldToListItems, + field_to_list_items_str: str + ) -> None: + self._write(f"{field_to_list_items_str}.clear()") + for i, item in enumerate(field_to_list_items): + socket_type = enum_to_py_str(item.socket_type) + name_str = str_to_py_str(item.name) + self._write((f"{field_to_list_items_str}.new(" + f"{socket_type}, {name_str})")) + def _hide_hidden_sockets(self, node: bpy.types.Node) -> None: """ Hide hidden sockets From 5457c576a33ead0b0b0b035ee4e262339854a2f1 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:53:52 -0500 Subject: [PATCH 10/15] feat: set panel states --- NodeToPython/export/node_tree_exporter.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index b262146..4caeca5 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -635,6 +635,9 @@ def _process_node(self, node: bpy.types.Node, ntp_nt: NTP_NodeTree) -> None: node_var: str = self._create_node(node, ntp_nt._var) self._set_settings_defaults(node) + if bpy.app.version >= (5, 2, 0): + self._set_panel_states(node) + if node.bl_idname in ntp_nt._zone_inputs: ntp_nt._zone_inputs[node.bl_idname].append(node) @@ -1636,6 +1639,14 @@ def _in_file_inputs(self, input: bpy.types.NodeSocket, socket_var: str, self._write(f"{socket_var}.default_value = bpy.data.{type}[{name}]", self._operator._inner_indent_level + 1) + if bpy.app.version >= (5, 2, 0): + def _set_panel_states(self, node: bpy.types.Node): + node_var = self._node_vars[node] + for i, panel_state in enumerate(node.panel_states): + pstate_var = f"{node_var}.panel_states[{i}]" + self._write(f"{pstate_var}.is_collapsed = " + f"{panel_state.is_collapsed}") + def _process_zones(self, zone_input_list: list[bpy.types.Node]) -> None: """ Recreates a zone @@ -1666,7 +1677,7 @@ def _get_node_var( ) -> str: nt_var = self._node_tree_vars[node_tree] return f"{nt_var}.nodes[{str_to_py_str(node.name)}]" - + def _set_parents(self, node_tree: bpy.types.NodeTree) -> None: """ Sets parents for all nodes, mostly used to put nodes in frames From 9236c97c68a3ebee909cac256e1214e8a477b86b Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:24:45 -0500 Subject: [PATCH 11/15] feat: support raycast sample attribute items --- NodeToPython/export/node_settings.py | 2 ++ NodeToPython/export/node_tree_exporter.py | 25 ++++++++++++++++++++ NodeToPython/export/utils.py | 19 ++++++++++++++- tools/node_settings_generator/types_utils.py | 3 +++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index fe5c52e..52492fe 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -42,6 +42,7 @@ class ST(Enum): INDEX_SWITCH_ITEMS = auto() MENU_SWITCH_ITEMS = auto() NODE_TREE = auto() + RAYCAST_ATTR_ITEMS = auto() REPEAT_OUTPUT_ITEMS = auto() SEPARATE_BUNDLE_ITEMS = auto() SIM_OUTPUT_ITEMS = auto() @@ -3206,6 +3207,7 @@ class NodeInfo(NamedTuple): [ NTPNodeSetting("active_index", ST.INT, min_version_=(5, 2, 0)), NTPNodeSetting("only_local", ST.BOOL), + NTPNodeSetting("sample_attribute_items", ST.RAYCAST_ATTR_ITEMS, min_version_=(5, 2, 0)), ], min_version_ = (5, 1, 0) ), diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index 4caeca5..a898d6d 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -831,6 +831,8 @@ def _set_settings_defaults(self, node: bpy.types.Node) -> None: self._closure_to_list_items(attr, setting_str) elif st == ST.FIELD_TO_LIST_ITEMS: self._field_to_list_items(attr, setting_str) + elif st == ST.RAYCAST_ATTR_ITEMS: + self._raycast_attr_items(attr, setting_str) def _set_if_in_blend_file(self, attr, setting_str: str, data_type: str ) -> None: @@ -1468,6 +1470,29 @@ def _field_to_list_items(self, name_str = str_to_py_str(item.name) self._write((f"{field_to_list_items_str}.new(" f"{socket_type}, {name_str})")) + + def _raycast_attr_items(self, + raycast_attr_items: bpy.types.NodeRaycastSampleAttributeItems, + raycast_attr_items_str: str + ) -> None: + if raycast_attr_items is None: + return + + self._write(f"{raycast_attr_items_str}.clear()") + for i, item in enumerate(raycast_attr_items): + socket_type = enum_to_py_str( + data_type_to_socket_type[item.data_type] + ) + name_str = str_to_py_str(item.name) + self._write((f"{raycast_attr_items_str}.new(" + f"{socket_type}, {name_str})")) + + item_str = f"{raycast_attr_items_str}[{i}]" + + data_type = enum_to_py_str(item.data_type) + self._write(f"{item_str}.data_type = {data_type}") + + def _hide_hidden_sockets(self, node: bpy.types.Node) -> None: """ diff --git a/NodeToPython/export/utils.py b/NodeToPython/export/utils.py index 2536572..4ac1cb2 100644 --- a/NodeToPython/export/utils.py +++ b/NodeToPython/export/utils.py @@ -174,4 +174,21 @@ def img_to_py_str(img : bpy.types.Image) -> str: """ name = img.name.split('.', 1)[0] format = img.file_format.lower() - return f"{name}.{format}" \ No newline at end of file + return f"{name}.{format}" + +data_type_to_socket_type : dict[str, str] = { + 'FLOAT' : 'FLOAT', + 'INT' : 'INT', + 'BOOLEAN' : 'BOOLEAN', + 'FLOAT_VECTOR' : 'VECTOR', + 'FLOAT_COLOR' : 'RGBA', + 'QUATERNION' : 'ROTATION', + 'FLOAT4X4' : 'MATRIX', + 'STRING' : 'STRING', + 'INT8' : 'INT', + 'INT16_2D' : 'INT_VECTOR', + 'INT32_2D' : 'INT_VECTOR', + 'FLOAT2' : 'VECTOR', + 'FLOAT4' : 'VECTOR', + 'BYTE_COLOR' : 'COLOR' +} \ No newline at end of file diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index f77ea90..48b3630 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -44,6 +44,7 @@ class ST(Enum): INDEX_SWITCH_ITEMS = auto() MENU_SWITCH_ITEMS = auto() NODE_TREE = auto() + RAYCAST_ATTR_ITEMS = auto() REPEAT_OUTPUT_ITEMS = auto() SEPARATE_BUNDLE_ITEMS = auto() SIM_OUTPUT_ITEMS = auto() @@ -98,6 +99,7 @@ class ST(Enum): ST.INDEX_SWITCH_ITEMS, ST.LAYER_SLOTS, ST.MENU_SWITCH_ITEMS, + ST.RAYCAST_ATTR_ITEMS, ST.REPEAT_OUTPUT_ITEMS, ST.SEPARATE_BUNDLE_ITEMS, ST.SIM_OUTPUT_ITEMS @@ -163,6 +165,7 @@ class ST(Enum): "NodeGeometryViewerItems" : ST.GEOMETRY_VIEWER_ITEMS, "NodeIndexSwitchItems" : ST.INDEX_SWITCH_ITEMS, "NodeMenuSwitchItems" : ST.MENU_SWITCH_ITEMS, + "NodeRaycastSampleAttributeItems" : ST.RAYCAST_ATTR_ITEMS, "NodeSeparateBundleItems" : ST.SEPARATE_BUNDLE_ITEMS, "NodeTree" : ST.NODE_TREE, "Object" : ST.OBJECT, From fe3cab191c4c0c7fa8aa6d9d39b860139ba03036 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:26:48 -0500 Subject: [PATCH 12/15] fix: initialization of input vector attributes --- NodeToPython/export/node_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 52492fe..1a85f10 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -1099,8 +1099,10 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputVector' : NodeInfo( [ NTPNodeSetting("vector", ST.VEC3, max_version_=(5, 2, 0)), - NTPNodeSetting("vector", ST.VEC, min_version_=(5, 2, 0)), + # NOTE: in order to export correctly, the dimensions + # must be set before the vector. NTPNodeSetting("vector_dimensions", ST.INT, min_version_=(5, 2, 0)), + NTPNodeSetting("vector", ST.VEC, min_version_=(5, 2, 0)), ] ), From b82083b56453e3a5cf5c95757a69abe269fb19af Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:38:07 -0500 Subject: [PATCH 13/15] cleanup: remove unused reserved names --- NodeToPython/export/geometry/exporter.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/NodeToPython/export/geometry/exporter.py b/NodeToPython/export/geometry/exporter.py index fc73db2..c4971be 100644 --- a/NodeToPython/export/geometry/exporter.py +++ b/NodeToPython/export/geometry/exporter.py @@ -7,13 +7,7 @@ from .node_tree import NTP_GeoNodeTree, NTP_NodeTree -OBJECT_NAME = "name" -OBJECT = "obj" -MODIFIER = "mod" GEO_OP_RESERVED_NAMES = { - OBJECT_NAME, - OBJECT, - MODIFIER } class GeometryNodesExporter(NodeTreeExporter): From f2a368dc0e2a0cbf75e015a6a5a2ea04412e9a4f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:23:23 -0500 Subject: [PATCH 14/15] fix: int vec dimensions --- NodeToPython/export/node_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 1a85f10..3f92b03 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -1067,8 +1067,10 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputIntVector' : NodeInfo( [ - NTPNodeSetting("vector", ST.VEC), + # NOTE: in order to export correctly, the dimensions + # must be set before the vector. NTPNodeSetting("vector_dimensions", ST.INT), + NTPNodeSetting("vector", ST.VEC), ], min_version_ = (5, 2, 0) ), From 09896f1bb4210506d938f3f0ee2e2879831469af Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 11 Jul 2026 15:29:22 -0500 Subject: [PATCH 15/15] fix: write input menu values after linking --- NodeToPython/export/node_settings.py | 3 ++- NodeToPython/export/node_tree_exporter.py | 14 +++++++++++++- tools/node_settings_generator/parse_nodes.py | 6 ++++++ tools/node_settings_generator/types_utils.py | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/NodeToPython/export/node_settings.py b/NodeToPython/export/node_settings.py index 3f92b03..ca9424d 100644 --- a/NodeToPython/export/node_settings.py +++ b/NodeToPython/export/node_settings.py @@ -40,6 +40,7 @@ class ST(Enum): FORMAT_STRING_ITEMS = auto() GEOMETRY_VIEWER_ITEMS = auto() INDEX_SWITCH_ITEMS = auto() + MENU_INPUT = auto() MENU_SWITCH_ITEMS = auto() NODE_TREE = auto() RAYCAST_ATTR_ITEMS = auto() @@ -1077,7 +1078,7 @@ class NodeInfo(NamedTuple): 'FunctionNodeInputMenu' : NodeInfo( [ - NTPNodeSetting("value", ST.STRING), + NTPNodeSetting("value", ST.MENU_INPUT), ], min_version_ = (5, 2, 0) ), diff --git a/NodeToPython/export/node_tree_exporter.py b/NodeToPython/export/node_tree_exporter.py index a898d6d..8c6d295 100644 --- a/NodeToPython/export/node_tree_exporter.py +++ b/NodeToPython/export/node_tree_exporter.py @@ -833,6 +833,8 @@ def _set_settings_defaults(self, node: bpy.types.Node) -> None: self._field_to_list_items(attr, setting_str) elif st == ST.RAYCAST_ATTR_ITEMS: self._raycast_attr_items(attr, setting_str) + elif st == ST.MENU_INPUT: + self._menu_input(attr, setting_str) def _set_if_in_blend_file(self, attr, setting_str: str, data_type: str ) -> None: @@ -1491,7 +1493,17 @@ def _raycast_attr_items(self, data_type = enum_to_py_str(item.data_type) self._write(f"{item_str}.data_type = {data_type}") - + + def _menu_input(self, + menu_input: str, + menu_input_str: str + ) -> None: + if menu_input != "": + self._write_after_links.append( + lambda _menu_input_str=menu_input_str, + _menu_input_value = str_to_py_str(menu_input): + self._write(f"{_menu_input_str} = {_menu_input_value}") + ) def _hide_hidden_sockets(self, node: bpy.types.Node) -> None: diff --git a/tools/node_settings_generator/parse_nodes.py b/tools/node_settings_generator/parse_nodes.py index 590bcb1..3baa4ea 100644 --- a/tools/node_settings_generator/parse_nodes.py +++ b/tools/node_settings_generator/parse_nodes.py @@ -88,8 +88,14 @@ def process_attr(attr, section, node: str, version: Version) -> None: log(f"WARNING: {version.tuple_str()} {node}.{name}'s " f"type is being ignored:\n\t{type_text.strip()}\n") return + + # Unfortunately needs handled spearately, + # node tree creation fails unless value is written after linking + if node == 'FunctionNodeInputMenu' and name == "value": + ntp_type = types_utils.ST.MENU_INPUT ntp_setting = NTPNodeSetting(name, ntp_type) + with mutex: if ntp_setting not in nodes_dict[node].attributes_: nodes_dict[node].attributes_[ntp_setting] = [] diff --git a/tools/node_settings_generator/types_utils.py b/tools/node_settings_generator/types_utils.py index 48b3630..025985a 100644 --- a/tools/node_settings_generator/types_utils.py +++ b/tools/node_settings_generator/types_utils.py @@ -42,6 +42,7 @@ class ST(Enum): FORMAT_STRING_ITEMS = auto() GEOMETRY_VIEWER_ITEMS = auto() INDEX_SWITCH_ITEMS = auto() + MENU_INPUT = auto() MENU_SWITCH_ITEMS = auto() NODE_TREE = auto() RAYCAST_ATTR_ITEMS = auto()