Skip to content

Commit 0a97c25

Browse files
Merge pull request #137 from BrendanParmer/v3.3
v3.3
2 parents a3a2603 + 832801e commit 0a97c25

File tree

12 files changed

+421
-167
lines changed

12 files changed

+421
-167
lines changed

NodeToPython/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "Node to Python",
33
"description": "Convert Blender node groups to a Python add-on!",
44
"author": "Brendan Parmer",
5-
"version": (3, 2, 2),
5+
"version": (3, 3, 0),
66
"blender": (3, 0, 0),
77
"location": "Node",
88
"category": "Node",

NodeToPython/blender_manifest.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
schema_version = "1.0.0"
22

33
id = "node_to_python"
4-
version = "3.2.2"
4+
version = "3.3.0"
55
name = "Node To Python"
66
tagline = "Turn node groups into Python code"
77
maintainer = "Brendan Parmer <[email protected]>"
@@ -12,7 +12,7 @@ website = "https://github.com/BrendanParmer/NodeToPython"
1212
tags = ["Development", "Compositing", "Geometry Nodes", "Material", "Node"]
1313

1414
blender_version_min = "4.2.0"
15-
blender_version_max = "4.3.0"
15+
blender_version_max = "4.4.0"
1616

1717
license = [
1818
"SPDX:MIT",

NodeToPython/compositor/operator.py

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,45 +31,44 @@ def __init__(self):
3131
self._used_vars[name] = 0
3232

3333

34-
def _create_scene(self, indent: str):
34+
def _create_scene(self, indent_level: int):
3535
#TODO: wrap in more general unique name util function
36-
self._write(f"# Generate unique scene name", indent)
36+
self._write(f"# Generate unique scene name", indent_level)
3737
self._write(f"{BASE_NAME} = {str_to_py_str(self.compositor_name)}",
38-
indent)
39-
self._write(f"{END_NAME} = {BASE_NAME}", indent)
40-
self._write(f"if bpy.data.scenes.get({END_NAME}) != None:", indent)
38+
indent_level)
39+
self._write(f"{END_NAME} = {BASE_NAME}", indent_level)
40+
self._write(f"if bpy.data.scenes.get({END_NAME}) != None:", indent_level)
4141

42-
indent2 = f"{indent}\t"
43-
self._write(f"{INDEX} = 1", indent2)
42+
self._write(f"{INDEX} = 1", indent_level + 1)
4443
self._write(f"{END_NAME} = {BASE_NAME} + f\".{{i:03d}}\"",
45-
indent2)
44+
indent_level + 1)
4645
self._write(f"while bpy.data.scenes.get({END_NAME}) != None:",
47-
indent2)
46+
indent_level + 1)
4847

49-
indent3 = f"{indent}\t\t"
50-
self._write(f"{END_NAME} = {BASE_NAME} + f\".{{{INDEX}:03d}}\"", indent3)
51-
self._write(f"{INDEX} += 1\n", indent3)
48+
self._write(f"{END_NAME} = {BASE_NAME} + f\".{{{INDEX}:03d}}\"",
49+
indent_level + 2)
50+
self._write(f"{INDEX} += 1\n", indent_level + 2)
5251

53-
self._write(f"{SCENE} = bpy.context.window.scene.copy()\n", indent)
54-
self._write(f"{SCENE}.name = {END_NAME}", indent)
55-
self._write(f"{SCENE}.use_fake_user = True", indent)
56-
self._write(f"bpy.context.window.scene = {SCENE}", indent)
52+
self._write(f"{SCENE} = bpy.context.window.scene.copy()\n", indent_level)
53+
self._write(f"{SCENE}.name = {END_NAME}", indent_level)
54+
self._write(f"{SCENE}.use_fake_user = True", indent_level)
55+
self._write(f"bpy.context.window.scene = {SCENE}", indent_level)
5756

5857
def _initialize_compositor_node_tree(self, ntp_nt, nt_name):
5958
#initialize node group
60-
self._write(f"#initialize {nt_name} node group", self._outer)
61-
self._write(f"def {ntp_nt.var}_node_group():", self._outer)
59+
self._write(f"#initialize {nt_name} node group", self._outer_indent_level)
60+
self._write(f"def {ntp_nt.var}_node_group():", self._outer_indent_level)
6261

6362
if ntp_nt.node_tree == self._base_node_tree:
6463
self._write(f"{ntp_nt.var} = {SCENE}.node_tree")
6564
self._write(f"#start with a clean node tree")
6665
self._write(f"for {NODE} in {ntp_nt.var}.nodes:")
67-
self._write(f"\t{ntp_nt.var}.nodes.remove({NODE})")
66+
self._write(f"{ntp_nt.var}.nodes.remove({NODE})", self._inner_indent_level + 1)
6867
else:
6968
self._write((f"{ntp_nt.var} = bpy.data.node_groups.new("
7069
f"type = \'CompositorNodeTree\', "
7170
f"name = {str_to_py_str(nt_name)})"))
72-
self._write("")
71+
self._write("", 0)
7372

7473
# Compositor node tree settings
7574
#TODO: might be good to make this optional
@@ -108,7 +107,7 @@ def _set_color_balance_settings(self, node: CompositorNodeColorBalance
108107
NTPNodeSetting("gamma", ST.COLOR, min_version_=(3, 5, 0)),
109108
NTPNodeSetting("lift", ST.VEC3, max_version_=(3, 5, 0)),
110109
NTPNodeSetting("lift", ST.COLOR, min_version_=(3, 5, 0))]
111-
else:
110+
elif node.correction_method == 'OFFSET_POWER_SLOPE':
112111
lst = [NTPNodeSetting("correction_method", ST.ENUM),
113112
NTPNodeSetting("offset", ST.VEC3, max_version_=(3, 5, 0)),
114113
NTPNodeSetting("offset", ST.COLOR, min_version_=(3, 5, 0)),
@@ -117,11 +116,20 @@ def _set_color_balance_settings(self, node: CompositorNodeColorBalance
117116
NTPNodeSetting("power", ST.COLOR, min_version_=(3, 5, 0)),
118117
NTPNodeSetting("slope", ST.VEC3, max_version_=(3, 5, 0)),
119118
NTPNodeSetting("slope", ST.COLOR, min_version_=(3, 5, 0))]
119+
elif node.correction_method == 'WHITEPOINT':
120+
lst = [NTPNodeSetting("correction_method", ST.ENUM),
121+
NTPNodeSetting("input_temperature", ST.FLOAT),
122+
NTPNodeSetting("input_tint", ST.FLOAT),
123+
NTPNodeSetting("output_temperature", ST.FLOAT),
124+
NTPNodeSetting("output_tint", ST.FLOAT)]
125+
else:
126+
self.report({'ERROR'},
127+
f"Unknown color balance correction method "
128+
f"{enum_to_py_str(node.correction_method)}")
129+
return
120130

121131
color_balance_info = self._node_infos['CompositorNodeColorBalance']
122132
self._node_infos['CompositorNodeColorBalance'] = color_balance_info._replace(attributes_ = lst)
123-
for setting in self._node_infos['CompositorNodeColorBalance'].attributes_:
124-
print(setting.name_)
125133

126134
def _process_node(self, node: Node, ntp_nt: NTP_NodeTree):
127135
"""
@@ -191,7 +199,7 @@ def _process_node_tree(self, node_tree: CompositorNodeTree):
191199
self._write(f"return {nt_var}\n")
192200

193201
#create node group
194-
self._write(f"{nt_var} = {nt_var}_node_group()\n", self._outer)
202+
self._write(f"{nt_var} = {nt_var}_node_group()\n", self._outer_indent_level)
195203

196204
def execute(self, context):
197205
if not self._setup_options(context.scene.ntp_options):
@@ -214,8 +222,8 @@ def execute(self, context):
214222
comp_var = clean_string(self.compositor_name)
215223

216224
if self._mode == 'ADDON':
217-
self._outer = "\t\t"
218-
self._inner = "\t\t\t"
225+
self._outer_indent_level = 2
226+
self._inner_indent_level = 3
219227

220228
if not self._setup_addon_directories(context, comp_var):
221229
return {'CANCELLED'}
@@ -226,25 +234,25 @@ def execute(self, context):
226234
self._class_name = clean_string(self.compositor_name, lower=False)
227235
self._init_operator(comp_var, self.compositor_name)
228236

229-
self._write("def execute(self, context):", "\t")
237+
self._write("def execute(self, context):", 1)
230238
else:
231239
self._file = StringIO("")
232240
if self._include_imports:
233241
self._file.write("import bpy, mathutils\n\n")
234242

235243
if self.is_scene:
236244
if self._mode == 'ADDON':
237-
self._create_scene("\t\t")
245+
self._create_scene(2)
238246
elif self._mode == 'SCRIPT':
239-
self._create_scene("")
247+
self._create_scene(0)
240248

241249
node_trees_to_process = self._topological_sort(self._base_node_tree)
242250

243251
for node_tree in node_trees_to_process:
244252
self._process_node_tree(node_tree)
245253

246254
if self._mode == 'ADDON':
247-
self._write("return {'FINISHED'}\n", self._outer)
255+
self._write("return {'FINISHED'}\n", self._outer_indent_level)
248256

249257
self._create_menu_func()
250258
self._create_register_func()

NodeToPython/geometry/node_tree.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import bpy
2-
from bpy.types import GeometryNodeTree
2+
from bpy.types import GeometryNodeTree, GeometryNode
33

44
if bpy.app.version >= (3, 6, 0):
55
from bpy.types import GeometryNodeSimulationInput
66

7-
if bpy.app.version > (4, 0, 0):
7+
if bpy.app.version >= (4, 0, 0):
88
from bpy.types import GeometryNodeRepeatInput
99

10+
if bpy.app.version >= (4, 3, 0):
11+
from bpy.types import GeometryNodeForeachGeometryElementInput
12+
1013
from ..ntp_node_tree import NTP_NodeTree
1114

1215
class NTP_GeoNodeTree(NTP_NodeTree):
1316
def __init__(self, node_tree: GeometryNodeTree, var: str):
1417
super().__init__(node_tree, var)
18+
self.zone_inputs: dict[list[GeometryNode]] = {}
1519
if bpy.app.version >= (3, 6, 0):
16-
self.sim_inputs: list[GeometryNodeSimulationInput] = []
20+
self.zone_inputs["GeometryNodeSimulationInput"] = []
1721
if bpy.app.version >= (4, 0, 0):
18-
self.repeat_inputs: list[GeometryNodeRepeatInput] = []
22+
self.zone_inputs["GeometryNodeRepeatInput"] = []
23+
if bpy.app.version >= (4, 3, 0):
24+
self.zone_inputs["GeometryNodeForeachGeometryElementInput"] = []

NodeToPython/geometry/operator.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,12 @@ def _process_node(self, node: Node, ntp_nt: NTP_GeoNodeTree) -> None:
4949
self._group_io_settings(node, "output", ntp_nt)
5050
ntp_nt.outputs_set = True
5151

52-
if node.bl_idname == 'GeometryNodeSimulationInput':
53-
ntp_nt.sim_inputs.append(node)
54-
55-
elif node.bl_idname == 'GeometryNodeRepeatInput':
56-
ntp_nt.repeat_inputs.append(node)
52+
if node.bl_idname in ntp_nt.zone_inputs:
53+
ntp_nt.zone_inputs[node.bl_idname].append(node)
5754

5855
self._hide_hidden_sockets(node)
5956

60-
if node.bl_idname not in {'GeometryNodeSimulationInput',
61-
'GeometryNodeRepeatInput'}:
57+
if node.bl_idname not in ntp_nt.zone_inputs:
6258
self._set_socket_defaults(node)
6359

6460
if bpy.app.version >= (3, 6, 0):
@@ -81,7 +77,7 @@ def _process_zones(self, zone_inputs: list[GeometryNode]) -> None:
8177
#must set defaults after paired with output
8278
self._set_socket_defaults(zone_input)
8379
self._set_socket_defaults(zone_output)
84-
self._write("")
80+
self._write("", 0)
8581

8682
if bpy.app.version >= (4, 0, 0):
8783
def _set_geo_tree_properties(self, node_tree: GeometryNodeTree) -> None:
@@ -105,7 +101,7 @@ def _set_geo_tree_properties(self, node_tree: GeometryNodeTree) -> None:
105101
for flag in tool_flags:
106102
if hasattr(node_tree, flag) is True:
107103
self._write(f"{nt_var}.{flag} = {getattr(node_tree, flag)}")
108-
self._write("")
104+
self._write("", 0)
109105

110106
def _process_node_tree(self, node_tree: GeometryNodeTree) -> None:
111107
"""
@@ -119,8 +115,8 @@ def _process_node_tree(self, node_tree: GeometryNodeTree) -> None:
119115
self._node_tree_vars[node_tree] = nt_var
120116

121117
#initialize node group
122-
self._write(f"#initialize {nt_var} node group", self._outer)
123-
self._write(f"def {nt_var}_node_group():", self._outer)
118+
self._write(f"#initialize {nt_var} node group", self._outer_indent_level)
119+
self._write(f"def {nt_var}_node_group():", self._outer_indent_level)
124120
self._write(f"{nt_var} = bpy.data.node_groups.new("
125121
f"type = \'GeometryNodeTree\', "
126122
f"name = {str_to_py_str(node_tree.name)})\n")
@@ -139,10 +135,8 @@ def _process_node_tree(self, node_tree: GeometryNodeTree) -> None:
139135
for node in node_tree.nodes:
140136
self._process_node(node, ntp_nt)
141137

142-
if bpy.app.version >= (3, 6, 0):
143-
self._process_zones(ntp_nt.sim_inputs)
144-
if bpy.app.version >= (4, 0, 0):
145-
self._process_zones(ntp_nt.repeat_inputs)
138+
for zone_list in ntp_nt.zone_inputs.values():
139+
self._process_zones(zone_list)
146140

147141
#set look of nodes
148142
self._set_parents(node_tree)
@@ -155,19 +149,19 @@ def _process_node_tree(self, node_tree: GeometryNodeTree) -> None:
155149
self._write(f"return {nt_var}\n")
156150

157151
#create node group
158-
self._write(f"{nt_var} = {nt_var}_node_group()\n", self._outer)
152+
self._write(f"{nt_var} = {nt_var}_node_group()\n", self._outer_indent_level)
159153

160154

161155
def _apply_modifier(self, nt: GeometryNodeTree, nt_var: str):
162156
#get object
163-
self._write(f"{OBJECT_NAME} = bpy.context.object.name", self._outer)
164-
self._write(f"{OBJECT} = bpy.data.objects[{OBJECT_NAME}]", self._outer)
157+
self._write(f"{OBJECT_NAME} = bpy.context.object.name", self._outer_indent_level)
158+
self._write(f"{OBJECT} = bpy.data.objects[{OBJECT_NAME}]", self._outer_indent_level)
165159

166160
#set modifier to the one we just created
167161
mod_name = str_to_py_str(nt.name)
168162
self._write(f"{MODIFIER} = obj.modifiers.new(name = {mod_name}, "
169-
f"type = 'NODES')", self._outer)
170-
self._write(f"{MODIFIER}.node_group = {nt_var}", self._outer)
163+
f"type = 'NODES')", self._outer_indent_level)
164+
self._write(f"{MODIFIER}.node_group = {nt_var}", self._outer_indent_level)
171165

172166

173167
def execute(self, context):
@@ -181,8 +175,8 @@ def execute(self, context):
181175
nt_var = clean_string(nt.name)
182176

183177
if self._mode == 'ADDON':
184-
self._outer = "\t\t"
185-
self._inner = "\t\t\t"
178+
self._outer_indent_level = 2
179+
self._inner_indent_level = 3
186180

187181
if not self._setup_addon_directories(context, nt_var):
188182
return {'CANCELLED'}
@@ -192,7 +186,7 @@ def execute(self, context):
192186
self._create_header(nt.name)
193187
self._class_name = clean_string(nt.name, lower = False)
194188
self._init_operator(nt_var, nt.name)
195-
self._write("def execute(self, context):", "\t")
189+
self._write("def execute(self, context):", 1)
196190
else:
197191
self._file = StringIO("")
198192
if self._include_imports:
@@ -206,7 +200,7 @@ def execute(self, context):
206200

207201
if self._mode == 'ADDON':
208202
self._apply_modifier(nt, nt_var)
209-
self._write("return {'FINISHED'}\n", self._outer)
203+
self._write("return {'FINISHED'}\n", self._outer_indent_level)
210204
self._create_menu_func()
211205
self._create_register_func()
212206
self._create_unregister_func()

0 commit comments

Comments
 (0)