diff --git a/compositor/node_settings.py b/compositor/node_settings.py index ff8a377..781eee6 100644 --- a/compositor/node_settings.py +++ b/compositor/node_settings.py @@ -414,7 +414,7 @@ ("offset_y", ST.FLOAT), ("space", ST.ENUM)], - 'CompositorNodeStablize' : [("clip", ST.MOVIE_CLIP), + 'CompositorNodeStabilize' : [("clip", ST.MOVIE_CLIP), ("filter_type", ST.ENUM), ("invert", ST.BOOL)], @@ -425,5 +425,12 @@ # LAYOUT - 'CompositorNodeSwitch' : [("check", ST.BOOL)] + 'CompositorNodeSwitch' : [("check", ST.BOOL)], + + + # MISC + 'NodeFrame' : [], + 'NodeGroupInput' : [], + 'NodeGroupOutput' : [], + 'NodeReroute' : [] } diff --git a/geometry/__init__.py b/geometry/__init__.py index e50e3c0..e2707b0 100644 --- a/geometry/__init__.py +++ b/geometry/__init__.py @@ -1,12 +1,12 @@ if "bpy" in locals(): import importlib - importlib.reload(node_tree) importlib.reload(node_settings) + importlib.reload(node_tree) importlib.reload(operator) importlib.reload(ui) else: - from . import node_tree from . import node_settings + from . import node_tree from . import operator from . import ui diff --git a/geometry/node_settings.py b/geometry/node_settings.py index 1900660..50d645e 100644 --- a/geometry/node_settings.py +++ b/geometry/node_settings.py @@ -494,5 +494,12 @@ ("pivot_axis", ST.ENUM)], 'FunctionNodeRotateEuler' : [("space", ST.ENUM), - ("type", ST.ENUM)] + ("type", ST.ENUM)], + + # MISC + 'NodeFrame' : [], + 'NodeGroupInput' : [], + 'NodeGroupOutput' : [], + 'NodeReroute' : [] + } \ No newline at end of file diff --git a/geometry/operator.py b/geometry/operator.py index 79f9cf1..90f45a9 100644 --- a/geometry/operator.py +++ b/geometry/operator.py @@ -118,6 +118,10 @@ def _process_node_tree(self, node_tree: GeometryNodeTree, f"name = {str_to_py_str(node_tree.name)})\n")) self._write("\n") + if bpy.app.version >= (4, 0, 0): + self._write(f"{inner}{nt_var}.is_modifier = {node_tree.is_modifier}\n") + self._write(f"{inner}{nt_var}.is_tool = {node_tree.is_tool}\n") + #initialize nodes self._write(f"{inner}#initialize {nt_var} nodes\n") diff --git a/material/node_settings.py b/material/node_settings.py index 30b3b08..c6b339e 100644 --- a/material/node_settings.py +++ b/material/node_settings.py @@ -215,6 +215,8 @@ 'ShaderNodeMapping' : [("vector_type", ST.ENUM)], + 'ShaderNodeNormal' : [], + 'ShaderNodeNormalMap' : [("space", ST.ENUM), ("uv_map", ST.STRING)], #TODO @@ -269,5 +271,11 @@ ("filepath", ST.STRING), ("mode", ST.ENUM), ("script", ST.TEXT), - ("use_auto_update", ST.BOOL)] + ("use_auto_update", ST.BOOL)], + + # MISC + 'NodeFrame' : [], + 'NodeGroupInput' : [], + 'NodeGroupOutput' : [], + 'NodeReroute' : [] } diff --git a/ntp_operator.py b/ntp_operator.py index 4a8b699..e7da207 100644 --- a/ntp_operator.py +++ b/ntp_operator.py @@ -1,6 +1,9 @@ import bpy from bpy.types import Context, Operator -from bpy.types import Node, NodeTree, NodeSocketInterface +from bpy.types import Node, NodeTree + +if bpy.app.version < (4, 0, 0): + from bpy.types import NodeSocketInterface import os from typing import TextIO @@ -8,28 +11,43 @@ from .ntp_node_tree import NTP_NodeTree from .utils import * + class NTP_Operator(Operator): """ "Abstract" base class for all NTP operators. Blender types and abstraction don't seem to mix well, but this should only be inherited from """ - + bl_idname = "" bl_label = "" - mode : bpy.props.EnumProperty( - name = "Mode", - items = [ + mode: bpy.props.EnumProperty( + name="Mode", + items=[ ('SCRIPT', "Script", "Copy just the node group to the Blender clipboard"), ('ADDON', "Addon", "Create a full addon") ] ) + #node tree input sockets that have default properties + if bpy.app.version < (4, 0, 0): + default_sockets_v3 = {'VALUE', 'INT', 'BOOLEAN', 'VECTOR', 'RGBA'} + else: + nondefault_sockets_v4 = { + bpy.types.NodeTreeInterfaceSocketCollection, + bpy.types.NodeTreeInterfaceSocketGeometry, + bpy.types.NodeTreeInterfaceSocketImage, + bpy.types.NodeTreeInterfaceSocketMaterial, + bpy.types.NodeTreeInterfaceSocketObject, + bpy.types.NodeTreeInterfaceSocketShader, + bpy.types.NodeTreeInterfaceSocketTexture + } + def __init__(self): super().__init__() # File (TextIO) or string (StringIO) the add-on/script is generated into - self._file : TextIO = None + self._file: TextIO = None # Path to the current directory self._dir: str = None @@ -62,13 +80,13 @@ def _setup_addon_directories(self, context: Context, nt_var: str) -> None: """ Finds/creates directories to save add-on to """ - #find base directory to save new addon + # find base directory to save new addon self._dir = bpy.path.abspath(context.scene.ntp_options.dir_path) if not self._dir or self._dir == "": - self.report({'ERROR'}, + self.report({'ERROR'}, ("NodeToPython: Save your blend file before using " - "NodeToPython!")) #TODO: Still valid?? - return {'CANCELLED'} #TODO + "NodeToPython!")) # TODO: Still valid?? + return {'CANCELLED'} # TODO self._zip_dir = os.path.join(self._dir, nt_var) self._addon_dir = os.path.join(self._zip_dir, nt_var) @@ -90,7 +108,7 @@ def _create_header(self, name: str) -> None: self._write("\t\"author\" : \"Node To Python\",\n") self._write("\t\"version\" : (1, 0, 0),\n") self._write(f"\t\"blender\" : {bpy.app.version},\n") - self._write("\t\"location\" : \"Object\",\n") #TODO + self._write("\t\"location\" : \"Object\",\n") # TODO self._write("\t\"category\" : \"Node\"\n") self._write("}\n") self._write("\n") @@ -122,7 +140,7 @@ def _is_outermost_node_group(self, level: int) -> bool: return True return False - def _process_group_node_tree(self, node: Node, node_var: str, level: int, + def _process_group_node_tree(self, node: Node, node_var: str, level: int, inner: str) -> None: """ Processes node tree of group node if one is present @@ -176,26 +194,27 @@ def _create_node(self, node: Node, inner: str, node_tree_var: str) -> str: self._write((f"{inner}{node_var} " f"= {node_tree_var}.nodes.new(\"{node.bl_idname}\")\n")) - #label + # label if node.label: self._write(f"{inner}{node_var}.label = \"{node.label}\"\n") - - #name + + # name self._write(f"{inner}{node_var}.name = \"{node.name}\"\n") - #color + # color if node.use_custom_color: self._write(f"{inner}{node_var}.use_custom_color = True\n") - self._write(f"{inner}{node_var}.color = {vec3_to_py_str(node.color)}\n") + self._write( + f"{inner}{node_var}.color = {vec3_to_py_str(node.color)}\n") - #mute + # mute if node.mute: self._write(f"{inner}{node_var}.mute = True\n") - + return node_var def _set_settings_defaults(self, node: Node, inner: str, node_var: str - ) -> None: + ) -> None: """ Sets the defaults for any settings a node may have @@ -252,108 +271,276 @@ def _set_settings_defaults(self, node: Node, inner: str, node_var: str if self._addon_dir is not None and attr is not None: if attr.source in {'FILE', 'GENERATED', 'TILED'}: self._save_image(attr) - self._load_image(attr, inner, f"{node_var}.{attr_name}") + self._load_image( + attr, inner, f"{node_var}.{attr_name}") elif type == ST.IMAGE_USER: - self._image_user_settings(attr, inner, f"{node_var}.{attr_name}") + self._image_user_settings( + attr, inner, f"{node_var}.{attr_name}") - def _set_group_socket_default(self, socket_interface: NodeSocketInterface, - inner: str, socket_var: str) -> None: - """ - Set a node group input/output's default properties if they exist + if bpy.app.version < (4, 0, 0): + def _set_group_socket_default_v3(self, socket_interface: NodeSocketInterface, + inner: str, socket_var: str) -> None: + """ + Set a node group input/output's default properties if they exist - Parameters: - socket_interface (NodeSocketInterface): socket interface associated - with the input/output - inner (str): indentation string - socket_var (str): variable name for the socket - """ - if socket_interface.type not in default_sockets: - return + Parameters: + socket_interface (NodeSocketInterface): socket interface associated + with the input/output + inner (str): indentation string + socket_var (str): variable name for the socket + """ + if socket_interface.type not in self.default_sockets_v3: + return + + if socket_interface.type == 'RGBA': + dv = vec4_to_py_str(socket_interface.default_value) + elif socket_interface.type == 'VECTOR': + dv = vec3_to_py_str(socket_interface.default_value) + else: + dv = socket_interface.default_value + self._write(f"{inner}{socket_var}.default_value = {dv}\n") + + # min value + if hasattr(socket_interface, "min_value"): + min_val = socket_interface.min_value + self._write(f"{inner}{socket_var}.min_value = {min_val}\n") + # max value + if hasattr(socket_interface, "min_value"): + max_val = socket_interface.max_value + self._write((f"{inner}{socket_var}.max_value = {max_val}\n")) + + def _group_io_settings_v3(self, node: bpy.types.Node, inner: str, + io: str, # TODO: convert to enum + ntp_node_tree: NTP_NodeTree) -> None: + """ + Set the settings for group input and output sockets + + Parameters: + node (bpy.types.Node) : group input/output node + inner (str): indentation string + io (str): whether we're generating the input or output settings + node_tree_var (str): variable name of the generated node tree + node_tree (bpy.types.NodeTree): node tree that we're generating + input and output settings for + """ + node_tree_var = ntp_node_tree.var + node_tree = ntp_node_tree.node_tree + + if io == "input": + io_sockets = node.outputs + io_socket_interfaces = node_tree.inputs + else: + io_sockets = node.inputs + io_socket_interfaces = node_tree.outputs + + self._write(f"{inner}#{node_tree_var} {io}s\n") + for i, inout in enumerate(io_sockets): + if inout.bl_idname == 'NodeSocketVirtual': + continue + self._write(f"{inner}#{io} {inout.name}\n") + idname = enum_to_py_str(inout.bl_idname) + name = str_to_py_str(inout.name) + self._write( + f"{inner}{node_tree_var}.{io}s.new({idname}, {name})\n") + socket_interface = io_socket_interfaces[i] + socket_var = f"{node_tree_var}.{io}s[{i}]" + + self._set_group_socket_default_v3(socket_interface, inner, + socket_var) + + # default attribute name + if hasattr(socket_interface, "default_attribute_name"): + if socket_interface.default_attribute_name != "": + dan = str_to_py_str( + socket_interface.default_attribute_name) + self._write((f"{inner}{socket_var}" + f".default_attribute_name = {dan}\n")) + + # attribute domain + if hasattr(socket_interface, "attribute_domain"): + ad = enum_to_py_str(socket_interface.attribute_domain) + self._write(f"{inner}{socket_var}.attribute_domain = {ad}\n") + + # tooltip + if socket_interface.description != "": + description = str_to_py_str(socket_interface.description) + self._write( + (f"{inner}{socket_var}.description = {description}\n")) + + # hide_value + if socket_interface.hide_value is True: + self._write(f"{inner}{socket_var}.hide_value = True\n") + + # hide in modifier + if hasattr(socket_interface, "hide_in_modifier"): + if socket_interface.hide_in_modifier is True: + self._write( + f"{inner}{socket_var}.hide_in_modifier = True\n") + + self._write("\n") + self._write("\n") + + elif bpy.app.version >= (4, 0, 0): + def _set_group_socket_default_v4(self, socket_interface: bpy.types.NodeTreeInterfaceSocket, + inner: str, socket_var: str) -> None: + """ + Set a node group input/output's default properties if they exist + + Parameters: + socket_interface (NodeTreeInterfaceSocket): socket interface associated + with the input/output + inner (str): indentation string + socket_var (str): variable name for the socket + """ + if type(socket_interface) in self.nondefault_sockets_v4: + return - if socket_interface.type == 'RGBA': - dv = vec4_to_py_str(socket_interface.default_value) - elif socket_interface.type == 'VECTOR': - dv = vec3_to_py_str(socket_interface.default_value) - else: dv = socket_interface.default_value - self._write(f"{inner}{socket_var}.default_value = {dv}\n") - #min value - if hasattr(socket_interface, "min_value"): - min_val = socket_interface.min_value - self._write(f"{inner}{socket_var}.min_value = {min_val}\n") - #max value - if hasattr(socket_interface, "min_value"): - max_val = socket_interface.max_value - self._write((f"{inner}{socket_var}.max_value = {max_val}\n")) + if type(socket_interface) == bpy.types.NodeTreeInterfaceSocketColor: + dv = vec4_to_py_str(dv) + elif type(dv) in {mathutils.Vector, mathutils.Euler}: + dv = vec3_to_py_str(dv) + elif type(dv) == str: + dv = str_to_py_str(dv) + self._write(f"{inner}{socket_var}.default_value = {dv}\n") + + # min value + if hasattr(socket_interface, "min_value"): + min_val = socket_interface.min_value + self._write(f"{inner}{socket_var}.min_value = {min_val}\n") + # max value + if hasattr(socket_interface, "min_value"): + max_val = socket_interface.max_value + self._write((f"{inner}{socket_var}.max_value = {max_val}\n")) + + def _group_io_settings_v4(self, node: bpy.types.Node, inner: str, + io: str, # TODO: convert to enum + ntp_node_tree: NTP_NodeTree) -> None: + """ + Set the settings for group input and output sockets + + Parameters: + node (bpy.types.Node) : group input/output node + inner (str): indentation string + io (str): whether we're generating the input or output settings + node_tree_var (str): variable name of the generated node tree + node_tree (bpy.types.NodeTree): node tree that we're generating + input and output settings for + """ + node_tree_var = ntp_node_tree.var + node_tree = ntp_node_tree.node_tree + + if io == "input": + io_sockets = node.outputs # Might be removeable, + # think we can get all the info from the inouts + # from the socket interfaces, need to double check. + # If so, then we can just run these at the initialization + # of the node tree, meaning we can clean up the clunky + # Group Input/Group Output node reliance, two calls + # Should be pretty easy to add in panels afterwards, + # looks like those are tied fairly close to the new socket + # system + items_tree = node_tree.interface.items_tree + io_socket_interfaces = [item for item in items_tree + if item.item_type == 'SOCKET' + and item.in_out == 'INPUT'] + else: + io_sockets = node.inputs + items_tree = node_tree.interface.items_tree + io_socket_interfaces = [item for item in items_tree + if item.item_type == 'SOCKET' + and item.in_out == 'OUTPUT'] + + self._write(f"{inner}#{node_tree_var} {io}s\n") + for i, socket_interface in enumerate(io_socket_interfaces): + self._write(f"{inner}#{io} {socket_interface.name}\n") + + socket_interface: bpy.types.NodeTreeInterfaceSocket = io_socket_interfaces[i] + + #initialization + socket_var = clean_string(socket_interface.name) + "_socket" + name = str_to_py_str(socket_interface.name) + in_out_enum = enum_to_py_str(socket_interface.in_out) + + socket_type = enum_to_py_str(socket_interface.bl_socket_idname) + """ + I might be missing something, but the Python API's set up a bit + weird here now. The new socket initialization only accepts types + from a list of basic ones, but there doesn't seem to be a way of + retrieving just this basic typewithout the subtype information. + """ + if 'Float' in socket_type: + socket_type = enum_to_py_str('NodeSocketFloat') + elif 'Int' in socket_type: + socket_type = enum_to_py_str('NodeSocketInt') + elif 'Vector' in socket_type: + socket_type = enum_to_py_str('NodeSocketVector') + - def _group_io_settings(self, node: bpy.types.Node, inner: str, - io: str, #TODO: convert to enum - ntp_node_tree: NTP_NodeTree) -> None: - """ - Set the settings for group input and output sockets + self._write(f"{inner}{socket_var} = " + f"{node_tree_var}.interface.new_socket(" + f"name = {name}, in_out={in_out_enum}, " + f"socket_type = {socket_type})\n") - Parameters: - node (bpy.types.Node) : group input/output node - inner (str): indentation string - io (str): whether we're generating the input or output settings - node_tree_var (str): variable name of the generated node tree - node_tree (bpy.types.NodeTree): node tree that we're generating input - and output settings for - """ - node_tree_var = ntp_node_tree.var - node_tree = ntp_node_tree.node_tree + #subtype + if hasattr(socket_interface, "subtype"): + subtype = enum_to_py_str(socket_interface.subtype) + self._write(f"{inner}{socket_var}.subtype = {subtype}\n") - if io == "input": - io_sockets = node.outputs - io_socket_interfaces = node_tree.inputs - else: - io_sockets = node.inputs - io_socket_interfaces = node_tree.outputs + self._set_group_socket_default_v4(socket_interface, inner, + socket_var) - self._write(f"{inner}#{node_tree_var} {io}s\n") - for i, inout in enumerate(io_sockets): - if inout.bl_idname == 'NodeSocketVirtual': - continue - self._write(f"{inner}#{io} {inout.name}\n") - idname = enum_to_py_str(inout.bl_idname) - name = str_to_py_str(inout.name) - self._write(f"{inner}{node_tree_var}.{io}s.new({idname}, {name})\n") - socket_interface = io_socket_interfaces[i] - socket_var = f"{node_tree_var}.{io}s[{i}]" - - self._set_group_socket_default(socket_interface, inner, socket_var) - - #default attribute name - if hasattr(socket_interface, "default_attribute_name"): + # default attribute name if socket_interface.default_attribute_name != "": dan = str_to_py_str(socket_interface.default_attribute_name) - self._write((f"{inner}{socket_var}" - f".default_attribute_name = {dan}\n")) + self._write((f"{inner}{socket_var}.default_attribute_name = {dan}\n")) - #attribute domain - if hasattr(socket_interface, "attribute_domain"): + # attribute domain ad = enum_to_py_str(socket_interface.attribute_domain) self._write(f"{inner}{socket_var}.attribute_domain = {ad}\n") - #tooltip - if socket_interface.description != "": - description = str_to_py_str(socket_interface.description) - self._write((f"{inner}{socket_var}.description = {description}\n")) + # tooltip + if socket_interface.description != "": + description = str_to_py_str(socket_interface.description) + self._write( + (f"{inner}{socket_var}.description = {description}\n")) - #hide_value - if socket_interface.hide_value is True: - self._write(f"{inner}{socket_var}.hide_value = True\n") + # hide_value + if socket_interface.hide_value is True: + self._write(f"{inner}{socket_var}.hide_value = True\n") - #hide in modifier - if hasattr(socket_interface, "hide_in_modifier"): + # hide in modifier if socket_interface.hide_in_modifier is True: self._write(f"{inner}{socket_var}.hide_in_modifier = True\n") + #force non field + if socket_interface.force_non_field is True: + self._write(f"{inner}{socket_var}.force_non_field = True\n") + + self._write("\n") self._write("\n") - self._write("\n") - def _set_input_defaults(self, node: bpy.types.Node, inner: str, + def _group_io_settings(self, node: bpy.types.Node, inner: str, + io: str, # TODO: convert to enum + ntp_node_tree: NTP_NodeTree) -> None: + """ + Set the settings for group input and output sockets + + Parameters: + node (bpy.types.Node) : group input/output node + inner (str): indentation string + io (str): whether we're generating the input or output settings + node_tree_var (str): variable name of the generated node tree + node_tree (bpy.types.NodeTree): node tree that we're generating + input and output settings for + """ + if bpy.app.version < (4, 0, 0): + self._group_io_settings_v3(node, inner, io, ntp_node_tree) + else: + self._group_io_settings_v4(node, inner, io, ntp_node_tree) + + def _set_input_defaults(self, node: bpy.types.Node, inner: str, node_var: str) -> None: """ Sets defaults for input sockets @@ -370,45 +557,51 @@ def _set_input_defaults(self, node: bpy.types.Node, inner: str, for i, input in enumerate(node.inputs): if input.bl_idname not in dont_set_defaults and not input.is_linked: - #TODO: this could be cleaner + # TODO: this could be cleaner socket_var = f"{node_var}.inputs[{i}]" - #colors + # colors if input.bl_idname == 'NodeSocketColor': default_val = vec4_to_py_str(input.default_value) - #vector types + # vector types elif "Vector" in input.bl_idname: default_val = vec3_to_py_str(input.default_value) - #strings + #rotation types + elif input.bl_idname == 'NodeSocketRotation': + default_val = vec3_to_py_str(input.default_value) + + # strings elif input.bl_idname == 'NodeSocketString': default_val = str_to_py_str(input.default_value) - #images + # 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 img is not None and self._addon_dir != None: # write in a better way self._save_image(img) - self._load_image(img, inner, f"{socket_var}.default_value") + self._load_image( + img, inner, f"{socket_var}.default_value") default_val = None - #materials + # materials elif input.bl_idname == 'NodeSocketMaterial': self._in_file_inputs(input, inner, socket_var, "materials") default_val = None - #collections + # collections elif input.bl_idname == 'NodeSocketCollection': - self._in_file_inputs(input, inner, socket_var, "collections") + self._in_file_inputs( + input, inner, socket_var, "collections") default_val = None - #objects + # objects elif input.bl_idname == 'NodeSocketObject': self._in_file_inputs(input, inner, socket_var, "objects") default_val = None - - #textures + + # textures elif input.bl_idname == 'NodeSocketTexture': self._in_file_inputs(input, inner, socket_var, "textures") default_val = None @@ -431,11 +624,11 @@ def _set_output_defaults(self, node: bpy.types.Node, inner (str): indentation string node_var (str): variable name for the node we're setting output defaults for """ - #TODO: probably should define elsewhere - output_default_nodes = {'ShaderNodeValue', - 'ShaderNodeRGB', + # TODO: probably should define elsewhere + output_default_nodes = {'ShaderNodeValue', + 'ShaderNodeRGB', 'ShaderNodeNormal', - 'CompositorNodeValue', + 'CompositorNodeValue', 'CompositorNodeRGB', 'CompositorNodeNormal'} @@ -449,11 +642,11 @@ def _set_output_defaults(self, node: bpy.types.Node, dv = vec3_to_py_str(dv) self._write((f"{inner}{node_var}.outputs[0].default_value = {dv}\n")) - def _in_file_inputs(self, input: bpy.types.NodeSocket, - inner: str, - socket_var: str, - type: str - ) -> None: + def _in_file_inputs(self, input: bpy.types.NodeSocket, + inner: str, + socket_var: str, + type: str + ) -> None: """ Sets inputs for a node input if one already exists in the blend file @@ -471,8 +664,8 @@ def _in_file_inputs(self, input: bpy.types.NodeSocket, self._write((f"{inner}\t{socket_var}.default_value = " f"bpy.data.{type}[{name}]\n")) - def _color_ramp_settings(self, node: bpy.types.Node, - inner: str, + def _color_ramp_settings(self, node: bpy.types.Node, + inner: str, node_var: str, color_ramp_name: str) -> None: """ @@ -487,9 +680,10 @@ def _color_ramp_settings(self, node: bpy.types.Node, color_ramp: bpy.types.ColorRamp = getattr(node, color_ramp_name) if not color_ramp: - raise ValueError(f"No color ramp named \"{color_ramp_name}\" found") + raise ValueError( + f"No color ramp named \"{color_ramp_name}\" found") - #settings + # settings ramp_str = f"{inner}{node_var}.{color_ramp_name}" color_mode = enum_to_py_str(color_ramp.color_mode) @@ -503,7 +697,7 @@ def _color_ramp_settings(self, node: bpy.types.Node, f"= {interpolation}\n")) self._write("\n") - #key points + # key points self._write(f"{inner}#initialize color ramp elements\n") self._write((f"{ramp_str}.elements.remove" f"({ramp_str}.elements[0])\n")) @@ -512,7 +706,8 @@ def _color_ramp_settings(self, node: bpy.types.Node, if i == 0: self._write(f"{inner}{element_var} = " f"{ramp_str}.elements[{i}]\n") - self._write(f"{inner}{element_var}.position = {element.position}\n") + self._write( + f"{inner}{element_var}.position = {element.position}\n") else: self._write((f"{inner}{element_var} = " f"{ramp_str}.elements" @@ -522,10 +717,10 @@ def _color_ramp_settings(self, node: bpy.types.Node, f"{element.alpha}\n")) color_str = vec4_to_py_str(element.color) self._write((f"{inner}{element_var}.color = {color_str}\n\n")) - - def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, + + def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, node_var: str, curve_mapping_name: str - ) -> None: + ) -> None: """ Sets defaults for Float, Vector, and Color curves @@ -540,27 +735,27 @@ def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, mapping = getattr(node, curve_mapping_name) if not mapping: raise ValueError((f"Curve mapping \"{curve_mapping_name}\" not found " - f"in node \"{node.bl_idname}\"")) + f"in node \"{node.bl_idname}\"")) - #mapping settings + # mapping settings self._write(f"{inner}#mapping settings\n") mapping_var = f"{inner}{node_var}.{curve_mapping_name}" - #extend + # extend extend = enum_to_py_str(mapping.extend) self._write(f"{mapping_var}.extend = {extend}\n") - #tone + # tone tone = enum_to_py_str(mapping.tone) self._write(f"{mapping_var}.tone = {tone}\n") - #black level + # black level b_lvl_str = vec3_to_py_str(mapping.black_level) self._write((f"{mapping_var}.black_level = {b_lvl_str}\n")) - #white level + # white level w_lvl_str = vec3_to_py_str(mapping.white_level) self._write((f"{mapping_var}.white_level = {w_lvl_str}\n")) - #minima and maxima + # minima and maxima min_x = mapping.clip_min_x self._write(f"{mapping_var}.clip_min_x = {min_x}\n") min_y = mapping.clip_min_y @@ -570,13 +765,13 @@ def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, max_y = mapping.clip_max_y self._write(f"{mapping_var}.clip_max_y = {max_y}\n") - #use_clip + # use_clip use_clip = mapping.use_clip self._write(f"{mapping_var}.use_clip = {use_clip}\n") - #create curves + # create curves for i, curve in enumerate(mapping.curves): - #TODO: curve function + # TODO: curve function self._write(f"{inner}#curve {i}\n") curve_i = f"{node_var}_curve_{i}" self._write((f"{inner}{curve_i} = " @@ -587,10 +782,11 @@ def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, if (node.bl_idname == 'CompositorNodeHueCorrect'): self._write((f"{inner}for i in range" f"(len({curve_i}.points.values()) - 1, 1, -1):\n")) - self._write(f"{inner}\t{curve_i}.points.remove({curve_i}.points[i])\n") - + self._write( + f"{inner}\t{curve_i}.points.remove({curve_i}.points[i])\n") + for j, point in enumerate(curve.points): - #TODO: point function + # TODO: point function point_j = f"{inner}{curve_i}_point_{j}" loc = point.location @@ -599,12 +795,13 @@ def _curve_mapping_settings(self, node: bpy.types.Node, inner: str, self._write(f"{point_j} = {curve_i}.points[{j}]\n") self._write(f"{point_j}.location = ({loc_str})\n") else: - self._write((f"{point_j} = {curve_i}.points.new({loc_str})\n")) + self._write( + (f"{point_j} = {curve_i}.points.new({loc_str})\n")) handle = enum_to_py_str(point.handle_type) self._write(f"{point_j}.handle_type = {handle}\n") - - #update curve + + # update curve self._write(f"{inner}#update curve after changes\n") self._write(f"{mapping_var}.update()\n") @@ -619,19 +816,19 @@ def _save_image(self, img: bpy.types.Image) -> None: if img is None: return - #create image dir if one doesn't exist + # create image dir if one doesn't exist img_dir = os.path.join(self._addon_dir, IMAGE_DIR_NAME) if not os.path.exists(img_dir): os.mkdir(img_dir) - #save the image + # save the image img_str = img_to_py_str(img) img_path = f"{img_dir}/{img_str}" if not os.path.exists(img_path): img.save_render(img_path) def _load_image(self, img: bpy.types.Image, - inner: str, + inner: str, img_var: str ) -> None: """ @@ -645,10 +842,10 @@ def _load_image(self, img: bpy.types.Image, if img is None: return - + img_str = img_to_py_str(img) - #TODO: convert to special variables + # TODO: convert to special variables self._write(f"{inner}#load image {img_str}\n") self._write((f"{inner}base_dir = " f"os.path.dirname(os.path.abspath(__file__))\n")) @@ -658,24 +855,25 @@ def _load_image(self, img: bpy.types.Image, self._write((f"{inner}{img_var} = bpy.data.images.load" f"(image_path, check_existing = True)\n")) - #copy image settings + # copy image settings self._write(f"{inner}#set image settings\n") - #source + # source source = enum_to_py_str(img.source) self._write(f"{inner}{img_var}.source = {source}\n") - #color space settings + # color space settings color_space = enum_to_py_str(img.colorspace_settings.name) - self._write(f"{inner}{img_var}.colorspace_settings.name = {color_space}\n") - - #alpha mode + self._write( + f"{inner}{img_var}.colorspace_settings.name = {color_space}\n") + + # alpha mode alpha_mode = enum_to_py_str(img.alpha_mode) self._write(f"{inner}{img_var}.alpha_mode = {alpha_mode}\n") def _image_user_settings(self, img_user: bpy.types.ImageUser, - inner: str, - img_user_var: str) -> None: + inner: str, + img_user_var: str) -> None: """ Replicate the image user of an image node @@ -686,14 +884,14 @@ def _image_user_settings(self, img_user: bpy.types.ImageUser, """ img_usr_attrs = ["frame_current", "frame_duration", "frame_offset", - "frame_start", "tile", "use_auto_refresh", "use_cyclic"] - + "frame_start", "tile", "use_auto_refresh", "use_cyclic"] + for img_usr_attr in img_usr_attrs: self._write((f"{inner}{img_user_var}.{img_usr_attr} = " f"{getattr(img_user, img_usr_attr)}\n")) def _set_parents(self, node_tree: bpy.types.NodeTree, - inner: str) -> None: + inner: str) -> None: """ Sets parents for all nodes, mostly used to put nodes in frames @@ -728,8 +926,8 @@ def _set_locations(self, node_tree: bpy.types.NodeTree, inner: str) -> None: f"= ({node.location.x}, {node.location.y})\n")) self._write("\n") - def _set_dimensions(self, node_tree: bpy.types.NodeTree, inner: str, - ) -> None: + def _set_dimensions(self, node_tree: bpy.types.NodeTree, inner: str, + ) -> None: """ Set dimensions for all nodes @@ -744,8 +942,8 @@ def _set_dimensions(self, node_tree: bpy.types.NodeTree, inner: str, f"= {node.width}, {node.height}\n")) self._write("\n") - def _init_links(self, node_tree: bpy.types.NodeTree, - inner: str, + def _init_links(self, node_tree: bpy.types.NodeTree, + inner: str, node_tree_var: str) -> None: """ Create all the links between nodes @@ -757,39 +955,39 @@ def _init_links(self, node_tree: bpy.types.NodeTree, """ if node_tree.links: - self._write(f"{inner}#initialize {node_tree_var} links\n") + self._write(f"{inner}#initialize {node_tree_var} links\n") for link in node_tree.links: in_node_var = self._node_vars[link.from_node] input_socket = link.from_socket - + """ Blender's socket dictionary doesn't guarantee unique keys, which has caused much wailing and gnashing of teeth. This is a quick fix that doesn't run quick """ - #TODO: try using index() method + # TODO: try using index() method for i, item in enumerate(link.from_node.outputs.items()): if item[1] == input_socket: input_idx = i break - + out_node_var = self._node_vars[link.to_node] output_socket = link.to_socket - + for i, item in enumerate(link.to_node.inputs.items()): if item[1] == output_socket: output_idx = i break - + self._write((f"{inner}#{in_node_var}.{input_socket.name} " f"-> {out_node_var}.{output_socket.name}\n")) self._write((f"{inner}{node_tree_var}.links.new({in_node_var}" f".outputs[{input_idx}], " f"{out_node_var}.inputs[{output_idx}])\n")) - def _hide_hidden_sockets(self, node: bpy.types.Node, inner: str, - node_var: str) -> None: + def _hide_hidden_sockets(self, node: bpy.types.Node, inner: str, + node_var: str) -> None: """ Hide hidden sockets @@ -803,7 +1001,7 @@ def _hide_hidden_sockets(self, node: bpy.types.Node, inner: str, self._write(f"{inner}{node_var}.inputs[{i}].hide = True\n") for i, socket in enumerate(node.outputs): if socket.hide is True: - self._write(f"{inner}{node_var}.outputs[{i}].hide = True\n") + self._write(f"{inner}{node_var}.outputs[{i}].hide = True\n") def _set_socket_defaults(self, node: Node, node_var: str, inner: str): self._set_input_defaults(node, inner, node_var) @@ -842,7 +1040,7 @@ def _create_main_func(self) -> None: """ self._write("if __name__ == \"__main__\":\n") self._write("\tregister()") - + def _zip_addon(self) -> None: """ Zips up the addon and removes the directory @@ -851,13 +1049,13 @@ def _zip_addon(self) -> None: shutil.rmtree(self._zip_dir) # ABSTRACT - def _process_node(self, node: Node, ntp_node_tree: NTP_NodeTree, inner: str, + def _process_node(self, node: Node, ntp_node_tree: NTP_NodeTree, inner: str, level: int) -> None: return # ABSTRACT def _process_node_tree(self, node_tree: NodeTree, level: int) -> None: - return + return def _report_finished(self, object: str): """ @@ -871,9 +1069,9 @@ def _report_finished(self, object: str): location = "clipboard" else: location = self._dir - self.report({'INFO'}, + self.report({'INFO'}, f"NodeToPython: Saved {object} to {location}") - + # ABSTRACT def execute(self): return {'FINISHED'} diff --git a/utils.py b/utils.py index bec9a44..68e6798 100644 --- a/utils.py +++ b/utils.py @@ -55,9 +55,6 @@ class ST(Enum): FILE_SLOTS = auto() LAYER_SLOTS = auto() #unimplemented -#node tree input sockets that have default properties -default_sockets = {'VALUE', 'INT', 'BOOLEAN', 'VECTOR', 'RGBA'} - def clean_string(string: str, lower: bool = True) -> str: """ Cleans up a string for use as a variable or file name