From a13e540f71930c31fab9fca48fd6c445f89c508a Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:50:45 -0500 Subject: [PATCH 01/10] feat: added geometry nodes from Blender 3.5 --- __init__.py | 2 +- geo_nodes.py | 165 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 104 insertions(+), 63 deletions(-) diff --git a/__init__.py b/__init__.py index d2f68ad..dacc2e7 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ "name": "Node to Python", "description": "Convert Blender node groups to a Python add-on!", "author": "Brendan Parmer", - "version": (2, 0, 1), + "version": (2, 1, 0), "blender": (3, 0, 0), "location": "Node", "category": "Node", diff --git a/geo_nodes.py b/geo_nodes.py index e9eff4c..05f550e 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -11,89 +11,110 @@ 'NodeSocketVector'} geo_node_settings = { - #attribute + # Attribute nodes "GeometryNodeAttributeStatistic" : ["data_type", "domain"], - "GeometryNodeCaptureAttribute" : ["data_type", "domain"], "GeometryNodeAttributeDomainSize" : ["component"], + + "GeometryNodeBlurAttribute" : ["data_type"], + "GeometryNodeCaptureAttribute" : ["data_type", "domain"], "GeometryNodeStoreNamedAttribute" : ["data_type", "domain"], "GeometryNodeAttributeTransfer" : ["data_type", "mapping"], - #color - "ShaderNodeMixRGB" : ["blend_type", "use_clamp"], - "FunctionNodeCombineColor" : ["mode"], - "FunctionNodeSeparateColor" : ["mode"], + # Input Nodes + # Input > Constant + "FunctionNodeInputBool" : ["boolean"], + "FunctionNodeInputColor" : ["color"], + "FunctionNodeInputInt" : ["integer"], + "GeometryNodeInputMaterial" : ["material"], + "FunctionNodeInputString" : ["string"], + "FunctionNodeInputVector" : ["vector"], - #curve - "GeometryNodeCurveToPoints" : ["mode"], - "GeometryNodeFillCurve" : ["mode"], - "GeometryNodeFilletCurve" : ["mode"], - "GeometryNodeResampleCurve" : ["mode"], + # Input > Scene + "GeometryNodeCollectionInfo" : ["transform_space"], + "GeometryNodeObjectInfo" : ["transform_space"], + + # Output Nodes + "GeometryNodeViewer" : ["domain"], + + # Geometry Nodes + # Geometry > Read + "GeometryNodeInputNamedAttribute" : ["data_type"], + + # Geometry > Sample + "GeometryNodeProximity" : ["target_element"], + "GeometryNodeRaycast" : ["data_type", "mapping"], + "GeometryNodeSampleIndex" : ["data_type", "domain", "clamp"], + "GeometryNodeSampleNearest" : ["domain"], + + # Geometry > Operations + "GeometryNodeDeleteGeometry" : ["domain", "mode"], + "GeometryNodeDuplicateElements" : ["domain"], + "GeometryNodeMergeByDistance" : ["mode"], + "GeometryNodeSeparateGeometry" : ["domain"], + + + # Curve + # Curve > Read + "GeometryNodeCurveHandleTypeSelection" : ["mode", "handle_type"], + + # Curve > Sample "GeometryNodeSampleCurve" : ["data_type", "mode", "use_all_curves"], - "GeometryNodeTrimCurve" : ["mode"], + + # Curve > Write "GeometryNodeSetCurveNormal" : ["mode"], - "GeometryNodeCurveHandleTypeSelection" : ["mode", "handle_type"], "GeometryNodeSetCurveHandlePositions" : ["mode"], "GeometryNodeCurveSetHandles" : ["mode", "handle_type"], "GeometryNodeCurveSplineType" : ["spline_type"], - #curve primitives + # Curve > Operations + "GeometryNodeCurveToPoints" : ["mode"], + "GeometryNodeFillCurve" : ["mode"], + "GeometryNodeFilletCurve" : ["mode"], + "GeometryNodeResampleCurve" : ["mode"], + "GeometryNodeTrimCurve" : ["mode"], + + # Curve > Primitives "GeometryNodeCurveArc" : ["mode"], "GeometryNodeCurvePrimitiveBezierSegment" : ["mode"], "GeometryNodeCurvePrimitiveCircle" : ["mode"], "GeometryNodeCurvePrimitiveLine" : ["mode"], "GeometryNodeCurvePrimitiveQuadrilateral" : ["mode"], - #geometry - "GeometryNodeDeleteGeometry" : ["domain", "mode"], - "GeometryNodeDuplicateElements" : ["domain"], - "GeometryNodeProximity" : ["target_element"], - "GeometryNodeMergeByDistance" : ["mode"], - "GeometryNodeRaycast" : ["data_type", "mapping"], - "GeometryNodeSampleIndex" : ["data_type", "domain", "clamp"], - "GeometryNodeSampleNearest" : ["domain"], - "GeometryNodeSeparateGeometry" : ["domain"], - #input - "FunctionNodeInputBool" : ["boolean"], - "GeometryNodeCollectionInfo" : ["transform_space"], - "FunctionNodeInputColor" : ["color"], - "FunctionNodeInputInt" : ["integer"], - "GeometryNodeInputMaterial" : ["material"], - "GeometryNodeObjectInfo" : ["transform_space"], - "FunctionNodeInputString" : ["string"], - "FunctionNodeInputVector" : ["vector"], - "GeometryNodeInputNamedAttribute" : ["data_type"], + # Mesh Nodes + # Mesh > Sample + "GeometryNodeSampleNearestSurface" : ["data_type"], + "GeometryNodeSampleUVSurface" : ["data_type"], - #mesh + # Mesh > Operations "GeometryNodeExtrudeMesh" : ["mode"], "GeometryNodeMeshBoolean" : ["operation"], "GeometryNodeMeshToPoints" : ["mode"], "GeometryNodeMeshToVolume" : ["resolution_mode"], - "GeometryNodeSampleNearestSurface" : ["data_type"], - "GeometryNodeSampleUVSurface" : ["data_type"], + "GeometryNodeScaleElements" : ["domain", "scale_mode"], "GeometryNodeSubdivisionSurface" : ["uv_smooth", "boundary_smooth"], "GeometryNodeTriangulate" : ["quad_method", "ngon_method"], - "GeometryNodeScaleElements" : ["domain", "scale_mode"], - #mesh primitives + # Mesh > Primitives "GeometryNodeMeshCone" : ["fill_type"], "GeometryNodeMeshCylinder" : ["fill_type"], "GeometryNodeMeshCircle" : ["fill_type"], "GeometryNodeMeshLine" : ["mode"], - #output - "GeometryNodeViewer" : ["domain"], - - #point + # Mesh > UV + "GeometryNodeUVUnwrap" : ["method"], + + + # Point Nodes "GeometryNodeDistributePointsInVolume" : ["mode"], "GeometryNodeDistributePointsOnFaces" : ["distribute_method"], "GeometryNodePointsToVolume" : ["resolution_mode"], - #text - "GeometryNodeStringToCurves" : ["overflow", "align_x", "align_y", - "pivot_mode"], - - #texture + # Volume Nodes + "GeometryNodeVolumeToMesh" : ["resolution_mode"], + + + # Texture Nodes "ShaderNodeTexBrick" : ["offset", "offset_frequency", "squash", "squash_frequency"], "ShaderNodeTexGradient" : ["gradient_type"], @@ -104,36 +125,51 @@ "ShaderNodeTexWave" : ["wave_type", "bands_direction", "wave_profile"], "ShaderNodeTexWhiteNoise" : ["noise_dimensions"], - #utilities + + # Utilities + # Utilities > Color + "FunctionNodeCombineColor" : ["mode"], + "ShaderNodeMixRGB" : ["blend_type", "use_clamp"], #legacy + "FunctionNodeSeparateColor" : ["mode"], + + # Utilities > Text + "GeometryNodeStringToCurves" : ["overflow", "align_x", "align_y", + "pivot_mode"], + + # Utilities > Vector + "ShaderNodeVectorMath" : ["operation"], + "ShaderNodeVectorRotate" : ["rotation_type", "invert"], + + # Utilities > Field "GeometryNodeAccumulateField" : ["data_type", "domain"], - "FunctionNodeAlignEulerToVector" : ["axis", "pivot_axis"], + "GeometryNodeFieldAtIndex" : ["data_type", "domain"], + "GeometryNodeFieldOnDomain" : ["data_type", "domain" ], + + # Utilities > Math "FunctionNodeBooleanMath" : ["operation"], "ShaderNodeClamp" : ["clamp_type"], "FunctionNodeCompare" : ["data_type", "operation", "mode"], - "GeometryNodeFieldAtIndex" : ["data_type", "domain"], "FunctionNodeFloatToInt" : ["rounding_mode"], - "GeometryNodeFieldOnDomain" : ["data_type", "domain" ], "ShaderNodeMapRange" : ["data_type", "interpolation_type", "clamp"], "ShaderNodeMath" : ["operation", "use_clamp"], - "FunctionNodeRandomValue" : ["data_type"], - "FunctionNodeRotateEuler" : ["type", "space"], - "GeometryNodeSwitch" : ["input_type"], - - #uv - "GeometryNodeUVUnwrap" : ["method"], - #vector - "ShaderNodeVectorMath" : ["operation"], - "ShaderNodeVectorRotate" : ["rotation_type", "invert"], + # Utilities > Rotate + "FunctionNodeAlignEulerToVector" : ["axis", "pivot_axis"], + "FunctionNodeRotateEuler" : ["type", "space"], - #volume - "GeometryNodeVolumeToMesh" : ["resolution_mode"] + # Utilities > General + "ShaderNodeMix" : ["data_type", "blend_type", "clamp_result", + "clamp_factor"], + "FunctionNodeRandomValue" : ["data_type"], + "GeometryNodeSwitch" : ["input_type"] } curve_nodes = {'ShaderNodeFloatCurve', 'ShaderNodeVectorCurve', 'ShaderNodeRGBCurve'} +image_nodes = {'GeometryNodeInputImage'} + class GeoNodesToPython(bpy.types.Operator): bl_idname = "node.geo_nodes_to_python" bl_label = "Geo Nodes to Python" @@ -322,6 +358,11 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): color_ramp_settings(node, file, inner, node_var) elif node.bl_idname in curve_nodes: curve_node_settings(node, file, inner, node_var) + elif node.bl_idname in image_nodes: + img = node.image + if img.source in {'FILE', 'GENERATED', 'TILED'}: + save_image(img, addon_dir) + load_image(img, file, inner, f"{node_var}.image") set_input_defaults(node, file, inner, node_var, addon_dir) set_output_defaults(node, file, inner, node_var) From af7ca34093dad16ea1c9882b6055e832858ec920 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 20 May 2023 17:42:11 -0500 Subject: [PATCH 02/10] feat: subtype and hide in modifier support --- geo_nodes.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/geo_nodes.py b/geo_nodes.py index 05f550e..9e3346e 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -4,11 +4,7 @@ from .utils import * #node tree input sockets that have default properties -default_sockets = {'NodeSocketBool', - 'NodeSocketColor', - 'NodeSocketFloat', - 'NodeSocketInt', - 'NodeSocketVector'} +default_sockets = {'VALUE', 'INT', 'BOOLEAN', 'VECTOR', 'RGBA'} geo_node_settings = { # Attribute nodes @@ -178,6 +174,7 @@ class GeoNodesToPython(bpy.types.Operator): geo_nodes_group_name: bpy.props.StringProperty(name="Node Group") def execute(self, context): + print("EXECUTE") #find node group to replicate nt = bpy.data.node_groups[self.geo_nodes_group_name] @@ -251,14 +248,11 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): f"(\"{input.bl_idname}\", " f"\"{input.name}\")\n")) socket = node_tree.inputs[i] - if input.bl_idname in default_sockets: - if input.bl_idname == 'NodeSocketColor': - col = socket.default_value - r, g, b, a = col[0], col[1], col[2], col[3] - dv = f"({r}, {g}, {b}, {a})" - elif input.bl_idname == 'NodeSocketVector': - vec = socket.default_value - dv = f"({vec[0]}, {vec[1]}, {vec[2]})" + if input.type in default_sockets: + if input.type == 'RGBA': + dv = vec4_to_py_str(socket.default_value) + elif input.type == 'VECTOR': + dv = vec3_to_py_str(socket.default_value) else: dv = socket.default_value @@ -299,6 +293,14 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): f".inputs[{i}]" f".hide_value = " f"{socket.hide_value}\n")) + + #hide in modifier + if hasattr(socket, "hide_in_modifier"): + if socket.hide_in_modifier is True: + file.write((f"{inner}{node_tree_var}" + f".inputs[{i}]" + f".hide_in_modifier = " + f"{socket.hide_in_modifier}\n")) file.write("\n") file.write("\n") inputs_set = True From 21680bb19b4c7b8eadecb6f4d8f610892e8d0aea Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 20 May 2023 17:57:53 -0500 Subject: [PATCH 03/10] cleanup: removed print statement --- geo_nodes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/geo_nodes.py b/geo_nodes.py index 9e3346e..4e9a27c 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -174,7 +174,6 @@ class GeoNodesToPython(bpy.types.Operator): geo_nodes_group_name: bpy.props.StringProperty(name="Node Group") def execute(self, context): - print("EXECUTE") #find node group to replicate nt = bpy.data.node_groups[self.geo_nodes_group_name] From 5bfd24591ea7008d364deaea10df2dda74168279 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 20 May 2023 18:25:22 -0500 Subject: [PATCH 04/10] feat: default values for group outputs --- geo_nodes.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/geo_nodes.py b/geo_nodes.py index 4e9a27c..bedad2e 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -155,7 +155,7 @@ # Utilities > General "ShaderNodeMix" : ["data_type", "blend_type", "clamp_result", - "clamp_factor"], + "clamp_factor", "factor_mode"], "FunctionNodeRandomValue" : ["data_type"], "GeometryNodeSwitch" : ["input_type"] } @@ -313,6 +313,31 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): f"\"{output.name}\")\n")) socket = node_tree.outputs[i] + if output.type in default_sockets: + if output.type == 'RGBA': + dv = vec4_to_py_str(socket.default_value) + elif output.type == 'VECTOR': + dv = vec3_to_py_str(socket.default_value) + else: + dv = socket.default_value + + #default value + file.write((f"{inner}{node_tree_var}" + f".outputs[{i}]" + f".default_value = {dv}\n")) + + #min value + if hasattr(socket, "min_value"): + file.write((f"{inner}{node_tree_var}" + f".outputs[{i}]" + f".min_value = " + f"{socket.min_value}\n")) + #max value + if hasattr(socket, "max_value"): + file.write((f"{inner}{node_tree_var}" + f".outputs[{i}]" + f".max_value = " + f"{socket.max_value}\n")) #description if socket.description != "": file.write((f"{inner}{node_tree_var}" @@ -339,7 +364,8 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): file.write((f"{inner}{node_tree_var}" f".outputs[{i}]" f".attribute_domain = " - f"\'{socket.attribute_domain}\'\n")) + f"\'{socket.attribute_domain}\'\n")) + file.write("\n") outputs_set = True From 4d2280e3e27fd193d26a2321edda85d5c1ef757f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 15:03:02 -0500 Subject: [PATCH 05/10] fix: typo for set dimensions comment --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index fc0a796..da3395e 100644 --- a/utils.py +++ b/utils.py @@ -529,7 +529,7 @@ def set_dimensions(node_tree, file: TextIO, inner: str, node_vars: dict): node_vars (dict): dictionary for (node, variable) name pairs """ - file.write(f"{inner}#sSet dimensions\n") + file.write(f"{inner}#Set dimensions\n") for node in node_tree.nodes: node_var = node_vars[node] file.write((f"{inner}{node_var}.width, {node_var}.height " From a5ed5e5196166eb7123c887b382057168a61d72e Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 15:59:33 -0500 Subject: [PATCH 06/10] feat: pair simulation inputs with corresponding output --- geo_nodes.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/geo_nodes.py b/geo_nodes.py index bedad2e..59b3452 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -231,6 +231,8 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): #initialize nodes file.write(f"{inner}#initialize {node_tree_var} nodes\n") + sim_inputs = [] + for node in node_tree.nodes: if node.bl_idname == 'GeometryNodeGroup': node_nt = node.node_tree @@ -390,9 +392,17 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): if img.source in {'FILE', 'GENERATED', 'TILED'}: save_image(img, addon_dir) load_image(img, file, inner, f"{node_var}.image") - + elif node.bl_idname == 'GeometryNodeSimulationInput': + sim_inputs.append(node) set_input_defaults(node, file, inner, node_var, addon_dir) set_output_defaults(node, file, inner, node_var) + + #create simulation zones + for sim_input in sim_inputs: + sim_input_var = node_vars[sim_input] + sim_output_var = node_vars[sim_input.paired_output] + file.write((f"{inner}{sim_input_var}.pair_with_output" + f"({sim_output_var})\n")) set_parents(node_tree, file, inner, node_vars) set_locations(node_tree, file, inner, node_vars) set_dimensions(node_tree, file, inner, node_vars) From 5a1ebc64c3c937759f56f58426cd0e4f254eb97f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 16:00:15 -0500 Subject: [PATCH 07/10] feat: don't write set parents comment unless parents are set --- utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils.py b/utils.py index da3395e..5392324 100644 --- a/utils.py +++ b/utils.py @@ -492,9 +492,12 @@ def set_parents(node_tree, file: TextIO, inner: str, node_vars: dict): inner (str): indentation string node_vars (dict): dictionary for (node, variable) name pairs """ - file.write(f"{inner}#Set parents\n") + parent_comment = False for node in node_tree.nodes: if node is not None and node.parent is not None: + if not parent_comment: + file.write(f"{inner}#Set parents\n") + parent_comment = True node_var = node_vars[node] parent_var = node_vars[node.parent] file.write(f"{inner}{node_var}.parent = {parent_var}\n") From 135a6e64469c08810ce3c969c67d28f7dd5b55ae Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 16:32:31 -0500 Subject: [PATCH 08/10] feat: handles simulation state items --- geo_nodes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/geo_nodes.py b/geo_nodes.py index 59b3452..eb6d906 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -394,6 +394,20 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): load_image(img, file, inner, f"{node_var}.image") elif node.bl_idname == 'GeometryNodeSimulationInput': sim_inputs.append(node) + elif node.bl_idname == 'GeometryNodeSimulationOutput': + file.write(f"{inner}#remove generated sim state items\n") + file.write(f"{inner}for item in {node_var}.state_items:\n") + file.write(f"{inner}\t{node_var}.state_items.remove(item)\n") + for i, si in enumerate(node.state_items): + socket_type = enum_to_py_str(si.socket_type) + name = str_to_py_str(si.name) + file.write((f"{inner}{node_var}.state_items.new" + f"({socket_type}, {name})\n")) + si_var = f"{node_var}.state_items[{i}]" + attr_domain = enum_to_py_str(si.attribute_domain) + file.write((f"{inner}{si_var}.attribute_domain = " + f"{attr_domain}\n")) + set_input_defaults(node, file, inner, node_var, addon_dir) set_output_defaults(node, file, inner, node_var) From cf9a960a3545d0afb5a02b52aa8ef6db2c0111e0 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 16:51:57 -0500 Subject: [PATCH 09/10] fix: sim input nodes now set socket defaults after being paired with the corresponding output --- geo_nodes.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/geo_nodes.py b/geo_nodes.py index eb6d906..6f9082a 100644 --- a/geo_nodes.py +++ b/geo_nodes.py @@ -401,6 +401,7 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): for i, si in enumerate(node.state_items): socket_type = enum_to_py_str(si.socket_type) name = str_to_py_str(si.name) + file.write(f"{inner}#create SSI {name}\n") file.write((f"{inner}{node_var}.state_items.new" f"({socket_type}, {name})\n")) si_var = f"{node_var}.state_items[{i}]" @@ -408,8 +409,9 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): file.write((f"{inner}{si_var}.attribute_domain = " f"{attr_domain}\n")) - set_input_defaults(node, file, inner, node_var, addon_dir) - set_output_defaults(node, file, inner, node_var) + if node.bl_idname != 'GeometryNodeSimulationInput': + set_input_defaults(node, file, inner, node_var, addon_dir) + set_output_defaults(node, file, inner, node_var) #create simulation zones for sim_input in sim_inputs: @@ -417,6 +419,11 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars): sim_output_var = node_vars[sim_input.paired_output] file.write((f"{inner}{sim_input_var}.pair_with_output" f"({sim_output_var})\n")) + + #must set defaults after paired with output + set_input_defaults(sim_input, file, inner, sim_input_var, addon_dir) + set_output_defaults(sim_input, file, inner, sim_input_var) + set_parents(node_tree, file, inner, node_vars) set_locations(node_tree, file, inner, node_vars) set_dimensions(node_tree, file, inner, node_vars) From 6efc3bac5ee4489f35891de17fe8addba755b8d6 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Mon, 22 May 2023 17:17:01 -0500 Subject: [PATCH 10/10] docs: update README for v2.1 --- docs/README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index 17f3139..3065a6b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,21 +7,19 @@ ## About A Blender add-on to create add-ons! This add-on will take your Geometry Nodes or Materials and convert them into legible Python add-ons! -It automatically handles node layout, default values, subgroups, naming, colors, and more! +Node To Python automatically handles node layout, default values, subgroups, naming, colors, and more! -I think 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, such as +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, such as * `for` loops * creating different node trees for different versions or settings * interfacing with other parts of the software or properties of an object -NodeToPython recreates the node networks for you, so you can focus on the good stuff. - ## Supported Versions -NodeToPython v2.0 is compatible with Blender 3.0 - 3.4 on Windows, macOS, and Linux. I generally try to update the addon to handle new nodes around the beta release of each update. +NodeToPython v2.1 is compatible with Blender 3.0 - 3.6 on Windows, macOS, and Linux. I generally try to update the add-on to handle new nodes around the beta release of each update. ## Installation 1. Download the `NodeToPython.zip` file from the [latest release](https://github.com/BrendanParmer/NodeToPython/releases) - * If you download other options, you'll need to rename the zip and the first folder to "NodeToPython" so Blender can properly import the module + * If you clone the repository or download other options, you'll need to rename the zip and the first folder to "NodeToPython" so Blender can properly import the add-on 2. In Blender, navigate to `Edit > Preferences > Add-ons` 3. Click Install, and find where you downloaded the zip file. Then hit the `Install Add-on` button, and you're done! @@ -37,19 +35,26 @@ Just select the one you want, and soon a zip file will be created in an `addons` From here, you can install it like a regular add-on. ## Future +### v2.2 * A "copy" mode, where just the functionality to build the node group is just copied to the clipbaord +* Choose the location where to save the add-on + +### v2.3 * Expansion to Compositing nodes * Add all referenced assets to the Asset Library for use outside of the original blend file + +### Later * Auto-set handle movies and image sequences * Automatically format code to be PEP8 compliant * Automatically detect the minimum version of Blender needed to run the add-on ## Potential Issues -* As of version 2.0.1, the add-on will not set default values for +* As of version 2.1, the add-on will not set default values for * Scripts * IES files * Filepaths * UV maps +* This add-on doesn't currently set default values in Geometry Nodes modifiers, just the node groups themselves * Currently when setting default values for the following, the add-on must be run in the same blend file as the node group was created in to set the default, otherwise it will just set it to `None`: * Materials * Objects @@ -67,4 +72,4 @@ When submitting an issue, please include * A short description of what you were trying to accomplish, or steps to reproduce the issue. * Sample blend files are more than welcome! -Suggestions for how to improve the add-on are more than welcome! \ No newline at end of file +Got suggestions? Create an issue, happy to hear what features people want. \ No newline at end of file