diff --git a/__init__.py b/__init__.py index 009d8d5..762b776 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ "name": "Node to Python", "description": "Convert Blender node groups to a Python add-on!", "author": "Brendan Parmer", - "version": (3, 0, 1), + "version": (3, 1, 0), "blender": (3, 0, 0), "location": "Node", "category": "Node", diff --git a/compositor/node_settings.py b/compositor/node_settings.py index 8034b30..2db216a 100644 --- a/compositor/node_settings.py +++ b/compositor/node_settings.py @@ -261,14 +261,19 @@ ], 'CompositorNodeKuwahara' : [ - NTPNodeSetting("eccentricity", ST.FLOAT, min_version = (4, 0, 0)), - NTPNodeSetting("sharpness", ST.FLOAT, min_version = (4, 0, 0)), - NTPNodeSetting("size", ST.INT, min_version = (4, 0, 0)), - NTPNodeSetting("uniformity", ST.INT, min_version = (4, 0, 0)), - NTPNodeSetting("variation", ST.ENUM, min_version = (4, 0, 0)) + NTPNodeSetting("eccentricity", ST.FLOAT, min_version = (4, 0, 0)), + NTPNodeSetting("sharpness", ST.FLOAT, min_version = (4, 0, 0)), + NTPNodeSetting("size", ST.INT, min_version = (4, 0, 0), + max_version = (4, 1, 0)), + NTPNodeSetting("uniformity", ST.INT, min_version = (4, 0, 0)), + NTPNodeSetting("variation", ST.ENUM, min_version = (4, 0, 0)), + NTPNodeSetting("use_high_precision", ST.BOOL, min_version = (4, 1, 0)) ], - 'CompositorNodePixelate' : [], + 'CompositorNodePixelate' : [ + NTPNodeSetting("pixel_size", ST.INT, min_version = (4, 1, 0)) + ], + 'CompositorNodePosterize' : [], 'CompositorNodeSunBeams' : [ @@ -324,7 +329,7 @@ NTPNodeSetting("distance", ST.FLOAT), NTPNodeSetting("iterations", ST.INT), NTPNodeSetting("spin", ST.FLOAT), - NTPNodeSetting("use_wrap", ST.BOOL, max_version = (3, 4, 0)), + NTPNodeSetting("use_wrap", ST.BOOL, max_version = (3, 5, 0)), NTPNodeSetting("zoom", ST.FLOAT) ], @@ -571,6 +576,10 @@ 'CompositorNodeNormalize' : [], + 'CompositorNodeSplit' : [ + NTPNodeSetting("axis", ST.ENUM, min_version=(4, 1, 0)), + NTPNodeSetting("factor", ST.INT, min_version=(4, 1, 0)) + ], 'CompositorNodeSwitch' : [ NTPNodeSetting("check", ST.BOOL) diff --git a/compositor/operator.py b/compositor/operator.py index d1d6a52..b09d456 100644 --- a/compositor/operator.py +++ b/compositor/operator.py @@ -78,6 +78,27 @@ def _initialize_compositor_node_tree(self, ntp_nt, nt_name): f"name = {str_to_py_str(nt_name)})")) self._write("") + # Compositor node tree settings + #TODO: might be good to make this optional + enum_settings = ["chunk_size", "edit_quality", "execution_mode", + "precision", "render_quality"] + for enum in enum_settings: + if not hasattr(ntp_nt.node_tree, enum): + continue + setting = getattr(ntp_nt.node_tree, enum) + if setting is not None and setting is not "": + py_str = enum_to_py_str(setting) + self._write(f"{ntp_nt.var}.{enum} = {py_str}") + + bool_settings = ["use_groupnode_buffer", "use_opencl", "use_two_pass", + "use_viewer_border"] + for bool_setting in bool_settings: + if not hasattr(ntp_nt.node_tree, bool_setting): + continue + if getattr(ntp_nt.node_tree, bool_setting) is True: + self._write(f"{ntp_nt.var}.{bool_setting} = True") + + def _set_color_balance_settings(self, node: CompositorNodeColorBalance ) -> None: """ diff --git a/geometry/node_settings.py b/geometry/node_settings.py index ffa30c6..35e87a1 100644 --- a/geometry/node_settings.py +++ b/geometry/node_settings.py @@ -71,6 +71,8 @@ # Input > Scene 'GeometryNodeTool3DCursor' : [], + 'GeometryNodeInputActiveCamera' : [], + 'GeometryNodeCollectionInfo' : [ NTPNodeSetting("transform_space", ST.ENUM) ], @@ -138,6 +140,10 @@ 'GeometryNodeToolSetSelection' : [], # Geometry > Operations + 'GeometryNodeBake' : [ + NTPNodeSetting("bake_items", ST.BAKE_ITEMS, min_version = (4, 1, 0)) + ], + 'GeometryNodeBoundBox' : [], 'GeometryNodeConvexHull' : [], @@ -154,6 +160,10 @@ NTPNodeSetting("mode", ST.ENUM, min_version = (3, 1, 0)) ], + 'GeometryNodeSortElements' : [ + NTPNodeSetting("domain", ST.ENUM, min_version = (4, 1, 0)) + ], + 'GeometryNodeTransform' : [], 'GeometryNodeSeparateComponents' : [], @@ -279,7 +289,7 @@ 'GeometryNodeRealizeInstances' : [ NTPNodeSetting("legacy_behavior", ST.BOOL, min_version = (3, 1, 0), - max_version = (3, 6, 0)) + max_version = (4, 0, 0)) ], 'GeometryNodeRotateInstances' : [], @@ -472,12 +482,14 @@ ], 'ShaderNodeTexMusgrave' : [ - NTPNodeSetting("musgrave_dimensions", ST.ENUM), - NTPNodeSetting("musgrave_type", ST.ENUM) + NTPNodeSetting("musgrave_dimensions", ST.ENUM, max_version = (4, 1, 0)), + NTPNodeSetting("musgrave_type", ST.ENUM, max_version = (4, 1, 0)) ], 'ShaderNodeTexNoise' : [ - NTPNodeSetting("noise_dimensions", ST.ENUM) + NTPNodeSetting("noise_dimensions", ST.ENUM), + NTPNodeSetting("noise_type", ST.ENUM, min_version=(4, 1, 0)), + NTPNodeSetting("normalize", ST.BOOL, min_version=(4, 0, 0)), ], 'ShaderNodeTexVoronoi' : [ @@ -499,6 +511,16 @@ # UTILITIES + 'GeometryNodeIndexSwitch' : [ + NTPNodeSetting("data_type", ST.ENUM, min_version = (4, 1, 0)), + NTPNodeSetting("index_switch_items", ST.INDEX_SWITCH_ITEMS, min_version = (4, 1, 0)) + ], + + 'GeometryNodeMenuSwitch' : [ + NTPNodeSetting("data_type", ST.ENUM, min_version = (4, 1, 0)), + NTPNodeSetting("enum_definition", ST.ENUM_DEFINITION, min_version = (4, 1, 0)) + ], + 'ShaderNodeMix' : [ NTPNodeSetting("blend_type", ST.ENUM, min_version = (3, 4, 0)), NTPNodeSetting("clamp_factor", ST.BOOL, min_version = (3, 4, 0)), @@ -610,7 +632,7 @@ ], 'FunctionNodeCompareFloats' : [ - NTPNodeSetting("operation", ST.ENUM, max_version = (3, 0, 0)) + NTPNodeSetting("operation", ST.ENUM, max_version = (3, 2, 0)) ], 'ShaderNodeFloatCurve' : [ @@ -641,7 +663,11 @@ 'FunctionNodeAxisAngleToRotation' : [], 'FunctionNodeEulerToRotation' : [], 'FunctionNodeInvertRotation' : [], - + + 'FunctionNodeRotateRotation' : [ + NTPNodeSetting("rotation_space", ST.ENUM, min_version = (4, 1, 0)) + ], + 'FunctionNodeRotateEuler' : [ NTPNodeSetting("space", ST.ENUM), NTPNodeSetting("type", ST.ENUM) diff --git a/geometry/operator.py b/geometry/operator.py index 61747e3..efbe375 100644 --- a/geometry/operator.py +++ b/geometry/operator.py @@ -139,14 +139,16 @@ def _set_geo_tree_properties(self, node_tree: GeometryNodeTree) -> None: if is_tool: self._write(f"{nt_var}.is_tool = True") - tool_flags = ["is_mode_edit", + tool_flags = ["is_mode_object", + "is_mode_edit", "is_mode_sculpt", "is_type_curve", "is_type_mesh", "is_type_point_cloud"] for flag in tool_flags: - self._write(f"{nt_var}.{flag} = {getattr(node_tree, flag)}") + if hasattr(node_tree, flag) is True: + self._write(f"{nt_var}.{flag} = {getattr(node_tree, flag)}") self._write("") def _process_node_tree(self, node_tree: GeometryNodeTree) -> None: diff --git a/material/node_settings.py b/material/node_settings.py index 3729d84..657a691 100644 --- a/material/node_settings.py +++ b/material/node_settings.py @@ -194,13 +194,14 @@ ], 'ShaderNodeTexMusgrave' : [ - NTPNodeSetting("musgrave_dimensions", ST.ENUM), - NTPNodeSetting("musgrave_type", ST.ENUM) + NTPNodeSetting("musgrave_dimensions", ST.ENUM, max_version = (4, 1, 0)), + NTPNodeSetting("musgrave_type", ST.ENUM, max_version = (4, 1, 0)) ], 'ShaderNodeTexNoise' : [ NTPNodeSetting("noise_dimensions", ST.ENUM), - NTPNodeSetting("normalize", ST.BOOL, min_version = (4, 0, 0)) + NTPNodeSetting("noise_type", ST.ENUM, min_version=(4, 1, 0)), + NTPNodeSetting("normalize", ST.BOOL, min_version=(4, 0, 0)), ], 'ShaderNodeTexPointDensity' : [ diff --git a/ntp_operator.py b/ntp_operator.py index da2e642..1da59ee 100644 --- a/ntp_operator.py +++ b/ntp_operator.py @@ -294,7 +294,7 @@ def _set_settings_defaults(self, node: Node) -> None: if not hasattr(node, attr_name): if (bpy.app.version >= setting.min_version and - bpy.app.version <= setting.max_version): + bpy.app.version < setting.max_version): self.report({'WARNING'}, f"NodeToPython: Couldn't find attribute " f"\"{attr_name}\" for node {node.name} of type " @@ -345,6 +345,12 @@ def _set_settings_defaults(self, node: Node) -> None: self._load_image(attr, f"{node_var}.{attr_name}") elif st == ST.IMAGE_USER: self._image_user_settings(attr, f"{node_var}.{attr_name}") + elif st == ST.INDEX_SWITCH_ITEMS: + self._index_switch_items(attr, f"{node_var}.{attr_name}") + elif st == ST.ENUM_DEFINITION: + self._enum_definition(attr, f"{node_var}.{attr_name}") + elif st == ST.BAKE_ITEMS: + self._bake_items(attr, f"{node_var}.{attr_name}") if bpy.app.version < (4, 0, 0): def _set_group_socket_defaults(self, socket_interface: NodeSocketInterface, @@ -703,6 +709,10 @@ def _set_input_defaults(self, node: Node) -> None: elif input.bl_idname == 'NodeSocketString': default_val = str_to_py_str(input.default_value) + #menu + elif input.bl_idname == 'NodeSocketMenu': + default_val = enum_to_py_str(input.default_value) + # images elif input.bl_idname == 'NodeSocketImage': img = input.default_value @@ -1043,6 +1053,61 @@ def _image_user_settings(self, img_user: bpy.types.ImageUser, self._write(f"{img_user_var}.{img_usr_attr} = " f"{getattr(img_user, img_usr_attr)}") + if bpy.app.version >= (4, 1, 0): + def _index_switch_items(self, switch_items: bpy.types.NodeIndexSwitchItems, + items_str: str) -> None: + """ + Set the proper amount of index switch items + + Parameters: + switch_items (bpy.types.NodeIndexSwitchItems): switch items to copy + items_str (str): string for the generated switch items attribute + """ + num_items = len(switch_items) + self._write(f"{items_str}.clear()") + for i in range(num_items): + self._write(f"{items_str}.new()") + + def _enum_definition(self, enum_def: bpy.types.NodeEnumDefinition, + enum_def_str: str) -> None: + """ + Set enum definition item for a node + + Parameters: + enum_def (bpy.types.NodeEnumDefinition): enum definition to replicate + enum_def_str (str): string for the generated enum definition + """ + self._write(f"{enum_def_str}.enum_items.clear()") + for i, enum_item in enumerate(enum_def.enum_items): + name = str_to_py_str(enum_item.name) + self._write(f"{enum_def_str}.enum_items.new({name})") + if enum_item.description != "": + self._write(f"{enum_def_str}.enum_items[{i}].description = " + f"{str_to_py_str(enum_item.description)}") + + def _bake_items(self, bake_items: bpy.types.NodeGeometryBakeItems, + bake_items_str: str) -> None: + """ + Set bake items for a node + + Parameters: + bake_items (bpy.types.NodeGeometryBakeItems): bake items to replicate + bake_items_str (str): string for the generated bake items + """ + self._write(f"{bake_items_str}.clear()") + for i, bake_item in enumerate(bake_items): + socket_type = enum_to_py_str(bake_item.socket_type) + name = str_to_py_str(bake_item.name) + self._write(f"{bake_items_str}.new({socket_type}, {name})") + + ad = enum_to_py_str(bake_item.attribute_domain) + self._write(f"{bake_items_str}[{i}].attribute_domain = {ad}") + + if bake_item.is_attribute: + self._write(f"{bake_items_str}[{i}].is_attribute = True") + + + def _set_parents(self, node_tree: NodeTree) -> None: """ Sets parents for all nodes, mostly used to put nodes in frames diff --git a/utils.py b/utils.py index 08d68fb..dfa9ee8 100644 --- a/utils.py +++ b/utils.py @@ -29,6 +29,10 @@ class ST(Enum): CURVE_MAPPING = auto() NODE_TREE = auto() + ENUM_DEFINITION = auto() + INDEX_SWITCH_ITEMS = auto() + BAKE_ITEMS = auto() + # Asset Library MATERIAL = auto() # Handle with asset library OBJECT = auto() # Handle with asset library @@ -53,7 +57,7 @@ class NTPNodeSetting(NamedTuple): name: str st: ST min_version: tuple = (3, 0, 0) - max_version: tuple = (4, 1, 0) + max_version: tuple = (4, 2, 0) #first version where a setting is invalid def clean_string(string: str, lower: bool = True) -> str: