Skip to content

v3.4 #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 16, 2025
2 changes: 1 addition & 1 deletion NodeToPython/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Node to Python",
"description": "Convert Blender node groups to a Python add-on!",
"author": "Brendan Parmer",
"version": (3, 3, 2),
"version": (3, 4, 0),
"blender": (3, 0, 0),
"location": "Node",
"category": "Node",
Expand Down
4 changes: 2 additions & 2 deletions NodeToPython/blender_manifest.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version = "1.0.0"

id = "node_to_python"
version = "3.3.2"
version = "3.4.0"
name = "Node To Python"
tagline = "Turn node groups into Python code"
maintainer = "Brendan Parmer <brendanparmer+nodetopython@gmail.com>"
Expand All @@ -12,7 +12,7 @@ website = "https://github.com/BrendanParmer/NodeToPython"
tags = ["Development", "Compositing", "Geometry Nodes", "Material", "Node"]

blender_version_min = "4.2.0"
blender_version_max = "4.4.0"
blender_version_max = "4.5.0"

license = [
"SPDX:MIT",
Expand Down
4 changes: 2 additions & 2 deletions NodeToPython/compositor/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class NTPCompositorOperator(NTP_Operator):
name="Is Scene",
description="Blender stores compositing node trees differently for scenes and in groups")

def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._node_infos = node_settings
for name in COMP_OP_RESERVED_NAMES:
self._used_vars[name] = 0
Expand Down
3 changes: 3 additions & 0 deletions NodeToPython/compositor/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class NTPCompositorPanel(Panel):
bl_context = ''
bl_category = "NodeToPython"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def poll(cls, context):
return True
Expand Down
5 changes: 3 additions & 2 deletions NodeToPython/geometry/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ class NTPGeoNodesOperator(NTP_Operator):

geo_nodes_group_name: bpy.props.StringProperty(name="Node Group")

def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self._node_infos = node_settings
for name in GEO_OP_RESERVED_NAMES:
self._used_vars[name] = 0
Expand Down
3 changes: 3 additions & 0 deletions NodeToPython/geometry/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class NTPGeoNodesPanel(Panel):
bl_context = ''
bl_category = "NodeToPython"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def poll(cls, context):
return True
Expand Down
31 changes: 28 additions & 3 deletions NodeToPython/node_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ST(Enum):
SIM_OUTPUT_ITEMS = auto()
IMAGE = auto()
IMAGE_USER = auto()
COLLECTION = auto()
CRYPTOMATTE_ENTRIES = auto()
FILE_SLOTS = auto()
FONT = auto()
Expand All @@ -51,12 +52,12 @@ class NTPNodeSetting(NamedTuple):
name_: str
st_: ST
min_version_: tuple = (3, 0, 0)
max_version_: tuple = (4, 4, 0)
max_version_: tuple = (4, 5, 0)

class NodeInfo(NamedTuple):
attributes_: list[NTPNodeSetting]
min_version_: tuple = (3, 0, 0)
max_version_: tuple = (4, 4, 0)
max_version_: tuple = (4, 5, 0)

node_settings : dict[str, NodeInfo] = {
'CompositorNodeAlphaOver' : NodeInfo(
Expand Down Expand Up @@ -378,6 +379,7 @@ class NodeInfo(NamedTuple):
'CompositorNodeDenoise' : NodeInfo(
[
NTPNodeSetting("prefilter", ST.ENUM),
NTPNodeSetting("quality", ST.ENUM, min_version_=(4, 4, 0)),
NTPNodeSetting("use_hdr", ST.BOOL),
]
),
Expand Down Expand Up @@ -882,6 +884,7 @@ class NodeInfo(NamedTuple):
NTPNodeSetting("center_x", ST.FLOAT, max_version_=(4, 2, 0)),
NTPNodeSetting("center_y", ST.FLOAT, max_version_=(4, 2, 0)),
NTPNodeSetting("tile_order", ST.ENUM, max_version_=(4, 2, 0)),
NTPNodeSetting("ui_shortcut", ST.INT, min_version_=(4, 4, 0)),
NTPNodeSetting("use_alpha", ST.BOOL),
]
),
Expand Down Expand Up @@ -965,6 +968,11 @@ class NodeInfo(NamedTuple):
min_version_ = (4, 0, 0)
),

'FunctionNodeFindInString' : NodeInfo(
[],
min_version_ = (4, 4, 0)
),

'FunctionNodeFloatToInt' : NodeInfo(
[
NTPNodeSetting("rounding_mode", ST.ENUM),
Expand Down Expand Up @@ -1581,6 +1589,13 @@ class NodeInfo(NamedTuple):
min_version_ = (4, 1, 0)
),

'GeometryNodeInputCollection' : NodeInfo(
[
NTPNodeSetting("collection", ST.COLLECTION),
],
min_version_ = (4, 4, 0)
),

'GeometryNodeInputCurveHandlePositions' : NodeInfo(
[]
),
Expand Down Expand Up @@ -1682,7 +1697,16 @@ class NodeInfo(NamedTuple):
),

'GeometryNodeInputNormal' : NodeInfo(
[]
[
NTPNodeSetting("legacy_corner_normals", ST.BOOL, min_version_=(4, 4, 0)),
]
),

'GeometryNodeInputObject' : NodeInfo(
[
NTPNodeSetting("object", ST.OBJECT),
],
min_version_ = (4, 4, 0)
),

'GeometryNodeInputPosition' : NodeInfo(
Expand Down Expand Up @@ -2282,6 +2306,7 @@ class NodeInfo(NamedTuple):

'GeometryNodeResampleCurve' : NodeInfo(
[
NTPNodeSetting("keep_last_segment", ST.BOOL, min_version_=(4, 4, 0)),
NTPNodeSetting("mode", ST.ENUM),
]
),
Expand Down
54 changes: 40 additions & 14 deletions NodeToPython/ntp_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class NTP_Operator(Operator):
bpy.types.NodeTreeInterfaceSocketTexture
}

def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# Write functions after nodes are mostly initialized and linked up
self._write_after_links: list[Callable] = []
Expand Down Expand Up @@ -229,6 +229,9 @@ def _init_operator(self, idname: str, label: str) -> None:
"""
self._idname = idname
self._write(f"class {self._class_name}(bpy.types.Operator):", 0)
self._write("def __init__(self, *args, **kwargs):", 1)
self._write("super().__init__(*args, **kwargs)\n", 2)

self._write(f"bl_idname = \"node.{idname}\"", 1)
self._write(f"bl_label = {str_to_py_str(label)}", 1)
self._write("bl_options = {\'REGISTER\', \'UNDO\'}\n", 1)
Expand Down Expand Up @@ -408,26 +411,27 @@ def _set_settings_defaults(self, node: Node) -> None:
elif st == ST.COLOR:
self._write(f"{setting_str} = {color_to_py_str(attr)}")
elif st == ST.MATERIAL:
name = str_to_py_str(attr.name)
self._write(f"if {name} in bpy.data.materials:")
self._write(f"{setting_str} = bpy.data.materials[{name}]",
self._inner_indent_level + 1)
self._set_if_in_blend_file(attr, setting_str, "materials")
elif st == ST.OBJECT:
name = str_to_py_str(attr.name)
self._write(f"if {name} in bpy.data.objects:")
self._write(f"{setting_str} = bpy.data.objects[{name}]",
self._inner_indent_level + 1)
self._set_if_in_blend_file(attr, setting_str, "objects")
elif st == ST.COLLECTION:
self._set_if_in_blend_file(attr, setting_str, "collections")
elif st == ST.COLOR_RAMP:
self._color_ramp_settings(node, attr_name)
elif st == ST.CURVE_MAPPING:
self._curve_mapping_settings(node, attr_name)
elif st == ST.NODE_TREE:
self._node_tree_settings(node, attr_name)
elif st == ST.IMAGE:
if self._addon_dir is not None and attr is not None:
if attr is None:
continue
if self._addon_dir is not None:
if attr.source in {'FILE', 'GENERATED', 'TILED'}:
if self._save_image(attr):
self._load_image(attr, f"{node_var}.{attr_name}")
else:
self._set_if_in_blend_file(attr, setting_str, "images")

elif st == ST.IMAGE_USER:
self._image_user_settings(attr, f"{node_var}.{attr_name}")
elif st == ST.SIM_OUTPUT_ITEMS:
Expand Down Expand Up @@ -755,6 +759,7 @@ def _process_items(self, parent: NodeTreeInterfacePanel,
processed items, so none are done twice
ntp_nt (NTP_NodeTree): owner of the socket
"""

if parent is None:
items = ntp_nt.node_tree.interface.items_tree
else:
Expand All @@ -774,6 +779,14 @@ def _process_items(self, parent: NodeTreeInterfacePanel,
elif item.item_type == 'PANEL':
self._create_panel(item, panel_dict, items_processed,
parent, ntp_nt)
if bpy.app.version >= (4, 4, 0) and parent is not None:
nt_var = self._node_tree_vars[ntp_nt.node_tree]
interface_var = f"{nt_var}.interface"
panel_var = panel_dict[item]
parent_var = panel_dict[parent]
self._write(f"{interface_var}.move_to_parent("
f"{panel_var}, {parent_var}, {item.index})")


def _tree_interface_settings(self, ntp_nt: NTP_NodeTree) -> None:
"""
Expand Down Expand Up @@ -837,9 +850,12 @@ def _set_input_defaults(self, node: Node) -> None:
# images
elif input.bl_idname == 'NodeSocketImage':
img = input.default_value
if img is not None and self._addon_dir != None: # write in a better way
if self._save_image(img):
self._load_image(img, f"{socket_var}.default_value")
if img is not None:
if self._addon_dir != None: # write in a better way
if self._save_image(img):
self._load_image(img, f"{socket_var}.default_value")
else:
self._in_file_inputs(input, socket_var, "images")
default_val = None

# materials
Expand Down Expand Up @@ -921,6 +937,16 @@ def _set_socket_defaults(self, node: Node):
self._set_input_defaults(node)
self._set_output_defaults(node)

def _set_if_in_blend_file(self, attr, setting_str: str, data_type: str
) -> None:
"""
Attempts to grab referenced thing from blend file
"""
name = str_to_py_str(attr.name)
self._write(f"if {name} in bpy.data.{data_type}:")
self._write(f"{setting_str} = bpy.data.{data_type}[{name}]",
self._inner_indent_level + 1)

def _color_ramp_settings(self, node: Node, color_ramp_name: str) -> None:
"""
Replicate a color ramp node
Expand Down
7 changes: 7 additions & 0 deletions NodeToPython/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class NTPOptions(bpy.types.PropertyGroup):
"""
Property group used during conversion of node group to python
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# General properties
mode: bpy.props.EnumProperty(
name = "Mode",
Expand Down Expand Up @@ -170,6 +174,9 @@ class NTPOptionsPanel(bpy.types.Panel):
bl_context = ''
bl_category = "NodeToPython"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def poll(cls, context):
return True
Expand Down
4 changes: 2 additions & 2 deletions NodeToPython/shader/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class NTPShaderOperator(NTP_Operator):
#TODO: add option for general shader node groups
material_name: bpy.props.StringProperty(name="Node Group")

def __init__(self):
super().__init__()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._node_infos = node_settings
for name in SHADER_OP_RESERVED_NAMES:
self._used_vars[name] = 0
Expand Down
3 changes: 3 additions & 0 deletions NodeToPython/shader/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class NTPShaderPanel(Panel):
bl_context = ''
bl_category = "NodeToPython"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@classmethod
def poll(cls, context):
return True
Expand Down
2 changes: 2 additions & 0 deletions tools/node_settings_generator/types_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ST(Enum):
IMAGE_USER = auto() #needs refactor

# Currently unimplemented
COLLECTION = auto()
CRYPTOMATTE_ENTRIES = auto()
FILE_SLOTS = auto()
FONT = auto()
Expand Down Expand Up @@ -78,6 +79,7 @@ class ST(Enum):
"" : "",
"bpy_prop_collection of CryptomatteEntry": ST.CRYPTOMATTE_ENTRIES,
"boolean" : ST.BOOL,
"Collection" : ST.COLLECTION,
"ColorMapping" : None, # Always read-only
"ColorRamp" : ST.COLOR_RAMP,
"CompositorNodeOutputFileFileSlots" : ST.FILE_SLOTS,
Expand Down