Skip to content

Commit c75d3df

Browse files
Merge pull request #53 from BrendanParmer/copy_node_group
Copy node group
2 parents 94ad28a + a505ce6 commit c75d3df

File tree

4 files changed

+157
-85
lines changed

4 files changed

+157
-85
lines changed

__init__.py

Lines changed: 3 additions & 2 deletions
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": (2, 1, 0),
5+
"version": (2, 2, 0),
66
"blender": (3, 0, 0),
77
"location": "Node",
88
"category": "Node",
@@ -36,7 +36,8 @@ def draw(self, context):
3636
geo_nodes.GeoNodesToPythonPanel,
3737
materials.MaterialToPython,
3838
materials.SelectMaterialMenu,
39-
materials.MaterialToPythonPanel]
39+
materials.MaterialToPythonPanel
40+
]
4041

4142
def register():
4243
for cls in classes:

geo_nodes.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33

44
from .utils import *
5+
from io import StringIO
56

67
geo_node_settings = {
78
# Attribute nodes
@@ -168,6 +169,13 @@ class GeoNodesToPython(bpy.types.Operator):
168169
bl_label = "Geo Nodes to Python"
169170
bl_options = {'REGISTER', 'UNDO'}
170171

172+
mode : bpy.props.EnumProperty(
173+
name = "Mode",
174+
items = [
175+
('SCRIPT', "Script", "Copy just the node group to the Blender clipboard"),
176+
('ADDON', "Addon", "Create a full addon")
177+
]
178+
)
171179
geo_nodes_group_name: bpy.props.StringProperty(name="Node Group")
172180

173181
def execute(self, context):
@@ -176,28 +184,30 @@ def execute(self, context):
176184

177185
#set up names to use in generated addon
178186
nt_var = clean_string(nt.name)
179-
class_name = clean_string(nt.name.replace(" ", "").replace('.', ""),
180-
lower = False)
181-
182-
#find base directory to save new addon
183-
base_dir = bpy.path.abspath("//")
184-
if not base_dir or base_dir == "":
185-
self.report({'ERROR'},
186-
("NodeToPython: Save your blend file before using "
187-
"NodeToPython!"))
188-
return {'CANCELLED'}
189-
190-
#save in /addons/ subdirectory
191-
zip_dir = os.path.join(base_dir, "addons", nt_var)
192-
addon_dir = os.path.join(zip_dir, nt_var)
193-
if not os.path.exists(addon_dir):
194-
os.makedirs(addon_dir)
195-
file = open(f"{addon_dir}/__init__.py", "w")
196-
197-
create_header(file, nt.name)
198-
init_operator(file, class_name, nt_var, nt.name)
199187

200-
file.write("\tdef execute(self, context):\n")
188+
if self.mode == 'ADDON':
189+
#find base directory to save new addon
190+
base_dir = bpy.path.abspath("//")
191+
if not base_dir or base_dir == "":
192+
self.report({'ERROR'},
193+
("NodeToPython: Save your blend file before using "
194+
"NodeToPython!"))
195+
return {'CANCELLED'}
196+
197+
#save in addons/ subdirectory
198+
zip_dir = os.path.join(base_dir, "addons", nt_var)
199+
addon_dir = os.path.join(zip_dir, nt_var)
200+
if not os.path.exists(addon_dir):
201+
os.makedirs(addon_dir)
202+
file = open(f"{addon_dir}/__init__.py", "w")
203+
204+
create_header(file, nt.name)
205+
class_name = clean_string(nt.name.replace(" ", "").replace('.', ""),
206+
lower = False)
207+
init_operator(file, class_name, nt_var, nt.name)
208+
file.write("\tdef execute(self, context):\n")
209+
else:
210+
file = StringIO("")
201211

202212
#set to keep track of already created node trees
203213
node_trees = set()
@@ -218,8 +228,8 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars):
218228
file.write(f"{outer}def {nt_var}_node_group():\n")
219229
file.write((f"{inner}{nt_var}"
220230
f"= bpy.data.node_groups.new("
221-
f"type = \"GeometryNodeTree\", "
222-
f"name = \"{node_tree.name}\")\n"))
231+
f"type = \'GeometryNodeTree\', "
232+
f"name = {str_to_py_str(node_tree.name)})\n"))
223233
file.write("\n")
224234

225235
inputs_set = False
@@ -256,12 +266,12 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars):
256266
if node.node_tree is not None:
257267
file.write((f"{inner}{node_var}.node_tree = "
258268
f"bpy.data.node_groups"
259-
f"[\"{node.node_tree.name}\"]\n"))
269+
f"[{str_to_py_str(node.node_tree.name)}]\n"))
260270
elif node.bl_idname == 'ShaderNodeValToRGB':
261271
color_ramp_settings(node, file, inner, node_var)
262272
elif node.bl_idname in curve_nodes:
263273
curve_node_settings(node, file, inner, node_var)
264-
elif node.bl_idname in image_nodes:
274+
elif node.bl_idname in image_nodes and self.mode == 'ADDON':
265275
img = node.image
266276
if img is not None and img.source in {'FILE', 'GENERATED', 'TILED'}:
267277
save_image(img, addon_dir)
@@ -284,7 +294,10 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars):
284294
f"{attr_domain}\n"))
285295

286296
if node.bl_idname != 'GeometryNodeSimulationInput':
287-
set_input_defaults(node, file, inner, node_var, addon_dir)
297+
if self.mode == 'ADDON':
298+
set_input_defaults(node, file, inner, node_var, addon_dir)
299+
else:
300+
set_input_defaults(node, file, inner, node_var)
288301
set_output_defaults(node, file, inner, node_var)
289302

290303
#create simulation zones
@@ -295,7 +308,10 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars):
295308
f"({sim_output_var})\n"))
296309

297310
#must set defaults after paired with output
298-
set_input_defaults(sim_input, file, inner, sim_input_var, addon_dir)
311+
if self.mode == 'ADDON':
312+
set_input_defaults(node, file, inner, node_var, addon_dir)
313+
else:
314+
set_input_defaults(node, file, inner, node_var)
299315
set_output_defaults(sim_input, file, inner, sim_input_var)
300316

301317
set_parents(node_tree, file, inner, node_vars)
@@ -311,7 +327,11 @@ def process_geo_nodes_group(node_tree, level, node_vars, used_vars):
311327
f"{nt_var}_node_group()\n\n"))
312328
return used_vars
313329

314-
process_geo_nodes_group(nt, 2, node_vars, used_vars)
330+
if self.mode == 'ADDON':
331+
level = 2
332+
else:
333+
level = 0
334+
process_geo_nodes_group(nt, level, node_vars, used_vars)
315335

316336
def apply_modifier():
317337
#get object
@@ -323,21 +343,30 @@ def apply_modifier():
323343
file.write((f"\t\tmod = obj.modifiers.new(name = {mod_name}, "
324344
f"type = 'NODES')\n"))
325345
file.write(f"\t\tmod.node_group = {nt_var}\n")
326-
apply_modifier()
327-
328-
file.write("\t\treturn {'FINISHED'}\n\n")
329-
330-
create_menu_func(file, class_name)
331-
create_register_func(file, class_name)
332-
create_unregister_func(file, class_name)
333-
create_main_func(file)
346+
if self.mode == 'ADDON':
347+
apply_modifier()
334348

349+
file.write("\t\treturn {'FINISHED'}\n\n")
350+
351+
create_menu_func(file, class_name)
352+
create_register_func(file, class_name)
353+
create_unregister_func(file, class_name)
354+
create_main_func(file)
355+
else:
356+
context.window_manager.clipboard = file.getvalue()
335357
file.close()
336358

337-
zip_addon(zip_dir)
338-
359+
if self.mode == 'ADDON':
360+
zip_addon(zip_dir)
361+
self.report({'INFO'}, "NodeToPython: Saved geometry nodes group")
339362
return {'FINISHED'}
363+
364+
def invoke(self, context, event):
365+
return context.window_manager.invoke_props_dialog(self)
340366

367+
def draw(self, context):
368+
self.layout.prop(self, "mode")
369+
341370
class SelectGeoNodesMenu(bpy.types.Menu):
342371
bl_idname = "NODE_MT_ntp_geo_nodes_selection"
343372
bl_label = "Select Geo Nodes"

0 commit comments

Comments
 (0)