22import os
33
44from .utils import *
5+ from io import StringIO
56
67geo_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 ("\t def 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 ("\t def 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 \t mod = obj.modifiers.new(name = { mod_name } , "
324344 f"type = 'NODES')\n " ))
325345 file .write (f"\t \t mod.node_group = { nt_var } \n " )
326- apply_modifier ()
327-
328- file .write ("\t \t return {'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 \t return {'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+
341370class SelectGeoNodesMenu (bpy .types .Menu ):
342371 bl_idname = "NODE_MT_ntp_geo_nodes_selection"
343372 bl_label = "Select Geo Nodes"
0 commit comments