From 54a5b9e91048eb5ebc1aa045b96dfb85c97f6096 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 20 Sep 2025 21:49:25 -0500 Subject: [PATCH 01/15] feat: new method of adding compositor nodes --- NodeToPython/__init__.py | 10 +- NodeToPython/compositor/__init__.py | 12 +- NodeToPython/compositor/operator.py | 4 +- NodeToPython/compositor/ui.py | 83 ------------- NodeToPython/compositor/ui/__init__.py | 25 ++++ .../compositor/ui/compositor_node_groups.py | 116 ++++++++++++++++++ NodeToPython/compositor/ui/panel.py | 29 +++++ NodeToPython/compositor/ui/scenes.py | 108 ++++++++++++++++ 8 files changed, 296 insertions(+), 91 deletions(-) delete mode 100644 NodeToPython/compositor/ui.py create mode 100644 NodeToPython/compositor/ui/__init__.py create mode 100644 NodeToPython/compositor/ui/compositor_node_groups.py create mode 100644 NodeToPython/compositor/ui/panel.py create mode 100644 NodeToPython/compositor/ui/scenes.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 04b301c..9942b81 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -36,16 +36,11 @@ def draw(self, context): layout.operator_context = 'INVOKE_DEFAULT' -classes = [ +classes: list[type] = [ NodeToPythonMenu, #options options.NTPOptions, options.NTPOptionsPanel, - #compositor - compositor.operator.NTPCompositorOperator, - compositor.ui.NTPCompositorScenesMenu, - compositor.ui.NTPCompositorGroupsMenu, - compositor.ui.NTPCompositorPanel, #geometry geometry.operator.NTPGeoNodesOperator, geometry.ui.NTPGeoNodesMenu, @@ -55,17 +50,20 @@ def draw(self, context): shader.ui.NTPShaderMenu, shader.ui.NTPShaderPanel, ] +classes += compositor.classes def register(): for cls in classes: bpy.utils.register_class(cls) scene = bpy.types.Scene scene.ntp_options = bpy.props.PointerProperty(type=options.NTPOptions) + compositor.register_props() def unregister(): for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.ntp_options + compositor.unregister_props() if __name__ == "__main__": register() \ No newline at end of file diff --git a/NodeToPython/compositor/__init__.py b/NodeToPython/compositor/__init__.py index 1c6d5b3..5d1ee8f 100644 --- a/NodeToPython/compositor/__init__.py +++ b/NodeToPython/compositor/__init__.py @@ -6,4 +6,14 @@ from . import operator from . import ui -import bpy \ No newline at end of file +import bpy + +classes: list[type] = [] +classes += operator.classes +classes += ui.classes + +def register_props(): + ui.register_props() + +def unregister_props(): + ui.unregister_props() \ No newline at end of file diff --git a/NodeToPython/compositor/operator.py b/NodeToPython/compositor/operator.py index e71ef13..c1facc2 100644 --- a/NodeToPython/compositor/operator.py +++ b/NodeToPython/compositor/operator.py @@ -280,4 +280,6 @@ def execute(self, context): self._report_finished("compositor nodes") - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} + +classes: list[type] = [NTPCompositorOperator] \ No newline at end of file diff --git a/NodeToPython/compositor/ui.py b/NodeToPython/compositor/ui.py deleted file mode 100644 index ca19247..0000000 --- a/NodeToPython/compositor/ui.py +++ /dev/null @@ -1,83 +0,0 @@ -import bpy -from bpy.types import Panel -from bpy.types import Menu -from .operator import NTPCompositorOperator - -class NTPCompositorPanel(Panel): - bl_label = "Compositor to Python" - bl_idname = "NODE_PT_ntp_compositor" - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_context = '' - bl_category = "NodeToPython" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @classmethod - def poll(cls, context): - return True - - def draw_header(self, context): - layout = self.layout - - def draw(self, context): - layout = self.layout - scenes_row = layout.row() - - # Disables menu when there are no compositing node groups - scenes = [scene for scene in bpy.data.scenes if scene.node_tree] - scenes_exist = len(scenes) > 0 - scenes_row.enabled = scenes_exist - - scenes_row.alignment = 'EXPAND' - scenes_row.operator_context = 'INVOKE_DEFAULT' - scenes_row.menu("NODE_MT_ntp_comp_scenes", - text="Scene Compositor Nodes") - - groups_row = layout.row() - groups = [node_tree for node_tree in bpy.data.node_groups - if node_tree.bl_idname == 'CompositorNodeTree'] - groups_exist = len(groups) > 0 - groups_row.enabled = groups_exist - - groups_row.alignment = 'EXPAND' - groups_row.operator_context = 'INVOKE_DEFAULT' - groups_row.menu("NODE_MT_ntp_comp_groups", - text="Group Compositor Nodes") - -class NTPCompositorScenesMenu(Menu): - bl_idname = "NODE_MT_ntp_comp_scenes" - bl_label = "Select " - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - for scene in bpy.data.scenes: - if scene.node_tree: - op = layout.operator(NTPCompositorOperator.bl_idname, - text=scene.name) - op.compositor_name = scene.name - op.is_scene = True - -class NTPCompositorGroupsMenu(Menu): - bl_idname = "NODE_MT_ntp_comp_groups" - bl_label = "Select " - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - for node_group in bpy.data.node_groups: - if node_group.bl_idname == 'CompositorNodeTree': - op = layout.operator(NTPCompositorOperator.bl_idname, - text=node_group.name) - op.compositor_name = node_group.name - op.is_scene = False \ No newline at end of file diff --git a/NodeToPython/compositor/ui/__init__.py b/NodeToPython/compositor/ui/__init__.py new file mode 100644 index 0000000..7873c50 --- /dev/null +++ b/NodeToPython/compositor/ui/__init__.py @@ -0,0 +1,25 @@ +if "bpy" in locals(): + import importlib + importlib.reload(panel) + importlib.reload(scenes) + importlib.reload(compositor_node_groups) + +else: + from . import panel + from . import scenes + from . import compositor_node_groups + +import bpy + +classes: list[type] = [] +classes += panel.classes +classes += scenes.classes +classes += compositor_node_groups.classes + +def register_props(): + scenes.register_props() + compositor_node_groups.register_props() + +def unregister_props(): + scenes.unregister_props() + compositor_node_groups.unregister_props() \ No newline at end of file diff --git a/NodeToPython/compositor/ui/compositor_node_groups.py b/NodeToPython/compositor/ui/compositor_node_groups.py new file mode 100644 index 0000000..c65836b --- /dev/null +++ b/NodeToPython/compositor/ui/compositor_node_groups.py @@ -0,0 +1,116 @@ +import bpy + +from . import panel + +class Slot(bpy.types.PropertyGroup): + """ + TODO: There's a bug where the filtering doesn't update when renaming a + slotted object. For now, we'll need to just remove and re-add the slot + to the UI list. + """ + name: bpy.props.StringProperty( + name="Node Tree Name", + default="" + ) + + def poll_node_tree(self, node_tree: bpy.types.NodeTree) -> bool: + scene = bpy.context.scene + + for slot in scene.compositor_node_group_slots: + if slot is not self and slot.node_tree == node_tree: + return False + return node_tree.bl_idname == 'CompositorNodeTree' + + def update_node_tree(self, context): + if self.node_tree: + self.name = self.node_tree.name + else: + self.name = "Compositor Node Group" + + node_tree: bpy.props.PointerProperty( + name="Node Tree", + type=bpy.types.NodeTree, + poll=poll_node_tree, + update=update_node_tree + ) + +def register_props(): + bpy.types.Scene.compositor_node_group_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.compositor_node_group_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.compositor_node_group_slots + del bpy.types.Scene.compositor_node_group_slots_index + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_compositor_node_group_slot_add" + bl_label = "Add Compositor Node Group Slot" + bl_description = "Add Compositor Node Group Slot" + + def execute(self, context): + slots = context.scene.compositor_node_group_slots + slot = slots.add() + context.scene.compositor_node_group_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_compositor_node_group_slot_remove" + bl_label = "Remove Compositor Node Group Slot" + bl_description = "Remove Compositor Node Group Slot" + + def execute(self, context): + slots = context.scene.compositor_node_group_slots + idx = context.scene.compositor_node_group_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.compositor_node_group_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class CNG_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") + +class CNG_Panel(bpy.types.Panel): + bl_idname = "node.ntp_compositor_node_group_panel" + bl_label = "Node Groups" + bl_parent_id = panel.NTPCompositorPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = ("List of compositor node group objects to replicate.\n" + "These are typically subgroups within a larger scene tree") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "CNG_UIList", "", + context.scene, "compositor_node_group_slots", + context.scene, "compositor_node_group_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + CNG_UIList, + CNG_Panel +] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/panel.py b/NodeToPython/compositor/ui/panel.py new file mode 100644 index 0000000..d374d6f --- /dev/null +++ b/NodeToPython/compositor/ui/panel.py @@ -0,0 +1,29 @@ +import bpy +from bpy.types import Panel +from bpy.types import Menu +from ..operator import NTPCompositorOperator + +class NTPCompositorPanel(Panel): + bl_label = "Compositor to Python" + bl_idname = "NODE_PT_ntp_compositor" + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + layout = self.layout + +classes: list[type] = [ + NTPCompositorPanel +] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/scenes.py b/NodeToPython/compositor/ui/scenes.py new file mode 100644 index 0000000..d2d6725 --- /dev/null +++ b/NodeToPython/compositor/ui/scenes.py @@ -0,0 +1,108 @@ +import bpy + +from . import panel + +class Slot(bpy.types.PropertyGroup): + name: bpy.props.StringProperty( + name="Scene Name", + default="" + ) + + def poll_scene(self, scene: bpy.types.Scene) -> bool: + for slot in bpy.context.scene.scene_slots: + if slot is not self and slot.scene == scene: + return False + return scene.use_nodes + + def update_scene(self, context): + if self.scene: + self.name = self.scene.name + else: + self.name = "Scene" + + scene: bpy.props.PointerProperty( + name="Scene", + type=bpy.types.Scene, + poll=poll_scene, + update=update_scene + ) + +def register_props(): + bpy.types.Scene.scene_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.scene_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.scene_slots + del bpy.types.Scene.scene_slots_index + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_scene_slot_add" + bl_label = "Add Scene Slot" + bl_description = "Add Scene Slot" + + def execute(self, context): + slots = context.scene.scene_slots + slot = slots.add() + context.scene.scene_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_scene_slot_remove" + bl_label = "Remove Scene Slot" + bl_description = "Remove Scene Slot" + + def execute(self, context): + slots = context.scene.scene_slots + idx = context.scene.scene_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.scene_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class Scene_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "scene", bpy.data, "scenes", text="") + +class Scene_Panel(bpy.types.Panel): + bl_idname = "node.ntp_scene_panel" + bl_label = "Scenes" + bl_parent_id = panel.NTPCompositorPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "List of bpy.types.Scene objects to replicate" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "Scene_UIList", "", + context.scene, "scene_slots", + context.scene, "scene_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + Scene_UIList, + Scene_Panel +] \ No newline at end of file From f5317e48f7ff2cce3a26806c5f3240527f53b53f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:14:51 -0500 Subject: [PATCH 02/15] feat: geometry nodes ui port, refactor module registration --- NodeToPython/__init__.py | 44 +++++-- NodeToPython/compositor/__init__.py | 13 +- NodeToPython/compositor/operator.py | 4 +- NodeToPython/compositor/ui/__init__.py | 18 +-- .../compositor/ui/compositor_node_groups.py | 36 +++--- NodeToPython/compositor/ui/panel.py | 5 +- NodeToPython/compositor/ui/scenes.py | 20 +-- NodeToPython/geometry/__init__.py | 8 +- NodeToPython/geometry/ui.py | 58 --------- NodeToPython/geometry/ui/__init__.py | 11 ++ .../geometry/ui/geometry_node_groups.py | 115 ++++++++++++++++++ NodeToPython/geometry/ui/panel.py | 26 ++++ NodeToPython/shader/__init__.py | 4 +- 13 files changed, 236 insertions(+), 126 deletions(-) delete mode 100644 NodeToPython/geometry/ui.py create mode 100644 NodeToPython/geometry/ui/__init__.py create mode 100644 NodeToPython/geometry/ui/geometry_node_groups.py create mode 100644 NodeToPython/geometry/ui/panel.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 9942b81..13a491e 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -35,35 +35,57 @@ def draw(self, context): layout = self.layout.column_flow(columns=1) layout.operator_context = 'INVOKE_DEFAULT' +def register_props(): + bpy.types.Scene.ntp_options = bpy.props.PointerProperty( + type=options.NTPOptions + ) +def unregister_props(): + del bpy.types.Scene.ntp_options + +# TODO: do away with this, separate out into more appropriate modules classes: list[type] = [ NodeToPythonMenu, #options options.NTPOptions, options.NTPOptionsPanel, - #geometry - geometry.operator.NTPGeoNodesOperator, - geometry.ui.NTPGeoNodesMenu, - geometry.ui.NTPGeoNodesPanel, - #material + #shader shader.operator.NTPShaderOperator, shader.ui.NTPShaderMenu, shader.ui.NTPShaderPanel, ] -classes += compositor.classes + +modules = [] +for parent_module in [compositor, geometry, shader]: + if hasattr(parent_module, "modules"): + modules += parent_module.modules + else: + raise Exception(f"Module {parent_module} does not have list of modules") + def register(): for cls in classes: bpy.utils.register_class(cls) - scene = bpy.types.Scene - scene.ntp_options = bpy.props.PointerProperty(type=options.NTPOptions) - compositor.register_props() + register_props() + + for module in modules: + if hasattr(module, "classes"): + for cls in getattr(module, "classes"): + bpy.utils.register_class(cls) + if hasattr(module, "register_props"): + getattr(module, "register_props")() def unregister(): for cls in classes: bpy.utils.unregister_class(cls) - del bpy.types.Scene.ntp_options - compositor.unregister_props() + unregister_props() + + for module in modules: + if hasattr(module, "classes"): + for cls in getattr(module, "classes"): + bpy.utils.unregister_class(cls) + if hasattr(module, "unregister_props"): + getattr(module, "unregister_props")() if __name__ == "__main__": register() \ No newline at end of file diff --git a/NodeToPython/compositor/__init__.py b/NodeToPython/compositor/__init__.py index 5d1ee8f..cc9aa89 100644 --- a/NodeToPython/compositor/__init__.py +++ b/NodeToPython/compositor/__init__.py @@ -8,12 +8,7 @@ import bpy -classes: list[type] = [] -classes += operator.classes -classes += ui.classes - -def register_props(): - ui.register_props() - -def unregister_props(): - ui.unregister_props() \ No newline at end of file +modules = [ + operator +] +modules += ui.modules \ No newline at end of file diff --git a/NodeToPython/compositor/operator.py b/NodeToPython/compositor/operator.py index c1facc2..99c2906 100644 --- a/NodeToPython/compositor/operator.py +++ b/NodeToPython/compositor/operator.py @@ -282,4 +282,6 @@ def execute(self, context): return {'FINISHED'} -classes: list[type] = [NTPCompositorOperator] \ No newline at end of file +classes: list[type] = [ + NTPCompositorOperator +] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/__init__.py b/NodeToPython/compositor/ui/__init__.py index 7873c50..e8a9165 100644 --- a/NodeToPython/compositor/ui/__init__.py +++ b/NodeToPython/compositor/ui/__init__.py @@ -3,7 +3,6 @@ importlib.reload(panel) importlib.reload(scenes) importlib.reload(compositor_node_groups) - else: from . import panel from . import scenes @@ -11,15 +10,8 @@ import bpy -classes: list[type] = [] -classes += panel.classes -classes += scenes.classes -classes += compositor_node_groups.classes - -def register_props(): - scenes.register_props() - compositor_node_groups.register_props() - -def unregister_props(): - scenes.unregister_props() - compositor_node_groups.unregister_props() \ No newline at end of file +modules : list = [ + panel, + scenes, + compositor_node_groups +] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/compositor_node_groups.py b/NodeToPython/compositor/ui/compositor_node_groups.py index c65836b..fd4954a 100644 --- a/NodeToPython/compositor/ui/compositor_node_groups.py +++ b/NodeToPython/compositor/ui/compositor_node_groups.py @@ -2,6 +2,16 @@ from . import panel +def register_props(): + bpy.types.Scene.ntp_compositor_node_group_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_compositor_node_group_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_compositor_node_group_slots + del bpy.types.Scene.ntp_compositor_node_group_slots_index + class Slot(bpy.types.PropertyGroup): """ TODO: There's a bug where the filtering doesn't update when renaming a @@ -16,7 +26,7 @@ class Slot(bpy.types.PropertyGroup): def poll_node_tree(self, node_tree: bpy.types.NodeTree) -> bool: scene = bpy.context.scene - for slot in scene.compositor_node_group_slots: + for slot in scene.ntp_compositor_node_group_slots: if slot is not self and slot.node_tree == node_tree: return False return node_tree.bl_idname == 'CompositorNodeTree' @@ -34,25 +44,15 @@ def update_node_tree(self, context): update=update_node_tree ) -def register_props(): - bpy.types.Scene.compositor_node_group_slots = bpy.props.CollectionProperty( - type=Slot - ) - bpy.types.Scene.compositor_node_group_slots_index = bpy.props.IntProperty() - -def unregister_props(): - del bpy.types.Scene.compositor_node_group_slots - del bpy.types.Scene.compositor_node_group_slots_index - class AddSlotOperator(bpy.types.Operator): bl_idname = "node.ntp_compositor_node_group_slot_add" bl_label = "Add Compositor Node Group Slot" bl_description = "Add Compositor Node Group Slot" def execute(self, context): - slots = context.scene.compositor_node_group_slots + slots = context.scene.ntp_compositor_node_group_slots slot = slots.add() - context.scene.compositor_node_group_slots_index = len(slots) - 1 + context.scene.ntp_compositor_node_group_slots_index = len(slots) - 1 return {'FINISHED'} class RemoveSlotOperator(bpy.types.Operator): @@ -61,12 +61,12 @@ class RemoveSlotOperator(bpy.types.Operator): bl_description = "Remove Compositor Node Group Slot" def execute(self, context): - slots = context.scene.compositor_node_group_slots - idx = context.scene.compositor_node_group_slots_index + slots = context.scene.ntp_compositor_node_group_slots + idx = context.scene.ntp_compositor_node_group_slots_index if idx >= 0 and idx < len(slots): slots.remove(idx) - context.scene.compositor_node_group_slots_index = min( + context.scene.ntp_compositor_node_group_slots_index = min( max(0, idx - 1), len(slots) - 1 ) return {'FINISHED'} @@ -99,8 +99,8 @@ def draw(self, context): row = layout.row() row.template_list( "CNG_UIList", "", - context.scene, "compositor_node_group_slots", - context.scene, "compositor_node_group_slots_index" + context.scene, "ntp_compositor_node_group_slots", + context.scene, "ntp_compositor_node_group_slots_index" ) col = row.column(align=True) diff --git a/NodeToPython/compositor/ui/panel.py b/NodeToPython/compositor/ui/panel.py index d374d6f..a24a7b5 100644 --- a/NodeToPython/compositor/ui/panel.py +++ b/NodeToPython/compositor/ui/panel.py @@ -1,9 +1,6 @@ import bpy -from bpy.types import Panel -from bpy.types import Menu -from ..operator import NTPCompositorOperator -class NTPCompositorPanel(Panel): +class NTPCompositorPanel(bpy.types.Panel): bl_label = "Compositor to Python" bl_idname = "NODE_PT_ntp_compositor" bl_space_type = 'NODE_EDITOR' diff --git a/NodeToPython/compositor/ui/scenes.py b/NodeToPython/compositor/ui/scenes.py index d2d6725..7a28588 100644 --- a/NodeToPython/compositor/ui/scenes.py +++ b/NodeToPython/compositor/ui/scenes.py @@ -2,6 +2,16 @@ from . import panel +def register_props(): + bpy.types.Scene.scene_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.scene_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.scene_slots + del bpy.types.Scene.scene_slots_index + class Slot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Scene Name", @@ -27,16 +37,6 @@ def update_scene(self, context): update=update_scene ) -def register_props(): - bpy.types.Scene.scene_slots = bpy.props.CollectionProperty( - type=Slot - ) - bpy.types.Scene.scene_slots_index = bpy.props.IntProperty() - -def unregister_props(): - del bpy.types.Scene.scene_slots - del bpy.types.Scene.scene_slots_index - class AddSlotOperator(bpy.types.Operator): bl_idname = "node.ntp_scene_slot_add" bl_label = "Add Scene Slot" diff --git a/NodeToPython/geometry/__init__.py b/NodeToPython/geometry/__init__.py index e40e80c..83ec3ca 100644 --- a/NodeToPython/geometry/__init__.py +++ b/NodeToPython/geometry/__init__.py @@ -8,4 +8,10 @@ from . import operator from . import ui -import bpy \ No newline at end of file +import bpy + +modules = [ + node_tree, + operator +] +modules += ui.modules \ No newline at end of file diff --git a/NodeToPython/geometry/ui.py b/NodeToPython/geometry/ui.py deleted file mode 100644 index dff6353..0000000 --- a/NodeToPython/geometry/ui.py +++ /dev/null @@ -1,58 +0,0 @@ -import bpy -from bpy.types import Panel -from bpy.types import Menu - -from .operator import NTPGeoNodesOperator - -class NTPGeoNodesPanel(Panel): - bl_label = "Geometry Nodes to Python" - bl_idname = "NODE_PT_geo_nodes" - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_context = '' - bl_category = "NodeToPython" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @classmethod - def poll(cls, context): - return True - - def draw_header(self, context): - layout = self.layout - - def draw(self, context): - layout = self.layout - col = layout.column() - row = col.row() - - # Disables menu when len of geometry nodes is 0 - geo_node_groups = [node_tree for node_tree in bpy.data.node_groups - if node_tree.bl_idname == 'GeometryNodeTree'] - geo_node_groups_exist = len(geo_node_groups) > 0 - row.enabled = geo_node_groups_exist - - row.alignment = 'EXPAND' - row.operator_context = 'INVOKE_DEFAULT' - row.menu("NODE_MT_ntp_geo_nodes", text="Geometry Nodes") - -class NTPGeoNodesMenu(Menu): - bl_idname = "NODE_MT_ntp_geo_nodes" - bl_label = "Select Geo Nodes" - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - - geo_node_groups = [node_tree for node_tree in bpy.data.node_groups - if node_tree.bl_idname == 'GeometryNodeTree'] - - for node_tree in geo_node_groups: - op = layout.operator(NTPGeoNodesOperator.bl_idname, - text=node_tree.name) - op.geo_nodes_group_name = node_tree.name \ No newline at end of file diff --git a/NodeToPython/geometry/ui/__init__.py b/NodeToPython/geometry/ui/__init__.py new file mode 100644 index 0000000..5d29caa --- /dev/null +++ b/NodeToPython/geometry/ui/__init__.py @@ -0,0 +1,11 @@ +if "bpy" in locals(): + import importlib + importlib.reload(panel) + importlib.reload(geometry_node_groups) +else: + from . import panel + from . import geometry_node_groups + +import bpy + +modules = [panel, geometry_node_groups] \ No newline at end of file diff --git a/NodeToPython/geometry/ui/geometry_node_groups.py b/NodeToPython/geometry/ui/geometry_node_groups.py new file mode 100644 index 0000000..d8bf6af --- /dev/null +++ b/NodeToPython/geometry/ui/geometry_node_groups.py @@ -0,0 +1,115 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.ntp_geometry_node_group_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_geometry_node_group_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_geometry_node_group_slots + del bpy.types.Scene.ntp_geometry_node_group_slots_index + +class Slot(bpy.types.PropertyGroup): + """ + TODO: There's a bug where the filtering doesn't update when renaming a + slotted object. For now, we'll need to just remove and re-add the slot + to the UI list. + """ + name: bpy.props.StringProperty( + name="Node Tree Name", + default="" + ) + + def poll_node_tree(self, node_tree: bpy.types.NodeTree) -> bool: + scene = bpy.context.scene + + for slot in scene.ntp_geometry_node_group_slots: + if slot is not self and slot.node_tree == node_tree: + return False + return node_tree.bl_idname == 'GeometryNodeTree' + + def update_node_tree(self, context): + if self.node_tree: + self.name = self.node_tree.name + else: + self.name = "Geometry Node Group" + + node_tree: bpy.props.PointerProperty( + name="Node Tree", + type=bpy.types.NodeTree, + poll=poll_node_tree, + update=update_node_tree + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_geometry_node_group_slot_add" + bl_label = "Add Geometry Node Group Slot" + bl_description = "Add Geometry Node Group Slot" + + def execute(self, context): + slots = context.scene.ntp_geometry_node_group_slots + slot = slots.add() + context.scene.ntp_geometry_node_group_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_geometry_node_group_slot_remove" + bl_label = "Remove Geometry Node Group Slot" + bl_description = "Remove Geometry Node Group Slot" + + def execute(self, context): + slots = context.scene.ntp_geometry_node_group_slots + idx = context.scene.ntp_geometry_node_group_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.ntp_geometry_node_group_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class GNG_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") + +class GNG_Panel(bpy.types.Panel): + bl_idname = "node.ntp_geometry_node_group_panel" + bl_label = "Node Groups" + bl_parent_id = panel.NTPGeoNodesPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = ("List of geometry node group objects to replicate") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "GNG_UIList", "", + context.scene, "ntp_geometry_node_group_slots", + context.scene, "ntp_geometry_node_group_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + GNG_UIList, + GNG_Panel +] \ No newline at end of file diff --git a/NodeToPython/geometry/ui/panel.py b/NodeToPython/geometry/ui/panel.py new file mode 100644 index 0000000..9874110 --- /dev/null +++ b/NodeToPython/geometry/ui/panel.py @@ -0,0 +1,26 @@ +import bpy + +class NTPGeoNodesPanel(bpy.types.Panel): + bl_label = "Geometry Nodes to Python" + bl_idname = "NODE_PT_ntp_geonodes" + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + layout = self.layout + +classes: list[type] = [ + NTPGeoNodesPanel +] \ No newline at end of file diff --git a/NodeToPython/shader/__init__.py b/NodeToPython/shader/__init__.py index 1c6d5b3..8444cf2 100644 --- a/NodeToPython/shader/__init__.py +++ b/NodeToPython/shader/__init__.py @@ -6,4 +6,6 @@ from . import operator from . import ui -import bpy \ No newline at end of file +import bpy + +modules = [] \ No newline at end of file From 4c1d966aed6282ba1f188e4cb3db025366906111 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:28:32 -0500 Subject: [PATCH 03/15] feat: shader nodes ui port --- NodeToPython/__init__.py | 6 +- NodeToPython/shader/__init__.py | 5 +- NodeToPython/shader/ui.py | 52 --------- NodeToPython/shader/ui/__init__.py | 17 +++ NodeToPython/shader/ui/materials.py | 108 +++++++++++++++++ NodeToPython/shader/ui/panel.py | 26 +++++ NodeToPython/shader/ui/shader_node_groups.py | 116 +++++++++++++++++++ 7 files changed, 272 insertions(+), 58 deletions(-) delete mode 100644 NodeToPython/shader/ui.py create mode 100644 NodeToPython/shader/ui/__init__.py create mode 100644 NodeToPython/shader/ui/materials.py create mode 100644 NodeToPython/shader/ui/panel.py create mode 100644 NodeToPython/shader/ui/shader_node_groups.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 13a491e..916ef1e 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -48,11 +48,7 @@ def unregister_props(): NodeToPythonMenu, #options options.NTPOptions, - options.NTPOptionsPanel, - #shader - shader.operator.NTPShaderOperator, - shader.ui.NTPShaderMenu, - shader.ui.NTPShaderPanel, + options.NTPOptionsPanel ] modules = [] diff --git a/NodeToPython/shader/__init__.py b/NodeToPython/shader/__init__.py index 8444cf2..cc9aa89 100644 --- a/NodeToPython/shader/__init__.py +++ b/NodeToPython/shader/__init__.py @@ -8,4 +8,7 @@ import bpy -modules = [] \ No newline at end of file +modules = [ + operator +] +modules += ui.modules \ No newline at end of file diff --git a/NodeToPython/shader/ui.py b/NodeToPython/shader/ui.py deleted file mode 100644 index 06f84b1..0000000 --- a/NodeToPython/shader/ui.py +++ /dev/null @@ -1,52 +0,0 @@ -import bpy -from bpy.types import Panel -from bpy.types import Menu -from .operator import NTPShaderOperator - -class NTPShaderPanel(Panel): - bl_label = "Material to Python" - bl_idname = "NODE_PT_mat_to_python" - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_context = '' - bl_category = "NodeToPython" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @classmethod - def poll(cls, context): - return True - - def draw_header(self, context): - layout = self.layout - - def draw(self, context): - layout = self.layout - row = layout.row() - - # Disables menu when there are no materials - materials = [mat for mat in bpy.data.materials if mat.node_tree] - materials_exist = len(materials) > 0 - row.enabled = materials_exist - - row.alignment = 'EXPAND' - row.operator_context = 'INVOKE_DEFAULT' - row.menu("NODE_MT_ntp_material", text="Materials") - -class NTPShaderMenu(Menu): - bl_idname = "NODE_MT_ntp_material" - bl_label = "Select Material" - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - for mat in bpy.data.materials: - if mat.node_tree: - op = layout.operator(NTPShaderOperator.bl_idname, - text=mat.name) - op.material_name = mat.name \ No newline at end of file diff --git a/NodeToPython/shader/ui/__init__.py b/NodeToPython/shader/ui/__init__.py new file mode 100644 index 0000000..e44539f --- /dev/null +++ b/NodeToPython/shader/ui/__init__.py @@ -0,0 +1,17 @@ +if "bpy" in locals(): + import importlib + importlib.reload(panel) + importlib.reload(materials) + importlib.reload(shader_node_groups) +else: + from . import panel + from . import materials + from . import shader_node_groups + +import bpy + +modules : list = [ + panel, + materials, + shader_node_groups +] \ No newline at end of file diff --git a/NodeToPython/shader/ui/materials.py b/NodeToPython/shader/ui/materials.py new file mode 100644 index 0000000..89fc672 --- /dev/null +++ b/NodeToPython/shader/ui/materials.py @@ -0,0 +1,108 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.material_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.material_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.material_slots + del bpy.types.Scene.material_slots_index + +class Slot(bpy.types.PropertyGroup): + name: bpy.props.StringProperty( + name="Material Name", + default="" + ) + + def poll_material(self, material: bpy.types.Material) -> bool: + for slot in bpy.context.scene.material_slots: + if slot is not self and slot.material == material: + return False + return material.use_nodes + + def update_material(self, context): + if self.material: + self.name = self.material.name + else: + self.name = "Material" + + material: bpy.props.PointerProperty( + name="Material", + type=bpy.types.Material, + poll=poll_material, + update=update_material + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_material_slot_add" + bl_label = "Add Material Slot" + bl_description = "Add Material Slot" + + def execute(self, context): + slots = context.scene.material_slots + slot = slots.add() + context.scene.material_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_material_slot_remove" + bl_label = "Remove Material Slot" + bl_description = "Remove Material Slot" + + def execute(self, context): + slots = context.scene.material_slots + idx = context.scene.material_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.material_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class Material_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "material", bpy.data, "materials", text="") + +class Material_Panel(bpy.types.Panel): + bl_idname = "node.ntp_material_panel" + bl_label = "Materials" + bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "List of bpy.types.Material objects to replicate" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "Material_UIList", "", + context.scene, "material_slots", + context.scene, "material_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + Material_UIList, + Material_Panel +] \ No newline at end of file diff --git a/NodeToPython/shader/ui/panel.py b/NodeToPython/shader/ui/panel.py new file mode 100644 index 0000000..938d172 --- /dev/null +++ b/NodeToPython/shader/ui/panel.py @@ -0,0 +1,26 @@ +import bpy + +class NTPShaderPanel(bpy.types.Panel): + bl_label = "Shader to Python" + bl_idname = "NODE_PT_ntp_shader" + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + layout = self.layout + +classes: list[type] = [ + NTPShaderPanel +] \ No newline at end of file diff --git a/NodeToPython/shader/ui/shader_node_groups.py b/NodeToPython/shader/ui/shader_node_groups.py new file mode 100644 index 0000000..59fd5f3 --- /dev/null +++ b/NodeToPython/shader/ui/shader_node_groups.py @@ -0,0 +1,116 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.ntp_shader_node_group_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_shader_node_group_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_shader_node_group_slots + del bpy.types.Scene.ntp_shader_node_group_slots_index + +class Slot(bpy.types.PropertyGroup): + """ + TODO: There's a bug where the filtering doesn't update when renaming a + slotted object. For now, we'll need to just remove and re-add the slot + to the UI list. + """ + name: bpy.props.StringProperty( + name="Node Tree Name", + default="" + ) + + def poll_node_tree(self, node_tree: bpy.types.NodeTree) -> bool: + scene = bpy.context.scene + + for slot in scene.ntp_shader_node_group_slots: + if slot is not self and slot.node_tree == node_tree: + return False + return node_tree.bl_idname == 'ShaderNodeTree' + + def update_node_tree(self, context): + if self.node_tree: + self.name = self.node_tree.name + else: + self.name = "Shader Node Group" + + node_tree: bpy.props.PointerProperty( + name="Node Tree", + type=bpy.types.NodeTree, + poll=poll_node_tree, + update=update_node_tree + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_shader_node_group_slot_add" + bl_label = "Add Shader Node Group Slot" + bl_description = "Add Shader Node Group Slot" + + def execute(self, context): + slots = context.scene.ntp_shader_node_group_slots + slot = slots.add() + context.scene.ntp_shader_node_group_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_shader_node_group_slot_remove" + bl_label = "Remove Shader Node Group Slot" + bl_description = "Remove Shader Node Group Slot" + + def execute(self, context): + slots = context.scene.ntp_shader_node_group_slots + idx = context.scene.ntp_shader_node_group_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.ntp_shader_node_group_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class SNG_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") + +class SNG_Panel(bpy.types.Panel): + bl_idname = "node.ntp_shader_node_group_panel" + bl_label = "Node Groups" + bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = ("List of shader node group objects to replicate.\n" + "These are typically subgroups within a larger material tree") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "SNG_UIList", "", + context.scene, "ntp_shader_node_group_slots", + context.scene, "ntp_shader_node_group_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + SNG_UIList, + SNG_Panel +] \ No newline at end of file From f007356ebcf1fdf4260c03985b80b286498f8524 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:49:24 -0500 Subject: [PATCH 04/15] cleanup: add ntp prefix to some scene properties --- NodeToPython/compositor/ui/scenes.py | 26 +++++++++++++------------- NodeToPython/shader/ui/materials.py | 24 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/NodeToPython/compositor/ui/scenes.py b/NodeToPython/compositor/ui/scenes.py index 7a28588..ac05b7a 100644 --- a/NodeToPython/compositor/ui/scenes.py +++ b/NodeToPython/compositor/ui/scenes.py @@ -3,15 +3,15 @@ from . import panel def register_props(): - bpy.types.Scene.scene_slots = bpy.props.CollectionProperty( + bpy.types.Scene.ntp_scene_slots = bpy.props.CollectionProperty( type=Slot ) - bpy.types.Scene.scene_slots_index = bpy.props.IntProperty() + bpy.types.Scene.ntp_scene_slots_index = bpy.props.IntProperty() def unregister_props(): - del bpy.types.Scene.scene_slots - del bpy.types.Scene.scene_slots_index - + del bpy.types.Scene.ntp_scene_slots + del bpy.types.Scene.ntp_scene_slots_index + class Slot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Scene Name", @@ -19,7 +19,7 @@ class Slot(bpy.types.PropertyGroup): ) def poll_scene(self, scene: bpy.types.Scene) -> bool: - for slot in bpy.context.scene.scene_slots: + for slot in bpy.context.scene.ntp_scene_slots: if slot is not self and slot.scene == scene: return False return scene.use_nodes @@ -43,9 +43,9 @@ class AddSlotOperator(bpy.types.Operator): bl_description = "Add Scene Slot" def execute(self, context): - slots = context.scene.scene_slots + slots = context.scene.ntp_scene_slots slot = slots.add() - context.scene.scene_slots_index = len(slots) - 1 + context.scene.ntp_scene_slots_index = len(slots) - 1 return {'FINISHED'} class RemoveSlotOperator(bpy.types.Operator): @@ -54,12 +54,12 @@ class RemoveSlotOperator(bpy.types.Operator): bl_description = "Remove Scene Slot" def execute(self, context): - slots = context.scene.scene_slots - idx = context.scene.scene_slots_index + slots = context.scene.ntp_scene_slots + idx = context.scene.ntp_scene_slots_index if idx >= 0 and idx < len(slots): slots.remove(idx) - context.scene.scene_slots_index = min( + context.scene.ntp_scene_slots_index = min( max(0, idx - 1), len(slots) - 1 ) return {'FINISHED'} @@ -91,8 +91,8 @@ def draw(self, context): row = layout.row() row.template_list( "Scene_UIList", "", - context.scene, "scene_slots", - context.scene, "scene_slots_index" + context.scene, "ntp_scene_slots", + context.scene, "ntp_scene_slots_index" ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/materials.py b/NodeToPython/shader/ui/materials.py index 89fc672..7dd8684 100644 --- a/NodeToPython/shader/ui/materials.py +++ b/NodeToPython/shader/ui/materials.py @@ -3,14 +3,14 @@ from . import panel def register_props(): - bpy.types.Scene.material_slots = bpy.props.CollectionProperty( + bpy.types.Scene.ntp_material_slots = bpy.props.CollectionProperty( type=Slot ) - bpy.types.Scene.material_slots_index = bpy.props.IntProperty() + bpy.types.Scene.ntp_material_slots_index = bpy.props.IntProperty() def unregister_props(): - del bpy.types.Scene.material_slots - del bpy.types.Scene.material_slots_index + del bpy.types.Scene.ntp_material_slots + del bpy.types.Scene.ntp_material_slots_index class Slot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( @@ -19,7 +19,7 @@ class Slot(bpy.types.PropertyGroup): ) def poll_material(self, material: bpy.types.Material) -> bool: - for slot in bpy.context.scene.material_slots: + for slot in bpy.context.scene.ntp_material_slots: if slot is not self and slot.material == material: return False return material.use_nodes @@ -43,9 +43,9 @@ class AddSlotOperator(bpy.types.Operator): bl_description = "Add Material Slot" def execute(self, context): - slots = context.scene.material_slots + slots = context.scene.ntp_material_slots slot = slots.add() - context.scene.material_slots_index = len(slots) - 1 + context.scene.ntp_material_slots_index = len(slots) - 1 return {'FINISHED'} class RemoveSlotOperator(bpy.types.Operator): @@ -54,12 +54,12 @@ class RemoveSlotOperator(bpy.types.Operator): bl_description = "Remove Material Slot" def execute(self, context): - slots = context.scene.material_slots - idx = context.scene.material_slots_index + slots = context.scene.ntp_material_slots + idx = context.scene.ntp_material_slots_index if idx >= 0 and idx < len(slots): slots.remove(idx) - context.scene.material_slots_index = min( + context.scene.ntp_material_slots_index = min( max(0, idx - 1), len(slots) - 1 ) return {'FINISHED'} @@ -91,8 +91,8 @@ def draw(self, context): row = layout.row() row.template_list( "Material_UIList", "", - context.scene, "material_slots", - context.scene, "material_slots_index" + context.scene, "ntp_material_slots", + context.scene, "ntp_material_slots_index" ) col = row.column(align=True) From 3739248173a0cedc1b967c60d3f9688d120faf53 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Tue, 30 Sep 2025 22:49:40 -0500 Subject: [PATCH 05/15] feat: add ui for adding world, linestyle, and light nodes --- NodeToPython/shader/ui/__init__.py | 11 ++- NodeToPython/shader/ui/lights.py | 108 +++++++++++++++++++++++++++ NodeToPython/shader/ui/linestyles.py | 108 +++++++++++++++++++++++++++ NodeToPython/shader/ui/worlds.py | 108 +++++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 NodeToPython/shader/ui/lights.py create mode 100644 NodeToPython/shader/ui/linestyles.py create mode 100644 NodeToPython/shader/ui/worlds.py diff --git a/NodeToPython/shader/ui/__init__.py b/NodeToPython/shader/ui/__init__.py index e44539f..be9175b 100644 --- a/NodeToPython/shader/ui/__init__.py +++ b/NodeToPython/shader/ui/__init__.py @@ -3,15 +3,24 @@ importlib.reload(panel) importlib.reload(materials) importlib.reload(shader_node_groups) + importlib.reload(worlds) + importlib.reload(linestyles) + importlib.reload(lights) else: from . import panel from . import materials from . import shader_node_groups + from . import worlds + from . import linestyles + from . import lights import bpy modules : list = [ panel, materials, - shader_node_groups + shader_node_groups, + worlds, + linestyles, + lights ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/lights.py b/NodeToPython/shader/ui/lights.py new file mode 100644 index 0000000..65a5bda --- /dev/null +++ b/NodeToPython/shader/ui/lights.py @@ -0,0 +1,108 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.ntp_light_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_light_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_light_slots + del bpy.types.Scene.ntp_light_slots_index + +class Slot(bpy.types.PropertyGroup): + name: bpy.props.StringProperty( + name="Light Name", + default="" + ) + + def poll_light(self, light: bpy.types.Light) -> bool: + for slot in bpy.context.scene.ntp_light_slots: + if slot is not self and slot.light == light: + return False + return light.use_nodes + + def update_light(self, context): + if self.light: + self.name = self.light.name + else: + self.name = "Light" + + light: bpy.props.PointerProperty( + name="Light", + type=bpy.types.Light, + poll=poll_light, + update=update_light + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_light_slot_add" + bl_label = "Add Light Slot" + bl_description = "Add Light Slot" + + def execute(self, context): + slots = context.scene.ntp_light_slots + slot = slots.add() + context.scene.ntp_light_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_light_slot_remove" + bl_label = "Remove Light Slot" + bl_description = "Remove Light Slot" + + def execute(self, context): + slots = context.scene.ntp_light_slots + idx = context.scene.ntp_light_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.ntp_light_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class Light_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "light", bpy.data, "lights", text="") + +class Light_Panel(bpy.types.Panel): + bl_idname = "node.ntp_light_panel" + bl_label = "Lights" + bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "List of bpy.types.Light objects to replicate" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "Light_UIList", "", + context.scene, "ntp_light_slots", + context.scene, "ntp_light_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + Light_UIList, + Light_Panel +] \ No newline at end of file diff --git a/NodeToPython/shader/ui/linestyles.py b/NodeToPython/shader/ui/linestyles.py new file mode 100644 index 0000000..08fc271 --- /dev/null +++ b/NodeToPython/shader/ui/linestyles.py @@ -0,0 +1,108 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.ntp_line_style_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_line_style_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_line_style_slots + del bpy.types.Scene.ntp_line_style_slots_index + +class Slot(bpy.types.PropertyGroup): + name: bpy.props.StringProperty( + name="Line Style Name", + default="" + ) + + def poll_line_style(self, line_style: bpy.types.FreestyleLineStyle) -> bool: + for slot in bpy.context.scene.ntp_line_style_slots: + if slot is not self and slot.line_style == line_style: + return False + return line_style.use_nodes + + def update_line_style(self, context): + if self.line_style: + self.name = self.line_style.name + else: + self.name = "Line Style" + + line_style: bpy.props.PointerProperty( + name="Line Style", + type=bpy.types.FreestyleLineStyle, + poll=poll_line_style, + update=update_line_style + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_line_style_slot_add" + bl_label = "Add Line Style Slot" + bl_description = "Add Line Style Slot" + + def execute(self, context): + slots = context.scene.ntp_line_style_slots + slot = slots.add() + context.scene.ntp_line_style_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_line_style_slot_remove" + bl_label = "Remove Line Style Slot" + bl_description = "Remove Line Style Slot" + + def execute(self, context): + slots = context.scene.ntp_line_style_slots + idx = context.scene.ntp_line_style_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.ntp_line_style_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class LineStyle_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "line_style", bpy.data, "linestyles", text="") + +class LineStyle_Panel(bpy.types.Panel): + bl_idname = "node.ntp_line_style_panel" + bl_label = "Line Styles" + bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "List of bpy.types.FreestyleLineStyle objects to replicate" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "LineStyle_UIList", "", + context.scene, "ntp_line_style_slots", + context.scene, "ntp_line_style_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + LineStyle_UIList, + LineStyle_Panel +] \ No newline at end of file diff --git a/NodeToPython/shader/ui/worlds.py b/NodeToPython/shader/ui/worlds.py new file mode 100644 index 0000000..c4a5fc6 --- /dev/null +++ b/NodeToPython/shader/ui/worlds.py @@ -0,0 +1,108 @@ +import bpy + +from . import panel + +def register_props(): + bpy.types.Scene.ntp_world_slots = bpy.props.CollectionProperty( + type=Slot + ) + bpy.types.Scene.ntp_world_slots_index = bpy.props.IntProperty() + +def unregister_props(): + del bpy.types.Scene.ntp_world_slots + del bpy.types.Scene.ntp_world_slots_index + +class Slot(bpy.types.PropertyGroup): + name: bpy.props.StringProperty( + name="World Name", + default="" + ) + + def poll_world(self, world: bpy.types.World) -> bool: + for slot in bpy.context.scene.ntp_world_slots: + if slot is not self and slot.world == world: + return False + return world.use_nodes + + def update_world(self, context): + if self.world: + self.name = self.world.name + else: + self.name = "World" + + world: bpy.props.PointerProperty( + name="World", + type=bpy.types.World, + poll=poll_world, + update=update_world + ) + +class AddSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_world_slot_add" + bl_label = "Add World Slot" + bl_description = "Add World Slot" + + def execute(self, context): + slots = context.scene.ntp_world_slots + slot = slots.add() + context.scene.ntp_world_slots_index = len(slots) - 1 + return {'FINISHED'} + +class RemoveSlotOperator(bpy.types.Operator): + bl_idname = "node.ntp_world_slot_remove" + bl_label = "Remove World Slot" + bl_description = "Remove World Slot" + + def execute(self, context): + slots = context.scene.ntp_world_slots + idx = context.scene.ntp_world_slots_index + + if idx >= 0 and idx < len(slots): + slots.remove(idx) + context.scene.ntp_world_slots_index = min( + max(0, idx - 1), len(slots) - 1 + ) + return {'FINISHED'} + +class World_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active): + if item: + layout.prop_search(item, "world", bpy.data, "worlds", text="") + +class World_Panel(bpy.types.Panel): + bl_idname = "node.ntp_world_panel" + bl_label = "Worlds" + bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "List of bpy.types.World objects to replicate" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + row.template_list( + "World_UIList", "", + context.scene, "ntp_world_slots", + context.scene, "ntp_world_slots_index" + ) + + col = row.column(align=True) + col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") + col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + +classes: list[type] = [ + Slot, + AddSlotOperator, + RemoveSlotOperator, + World_UIList, + World_Panel +] \ No newline at end of file From 1d260746d820203a0395503e01ca8d962c25d201 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:04:45 -0500 Subject: [PATCH 06/15] refactor: class renamings, fix bug on some slot removal operators --- NodeToPython/compositor/operator.py | 6 +-- .../compositor/ui/compositor_node_groups.py | 43 +++++++++++-------- NodeToPython/compositor/ui/panel.py | 7 +-- NodeToPython/compositor/ui/scenes.py | 41 ++++++++++-------- NodeToPython/geometry/operator.py | 6 +-- .../geometry/ui/geometry_node_groups.py | 39 +++++++++-------- NodeToPython/geometry/ui/panel.py | 7 +-- NodeToPython/shader/operator.py | 6 +-- NodeToPython/shader/ui/__init__.py | 6 +-- NodeToPython/shader/ui/lights.py | 41 ++++++++++-------- .../ui/{linestyles.py => line_styles.py} | 41 ++++++++++-------- NodeToPython/shader/ui/materials.py | 41 ++++++++++-------- NodeToPython/shader/ui/panel.py | 9 ++-- NodeToPython/shader/ui/shader_node_groups.py | 41 ++++++++++-------- NodeToPython/shader/ui/worlds.py | 41 ++++++++++-------- 15 files changed, 202 insertions(+), 173 deletions(-) rename NodeToPython/shader/ui/{linestyles.py => line_styles.py} (74%) diff --git a/NodeToPython/compositor/operator.py b/NodeToPython/compositor/operator.py index 99c2906..cf6723e 100644 --- a/NodeToPython/compositor/operator.py +++ b/NodeToPython/compositor/operator.py @@ -16,8 +16,8 @@ COMP_OP_RESERVED_NAMES = {SCENE, BASE_NAME, END_NAME, NODE} -class NTPCompositorOperator(NTP_Operator): - bl_idname = "node.ntp_compositor" +class NTP_OT_Compositor(NTP_Operator): + bl_idname = "ntp.compositor" bl_label = "Compositor to Python" bl_options = {'REGISTER', 'UNDO'} @@ -283,5 +283,5 @@ def execute(self, context): return {'FINISHED'} classes: list[type] = [ - NTPCompositorOperator + NTP_OT_Compositor ] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/compositor_node_groups.py b/NodeToPython/compositor/ui/compositor_node_groups.py index fd4954a..305a3cc 100644 --- a/NodeToPython/compositor/ui/compositor_node_groups.py +++ b/NodeToPython/compositor/ui/compositor_node_groups.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_compositor_node_group_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_CompositorNodeGroupSlot ) bpy.types.Scene.ntp_compositor_node_group_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_compositor_node_group_slots del bpy.types.Scene.ntp_compositor_node_group_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_CompositorNodeGroupSlot(bpy.types.PropertyGroup): """ TODO: There's a bug where the filtering doesn't update when renaming a slotted object. For now, we'll need to just remove and re-add the slot @@ -44,19 +44,19 @@ def update_node_tree(self, context): update=update_node_tree ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_compositor_node_group_slot_add" +class NTP_OT_AddCompositorNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.add_compositor_node_group_slot" bl_label = "Add Compositor Node Group Slot" bl_description = "Add Compositor Node Group Slot" def execute(self, context): slots = context.scene.ntp_compositor_node_group_slots - slot = slots.add() + slots.add() context.scene.ntp_compositor_node_group_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_compositor_node_group_slot_remove" +class NTP_OT_RemoveCompositorNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.remove_compositor_node_group_slot" bl_label = "Remove Compositor Node Group Slot" bl_description = "Remove Compositor Node Group Slot" @@ -71,21 +71,24 @@ def execute(self, context): ) return {'FINISHED'} -class CNG_UIList(bpy.types.UIList): +class NTP_UL_CompositorNodeGroup(bpy.types.UIList): + bl_idname = "NTP_UL_compositor_node_group" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") -class CNG_Panel(bpy.types.Panel): - bl_idname = "node.ntp_compositor_node_group_panel" +class NTP_PT_CompositorNodeGroup(bpy.types.Panel): + bl_idname = "NTP_PT_compositor_node_group" bl_label = "Node Groups" - bl_parent_id = panel.NTPCompositorPanel.bl_idname + bl_parent_id = panel.NTP_PT_Compositor.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = ("List of compositor node group objects to replicate.\n" "These are typically subgroups within a larger scene tree") + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -98,19 +101,21 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "CNG_UIList", "", + NTP_UL_CompositorNodeGroup.bl_idname, "", context.scene, "ntp_compositor_node_group_slots", context.scene, "ntp_compositor_node_group_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddCompositorNodeGroupSlot.bl_idname, + icon="ADD", text="") + col.operator(NTP_OT_RemoveCompositorNodeGroupSlot.bl_idname, + icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - CNG_UIList, - CNG_Panel + NTP_PG_CompositorNodeGroupSlot, + NTP_OT_AddCompositorNodeGroupSlot, + NTP_OT_RemoveCompositorNodeGroupSlot, + NTP_UL_CompositorNodeGroup, + NTP_PT_CompositorNodeGroup ] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/panel.py b/NodeToPython/compositor/ui/panel.py index a24a7b5..a221ce0 100644 --- a/NodeToPython/compositor/ui/panel.py +++ b/NodeToPython/compositor/ui/panel.py @@ -1,12 +1,13 @@ import bpy -class NTPCompositorPanel(bpy.types.Panel): +class NTP_PT_Compositor(bpy.types.Panel): bl_label = "Compositor to Python" - bl_idname = "NODE_PT_ntp_compositor" + bl_idname = "NTP_PT_compositor" bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -22,5 +23,5 @@ def draw(self, context): layout = self.layout classes: list[type] = [ - NTPCompositorPanel + NTP_PT_Compositor ] \ No newline at end of file diff --git a/NodeToPython/compositor/ui/scenes.py b/NodeToPython/compositor/ui/scenes.py index ac05b7a..82ffe25 100644 --- a/NodeToPython/compositor/ui/scenes.py +++ b/NodeToPython/compositor/ui/scenes.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_scene_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_SceneSlot ) bpy.types.Scene.ntp_scene_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_scene_slots del bpy.types.Scene.ntp_scene_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_SceneSlot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Scene Name", default="" @@ -37,8 +37,8 @@ def update_scene(self, context): update=update_scene ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_scene_slot_add" +class NTP_OT_AddSceneSlot(bpy.types.Operator): + bl_idname = "ntp.add_scene_slot" bl_label = "Add Scene Slot" bl_description = "Add Scene Slot" @@ -48,8 +48,8 @@ def execute(self, context): context.scene.ntp_scene_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_scene_slot_remove" +class NTP_OT_RemoveSceneSlot(bpy.types.Operator): + bl_idname = "ntp.remove_scene_slot" bl_label = "Remove Scene Slot" bl_description = "Remove Scene Slot" @@ -62,22 +62,25 @@ def execute(self, context): context.scene.ntp_scene_slots_index = min( max(0, idx - 1), len(slots) - 1 ) - return {'FINISHED'} + return {'FINISHED'} -class Scene_UIList(bpy.types.UIList): +class NTP_UL_Scene(bpy.types.UIList): + bl_idname = "NTP_UL_scene" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "scene", bpy.data, "scenes", text="") -class Scene_Panel(bpy.types.Panel): - bl_idname = "node.ntp_scene_panel" +class NTP_PT_Scene(bpy.types.Panel): + bl_idname = "NTP_PT_scene" bl_label = "Scenes" - bl_parent_id = panel.NTPCompositorPanel.bl_idname + bl_parent_id = panel.NTP_PT_Compositor.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = "List of bpy.types.Scene objects to replicate" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,19 +93,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "Scene_UIList", "", + NTP_UL_Scene.bl_idname, "", context.scene, "ntp_scene_slots", context.scene, "ntp_scene_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddSceneSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveSceneSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - Scene_UIList, - Scene_Panel + NTP_PG_SceneSlot, + NTP_OT_AddSceneSlot, + NTP_OT_RemoveSceneSlot, + NTP_UL_Scene, + NTP_PT_Scene ] \ No newline at end of file diff --git a/NodeToPython/geometry/operator.py b/NodeToPython/geometry/operator.py index 8fecd7e..f991b6d 100644 --- a/NodeToPython/geometry/operator.py +++ b/NodeToPython/geometry/operator.py @@ -16,9 +16,9 @@ OBJECT, MODIFIER} -class NTPGeoNodesOperator(NTP_Operator): - bl_idname = "node.ntp_geo_nodes" - bl_label = "Geo Nodes to Python" +class NTP_OT_GeometryNodes(NTP_Operator): + bl_idname = "ntp.geometry_nodes" + bl_label = "Geometry Nodes to Python" bl_options = {'REGISTER', 'UNDO'} geo_nodes_group_name: bpy.props.StringProperty(name="Node Group") diff --git a/NodeToPython/geometry/ui/geometry_node_groups.py b/NodeToPython/geometry/ui/geometry_node_groups.py index d8bf6af..b934e9c 100644 --- a/NodeToPython/geometry/ui/geometry_node_groups.py +++ b/NodeToPython/geometry/ui/geometry_node_groups.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_geometry_node_group_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_GeometryNodeGroupSlot ) bpy.types.Scene.ntp_geometry_node_group_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_geometry_node_group_slots del bpy.types.Scene.ntp_geometry_node_group_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_GeometryNodeGroupSlot(bpy.types.PropertyGroup): """ TODO: There's a bug where the filtering doesn't update when renaming a slotted object. For now, we'll need to just remove and re-add the slot @@ -44,8 +44,8 @@ def update_node_tree(self, context): update=update_node_tree ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_geometry_node_group_slot_add" +class NTP_OT_AddGeometryNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.add_geometry_node_group_slot" bl_label = "Add Geometry Node Group Slot" bl_description = "Add Geometry Node Group Slot" @@ -55,8 +55,8 @@ def execute(self, context): context.scene.ntp_geometry_node_group_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_geometry_node_group_slot_remove" +class NTP_OT_RemoveGeometryNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.remove_geometry_node_group_slot" bl_label = "Remove Geometry Node Group Slot" bl_description = "Remove Geometry Node Group Slot" @@ -71,20 +71,23 @@ def execute(self, context): ) return {'FINISHED'} -class GNG_UIList(bpy.types.UIList): +class NTP_UL_GeometryNodeGroup(bpy.types.UIList): + bl_idname = "NTP_UL_geometry_node_group" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") -class GNG_Panel(bpy.types.Panel): - bl_idname = "node.ntp_geometry_node_group_panel" +class NTP_PT_GeometryNodeGroup(bpy.types.Panel): + bl_idname = "NTP_PT_geometry_node_group" bl_label = "Node Groups" - bl_parent_id = panel.NTPGeoNodesPanel.bl_idname + bl_parent_id = panel.NTP_PT_GeometryNodes.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = ("List of geometry node group objects to replicate") + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -97,19 +100,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "GNG_UIList", "", + NTP_UL_GeometryNodeGroup.bl_idname, "", context.scene, "ntp_geometry_node_group_slots", context.scene, "ntp_geometry_node_group_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddGeometryNodeGroupSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveGeometryNodeGroupSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - GNG_UIList, - GNG_Panel + NTP_PG_GeometryNodeGroupSlot, + NTP_OT_AddGeometryNodeGroupSlot, + NTP_OT_RemoveGeometryNodeGroupSlot, + NTP_UL_GeometryNodeGroup, + NTP_PT_GeometryNodeGroup ] \ No newline at end of file diff --git a/NodeToPython/geometry/ui/panel.py b/NodeToPython/geometry/ui/panel.py index 9874110..d63f01b 100644 --- a/NodeToPython/geometry/ui/panel.py +++ b/NodeToPython/geometry/ui/panel.py @@ -1,12 +1,13 @@ import bpy -class NTPGeoNodesPanel(bpy.types.Panel): +class NTP_PT_GeometryNodes(bpy.types.Panel): bl_label = "Geometry Nodes to Python" - bl_idname = "NODE_PT_ntp_geonodes" + bl_idname = "NTP_PT_geometry_nodes" bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -22,5 +23,5 @@ def draw(self, context): layout = self.layout classes: list[type] = [ - NTPGeoNodesPanel + NTP_PT_GeometryNodes ] \ No newline at end of file diff --git a/NodeToPython/shader/operator.py b/NodeToPython/shader/operator.py index 2a6342e..efefd91 100644 --- a/NodeToPython/shader/operator.py +++ b/NodeToPython/shader/operator.py @@ -13,9 +13,9 @@ NODE = "node" SHADER_OP_RESERVED_NAMES = {MAT_VAR, NODE} -class NTPShaderOperator(NTP_Operator): - bl_idname = "node.ntp_material" - bl_label = "Material to Python" +class NTP_OT_Shader(NTP_Operator): + bl_idname = "ntp.shader" + bl_label = "Shader to Python" bl_options = {'REGISTER', 'UNDO'} #TODO: add option for general shader node groups diff --git a/NodeToPython/shader/ui/__init__.py b/NodeToPython/shader/ui/__init__.py index be9175b..cc8f63a 100644 --- a/NodeToPython/shader/ui/__init__.py +++ b/NodeToPython/shader/ui/__init__.py @@ -4,14 +4,14 @@ importlib.reload(materials) importlib.reload(shader_node_groups) importlib.reload(worlds) - importlib.reload(linestyles) + importlib.reload(line_styles) importlib.reload(lights) else: from . import panel from . import materials from . import shader_node_groups from . import worlds - from . import linestyles + from . import line_styles from . import lights import bpy @@ -21,6 +21,6 @@ materials, shader_node_groups, worlds, - linestyles, + line_styles, lights ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/lights.py b/NodeToPython/shader/ui/lights.py index 65a5bda..2ba0fda 100644 --- a/NodeToPython/shader/ui/lights.py +++ b/NodeToPython/shader/ui/lights.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_light_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_LightSlot ) bpy.types.Scene.ntp_light_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_light_slots del bpy.types.Scene.ntp_light_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_LightSlot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Light Name", default="" @@ -37,8 +37,8 @@ def update_light(self, context): update=update_light ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_light_slot_add" +class NTP_OT_AddLightSlot(bpy.types.Operator): + bl_idname = "ntp.add_light_slot" bl_label = "Add Light Slot" bl_description = "Add Light Slot" @@ -48,8 +48,8 @@ def execute(self, context): context.scene.ntp_light_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_light_slot_remove" +class NTP_OT_RemoveLightSlot(bpy.types.Operator): + bl_idname = "ntp.remove_light_slot" bl_label = "Remove Light Slot" bl_description = "Remove Light Slot" @@ -62,22 +62,25 @@ def execute(self, context): context.scene.ntp_light_slots_index = min( max(0, idx - 1), len(slots) - 1 ) - return {'FINISHED'} + return {'FINISHED'} -class Light_UIList(bpy.types.UIList): +class NTP_UL_Light(bpy.types.UIList): + bl_idname = "NTP_UL_light" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "light", bpy.data, "lights", text="") -class Light_Panel(bpy.types.Panel): - bl_idname = "node.ntp_light_panel" +class NTP_PT_Light(bpy.types.Panel): + bl_idname = "NTP_PT_light" bl_label = "Lights" - bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_parent_id = panel.NTP_PT_Shader.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = "List of bpy.types.Light objects to replicate" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,19 +93,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "Light_UIList", "", + NTP_UL_Light.bl_idname, "", context.scene, "ntp_light_slots", context.scene, "ntp_light_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddLightSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveLightSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - Light_UIList, - Light_Panel + NTP_PG_LightSlot, + NTP_OT_AddLightSlot, + NTP_OT_RemoveLightSlot, + NTP_UL_Light, + NTP_PT_Light ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/linestyles.py b/NodeToPython/shader/ui/line_styles.py similarity index 74% rename from NodeToPython/shader/ui/linestyles.py rename to NodeToPython/shader/ui/line_styles.py index 08fc271..7afd841 100644 --- a/NodeToPython/shader/ui/linestyles.py +++ b/NodeToPython/shader/ui/line_styles.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_line_style_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_LineStyleSlot ) bpy.types.Scene.ntp_line_style_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_line_style_slots del bpy.types.Scene.ntp_line_style_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_LineStyleSlot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Line Style Name", default="" @@ -37,8 +37,8 @@ def update_line_style(self, context): update=update_line_style ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_line_style_slot_add" +class NTP_OT_AddLineStyleSlot(bpy.types.Operator): + bl_idname = "ntp.add_line_style_slot" bl_label = "Add Line Style Slot" bl_description = "Add Line Style Slot" @@ -48,8 +48,8 @@ def execute(self, context): context.scene.ntp_line_style_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_line_style_slot_remove" +class NTP_OT_RemoveLineStyleSlot(bpy.types.Operator): + bl_idname = "ntp.remove_line_style_slot" bl_label = "Remove Line Style Slot" bl_description = "Remove Line Style Slot" @@ -62,22 +62,25 @@ def execute(self, context): context.scene.ntp_line_style_slots_index = min( max(0, idx - 1), len(slots) - 1 ) - return {'FINISHED'} + return {'FINISHED'} -class LineStyle_UIList(bpy.types.UIList): +class NTP_UL_LineStyle(bpy.types.UIList): + bl_idname = "NTP_UL_line_style" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "line_style", bpy.data, "linestyles", text="") -class LineStyle_Panel(bpy.types.Panel): - bl_idname = "node.ntp_line_style_panel" +class NTP_PT_LineStyle(bpy.types.Panel): + bl_idname = "NTP_PT_line_style" bl_label = "Line Styles" - bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_parent_id = panel.NTP_PT_Shader.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = "List of bpy.types.FreestyleLineStyle objects to replicate" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,19 +93,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "LineStyle_UIList", "", + NTP_UL_LineStyle.bl_idname, "", context.scene, "ntp_line_style_slots", context.scene, "ntp_line_style_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddLineStyleSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveLineStyleSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - LineStyle_UIList, - LineStyle_Panel + NTP_PG_LineStyleSlot, + NTP_OT_AddLineStyleSlot, + NTP_OT_RemoveLineStyleSlot, + NTP_UL_LineStyle, + NTP_PT_LineStyle ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/materials.py b/NodeToPython/shader/ui/materials.py index 7dd8684..6c9c78b 100644 --- a/NodeToPython/shader/ui/materials.py +++ b/NodeToPython/shader/ui/materials.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_material_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_MaterialSlot ) bpy.types.Scene.ntp_material_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_material_slots del bpy.types.Scene.ntp_material_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_MaterialSlot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="Material Name", default="" @@ -37,8 +37,8 @@ def update_material(self, context): update=update_material ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_material_slot_add" +class NTP_OT_AddMaterialSlot(bpy.types.Operator): + bl_idname = "ntp.add_material_slot" bl_label = "Add Material Slot" bl_description = "Add Material Slot" @@ -48,8 +48,8 @@ def execute(self, context): context.scene.ntp_material_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_material_slot_remove" +class NTP_OT_RemoveMaterialSlot(bpy.types.Operator): + bl_idname = "ntp.remove_material_slot" bl_label = "Remove Material Slot" bl_description = "Remove Material Slot" @@ -62,22 +62,25 @@ def execute(self, context): context.scene.ntp_material_slots_index = min( max(0, idx - 1), len(slots) - 1 ) - return {'FINISHED'} + return {'FINISHED'} -class Material_UIList(bpy.types.UIList): +class NTP_UL_Material(bpy.types.UIList): + bl_idname = "NTP_UL_material" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "material", bpy.data, "materials", text="") -class Material_Panel(bpy.types.Panel): - bl_idname = "node.ntp_material_panel" +class NTP_PT_Material(bpy.types.Panel): + bl_idname = "NTP_PT_material" bl_label = "Materials" - bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_parent_id = panel.NTP_PT_Shader.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = "List of bpy.types.Material objects to replicate" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,19 +93,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "Material_UIList", "", + NTP_UL_Material.bl_idname, "", context.scene, "ntp_material_slots", context.scene, "ntp_material_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddMaterialSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveMaterialSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - Material_UIList, - Material_Panel + NTP_PG_MaterialSlot, + NTP_OT_AddMaterialSlot, + NTP_OT_RemoveMaterialSlot, + NTP_UL_Material, + NTP_PT_Material ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/panel.py b/NodeToPython/shader/ui/panel.py index 938d172..896907a 100644 --- a/NodeToPython/shader/ui/panel.py +++ b/NodeToPython/shader/ui/panel.py @@ -1,13 +1,14 @@ import bpy -class NTPShaderPanel(bpy.types.Panel): +class NTP_PT_Shader(bpy.types.Panel): bl_label = "Shader to Python" - bl_idname = "NODE_PT_ntp_shader" + bl_idname = "NTP_PT_shader" bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" - + bl_options = {'DEFAULT_CLOSED'} + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -22,5 +23,5 @@ def draw(self, context): layout = self.layout classes: list[type] = [ - NTPShaderPanel + NTP_PT_Shader ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/shader_node_groups.py b/NodeToPython/shader/ui/shader_node_groups.py index 59fd5f3..8c928d2 100644 --- a/NodeToPython/shader/ui/shader_node_groups.py +++ b/NodeToPython/shader/ui/shader_node_groups.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_shader_node_group_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_ShaderNodeGroupSlot ) bpy.types.Scene.ntp_shader_node_group_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_shader_node_group_slots del bpy.types.Scene.ntp_shader_node_group_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_ShaderNodeGroupSlot(bpy.types.PropertyGroup): """ TODO: There's a bug where the filtering doesn't update when renaming a slotted object. For now, we'll need to just remove and re-add the slot @@ -44,8 +44,8 @@ def update_node_tree(self, context): update=update_node_tree ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_shader_node_group_slot_add" +class NTP_OT_AddShaderNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.add_shader_node_group_slot" bl_label = "Add Shader Node Group Slot" bl_description = "Add Shader Node Group Slot" @@ -55,8 +55,8 @@ def execute(self, context): context.scene.ntp_shader_node_group_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_shader_node_group_slot_remove" +class NTP_OT_RemoveShaderNodeGroupSlot(bpy.types.Operator): + bl_idname = "ntp.remove_shader_node_group_slot" bl_label = "Remove Shader Node Group Slot" bl_description = "Remove Shader Node Group Slot" @@ -71,22 +71,25 @@ def execute(self, context): ) return {'FINISHED'} -class SNG_UIList(bpy.types.UIList): +class NTP_UL_ShaderNodeGroup(bpy.types.UIList): + bl_idname = "NTP_UL_shader_node_group" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "node_tree", bpy.data, "node_groups", text="") -class SNG_Panel(bpy.types.Panel): - bl_idname = "node.ntp_shader_node_group_panel" +class NTP_PT_ShaderNodeGroup(bpy.types.Panel): + bl_idname = "NTP_PT_shader_node_group" bl_label = "Node Groups" - bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_parent_id = panel.NTP_PT_Shader.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = ("List of shader node group objects to replicate.\n" "These are typically subgroups within a larger material tree") - + bl_options = {'DEFAULT_CLOSED'} + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -98,19 +101,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "SNG_UIList", "", + NTP_UL_ShaderNodeGroup.bl_idname, "", context.scene, "ntp_shader_node_group_slots", context.scene, "ntp_shader_node_group_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddShaderNodeGroupSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveShaderNodeGroupSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - SNG_UIList, - SNG_Panel + NTP_PG_ShaderNodeGroupSlot, + NTP_OT_AddShaderNodeGroupSlot, + NTP_OT_RemoveShaderNodeGroupSlot, + NTP_UL_ShaderNodeGroup, + NTP_PT_ShaderNodeGroup ] \ No newline at end of file diff --git a/NodeToPython/shader/ui/worlds.py b/NodeToPython/shader/ui/worlds.py index c4a5fc6..34c012f 100644 --- a/NodeToPython/shader/ui/worlds.py +++ b/NodeToPython/shader/ui/worlds.py @@ -4,7 +4,7 @@ def register_props(): bpy.types.Scene.ntp_world_slots = bpy.props.CollectionProperty( - type=Slot + type=NTP_PG_WorldSlot ) bpy.types.Scene.ntp_world_slots_index = bpy.props.IntProperty() @@ -12,7 +12,7 @@ def unregister_props(): del bpy.types.Scene.ntp_world_slots del bpy.types.Scene.ntp_world_slots_index -class Slot(bpy.types.PropertyGroup): +class NTP_PG_WorldSlot(bpy.types.PropertyGroup): name: bpy.props.StringProperty( name="World Name", default="" @@ -37,8 +37,8 @@ def update_world(self, context): update=update_world ) -class AddSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_world_slot_add" +class NTP_OT_AddWorldSlot(bpy.types.Operator): + bl_idname = "ntp.add_world_slot" bl_label = "Add World Slot" bl_description = "Add World Slot" @@ -48,8 +48,8 @@ def execute(self, context): context.scene.ntp_world_slots_index = len(slots) - 1 return {'FINISHED'} -class RemoveSlotOperator(bpy.types.Operator): - bl_idname = "node.ntp_world_slot_remove" +class NTP_OT_RemoveWorldSlot(bpy.types.Operator): + bl_idname = "ntp.remove_world_slot" bl_label = "Remove World Slot" bl_description = "Remove World Slot" @@ -62,22 +62,25 @@ def execute(self, context): context.scene.ntp_world_slots_index = min( max(0, idx - 1), len(slots) - 1 ) - return {'FINISHED'} + return {'FINISHED'} -class World_UIList(bpy.types.UIList): +class NTP_UL_World(bpy.types.UIList): + bl_idname = "NTP_UL_world" + def draw_item(self, context, layout, data, item, icon, active_data, active): if item: layout.prop_search(item, "world", bpy.data, "worlds", text="") -class World_Panel(bpy.types.Panel): - bl_idname = "node.ntp_world_panel" +class NTP_PT_World(bpy.types.Panel): + bl_idname = "NTP_PT_world" bl_label = "Worlds" - bl_parent_id = panel.NTPShaderPanel.bl_idname + bl_parent_id = panel.NTP_PT_Shader.bl_idname bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_context = '' bl_category = "NodeToPython" bl_description = "List of bpy.types.World objects to replicate" + bl_options = {'DEFAULT_CLOSED'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -90,19 +93,19 @@ def draw(self, context): layout = self.layout row = layout.row() row.template_list( - "World_UIList", "", + NTP_UL_World.bl_idname, "", context.scene, "ntp_world_slots", context.scene, "ntp_world_slots_index" ) col = row.column(align=True) - col.operator(AddSlotOperator.bl_idname, icon="ADD", text="") - col.operator(RemoveSlotOperator.bl_idname, icon="REMOVE", text="") + col.operator(NTP_OT_AddWorldSlot.bl_idname, icon="ADD", text="") + col.operator(NTP_OT_RemoveWorldSlot.bl_idname, icon="REMOVE", text="") classes: list[type] = [ - Slot, - AddSlotOperator, - RemoveSlotOperator, - World_UIList, - World_Panel + NTP_PG_WorldSlot, + NTP_OT_AddWorldSlot, + NTP_OT_RemoveWorldSlot, + NTP_UL_World, + NTP_PT_World ] \ No newline at end of file From d80318a531d93c5fd663038c3b0182ec63b46125 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:25:23 -0500 Subject: [PATCH 07/15] refactor: more descriptive option names/tooltips --- NodeToPython/__init__.py | 18 +----- NodeToPython/ntp_operator.py | 8 +-- NodeToPython/options.py | 107 ++++++++++++++++++++--------------- 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 916ef1e..681ee51 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -35,34 +35,21 @@ def draw(self, context): layout = self.layout.column_flow(columns=1) layout.operator_context = 'INVOKE_DEFAULT' -def register_props(): - bpy.types.Scene.ntp_options = bpy.props.PointerProperty( - type=options.NTPOptions - ) - -def unregister_props(): - del bpy.types.Scene.ntp_options - # TODO: do away with this, separate out into more appropriate modules classes: list[type] = [ - NodeToPythonMenu, - #options - options.NTPOptions, - options.NTPOptionsPanel + NodeToPythonMenu ] -modules = [] +modules = [options] for parent_module in [compositor, geometry, shader]: if hasattr(parent_module, "modules"): modules += parent_module.modules else: raise Exception(f"Module {parent_module} does not have list of modules") - def register(): for cls in classes: bpy.utils.register_class(cls) - register_props() for module in modules: if hasattr(module, "classes"): @@ -74,7 +61,6 @@ def register(): def unregister(): for cls in classes: bpy.utils.unregister_class(cls) - unregister_props() for module in modules: if hasattr(module, "classes"): diff --git a/NodeToPython/ntp_operator.py b/NodeToPython/ntp_operator.py index 9db9d44..8b1d0c5 100644 --- a/NodeToPython/ntp_operator.py +++ b/NodeToPython/ntp_operator.py @@ -17,7 +17,7 @@ from .license_templates import license_templates from .ntp_node_tree import NTP_NodeTree -from .options import NTPOptions +from .options import NTP_PG_Options from .node_settings import NodeInfo, ST from .utils import * @@ -125,7 +125,7 @@ def _write(self, string: str, indent_level: int = None): indent_str = indent_level * self._indentation self._file.write(f"{indent_str}{string}\n") - def _setup_options(self, options: NTPOptions) -> bool: + def _setup_options(self, options: NTP_PG_Options) -> bool: if bpy.app.version >= MAX_BLENDER_VERSION: self.report({'WARNING'}, f"Blender version {bpy.app.version} is not supported yet!\n" @@ -137,8 +137,8 @@ def _setup_options(self, options: NTPOptions) -> bool: # General self._mode = options.mode - self._include_group_socket_values = options.include_group_socket_values - self._should_set_dimensions = options.set_dimensions + self._include_group_socket_values = options.set_group_defaults + self._should_set_dimensions = options.set_node_sizes if options.indentation_type == 'SPACES_2': self._indentation = " " diff --git a/NodeToPython/options.py b/NodeToPython/options.py index da8c0f1..d0cebba 100644 --- a/NodeToPython/options.py +++ b/NodeToPython/options.py @@ -1,6 +1,14 @@ import bpy -class NTPOptions(bpy.types.PropertyGroup): +def register_props(): + bpy.types.Scene.ntp_options = bpy.props.PointerProperty( + type=NTP_PG_Options + ) + +def unregister_props(): + del bpy.types.Scene.ntp_options + +class NTP_PG_Options(bpy.types.PropertyGroup): """ Property group used during conversion of node group to python """ @@ -16,13 +24,13 @@ def __init__(self, *args, **kwargs): ('ADDON', "Addon", "Create a full add-on") ] ) - include_group_socket_values : bpy.props.BoolProperty( - name = "Include group socket values", + set_group_defaults : bpy.props.BoolProperty( + name = "Set Group Defaults", description = "Generate group socket default, min, and max values", default = True ) - set_dimensions : bpy.props.BoolProperty( - name = "Set dimensions", + set_node_sizes : bpy.props.BoolProperty( + name = "Set Node Sizes", description = "Set dimensions of generated nodes", default = True ) @@ -48,8 +56,8 @@ def __init__(self, *args, **kwargs): #Script properties include_imports : bpy.props.BoolProperty( - name = "Include imports", - description="Generate necessary import statements", + name = "Include Imports", + description="Generate necessary import statements (i.e. bpy, mathutils, etc)", default = True ) @@ -71,7 +79,7 @@ def __init__(self, *args, **kwargs): default="" ) author_name : bpy.props.StringProperty( - name = "Author", + name = "Author Name", description = "Name used for the author/maintainer of the add-on", default = "Node To Python" ) @@ -82,13 +90,13 @@ def __init__(self, *args, **kwargs): ) location: bpy.props.StringProperty( name = "Location", - description="Location of the addon", + description="Location property of the addon", default="Node" ) menu_id: bpy.props.StringProperty( name = "Menu ID", description = "Python ID of the menu you'd like to register the add-on " - "to. You can find this by enabling Python tooltips " + "to. You can find these by enabling Python tooltips " "(Preferences > Interface > Python tooltips) and " "hovering over the desired menu", default="NODE_MT_add" @@ -100,7 +108,7 @@ def __init__(self, *args, **kwargs): ('OTHER', "Other", "User is responsible for including the license " "and adding it to the manifest.\n" "Please note that by using the Blender Python " - "API your add-on must comply with the GNU GPL. " + "API, your add-on must comply with the GNU GPL. " "See https://www.blender.org/about/license/ for " "more details") ], @@ -108,53 +116,53 @@ def __init__(self, *args, **kwargs): ) should_create_license: bpy.props.BoolProperty( name="Create License", - description="Should NodeToPython include a license file", + description="Should NodeToPython include a license file for your add-on", default=True ) category: bpy.props.EnumProperty( name = "Category", items = [ - ('Custom', "Custom", "Use an unofficial category"), - ('3D View', "3D View", ""), - ('Add Curve', "Add Curve", ""), - ('Add Mesh', "Add Mesh", ""), - ('Animation', "Animation", ""), - ('Bake', "Bake", ""), - ('Compositing', "Compositing", ""), - ('Development', "Development", ""), - ('Game Engine', "Game Engine", ""), - ('Geometry Nodes', "Geometry Nodes", ""), - ("Grease Pencil", "Grease Pencil", ""), - ('Import-Export', "Import-Export", ""), - ('Lighting', "Lighting", ""), - ('Material', "Material", ""), - ('Mesh', "Mesh", ""), - ('Modeling', "Modeling", ""), - ('Node', "Node", ""), - ('Object', "Object", ""), - ('Paint', "Paint", ""), - ('Pipeline', "Pipeline", ""), - ('Physics', "Physics", ""), - ('Render', "Render", ""), - ('Rigging', "Rigging", ""), - ('Scene', "Scene", ""), - ('Sculpt', "Sculpt", ""), - ('Sequencer', "Sequencer", ""), - ('System', "System", ""), - ('Text Editor', "Text Editor", ""), - ('Tracking', "Tracking", ""), - ('UV', "UV", ""), - ('User Interface', "User Interface", ""), + ('Custom', "Custom", "Use an unofficial category"), + ('3D View', "3D View", ""), + ('Add Curve', "Add Curve", ""), + ('Add Mesh', "Add Mesh", ""), + ('Animation', "Animation", ""), + ('Bake', "Bake", ""), + ('Compositing', "Compositing", ""), + ('Development', "Development", ""), + ('Game Engine', "Game Engine", ""), + ('Geometry Nodes', "Geometry Nodes", ""), + ("Grease Pencil", "Grease Pencil", ""), + ('Import-Export', "Import-Export", ""), + ('Lighting', "Lighting", ""), + ('Material', "Material", ""), + ('Mesh', "Mesh", ""), + ('Modeling', "Modeling", ""), + ('Node', "Node", ""), + ('Object', "Object", ""), + ('Paint', "Paint", ""), + ('Pipeline', "Pipeline", ""), + ('Physics', "Physics", ""), + ('Render', "Render", ""), + ('Rigging', "Rigging", ""), + ('Scene', "Scene", ""), + ('Sculpt', "Sculpt", ""), + ('Sequencer', "Sequencer", ""), + ('System', "System", ""), + ('Text Editor', "Text Editor", ""), + ('Tracking', "Tracking", ""), + ('UV', "UV", ""), + ('User Interface', "User Interface", ""), ], default = 'Node' ) custom_category: bpy.props.StringProperty( name="Custom Category", - description="Custom category", + description="Set the custom category property for your add-on", default = "" ) -class NTPOptionsPanel(bpy.types.Panel): +class NTP_PT_Options(bpy.types.Panel): bl_label = "Options" bl_idname = "NODE_PT_ntp_options" bl_space_type = 'NODE_EDITOR' @@ -201,8 +209,13 @@ def draw(self, context): "category" ] option_list += addon_options - if ntp_options.category == 'CUSTOM': + if ntp_options.category == 'Custom': option_list.append("custom_category") for option in option_list: - layout.prop(ntp_options, option) \ No newline at end of file + layout.prop(ntp_options, option) + +classes = [ + NTP_PG_Options, + NTP_PT_Options +] \ No newline at end of file From cc81659ce05041eeda2ae266358a90c0363ef040 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:28:07 -0500 Subject: [PATCH 08/15] fix: bring back group defaults and node sizes --- NodeToPython/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NodeToPython/options.py b/NodeToPython/options.py index d0cebba..b40b90c 100644 --- a/NodeToPython/options.py +++ b/NodeToPython/options.py @@ -183,8 +183,8 @@ def draw(self, context): option_list = [ "mode", - "include_group_socket_values", - "set_dimensions", + "set_group_defaults", + "set_node_sizes", "indentation_type" ] if bpy.app.version >= (3, 4, 0): From 05f798f772d797f508aa32281f363a1d10a39c3f Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:41:33 -0500 Subject: [PATCH 09/15] refactor: move menu to its own file, rename options file --- NodeToPython/__init__.py | 32 ++++----------------- NodeToPython/ntp_menu.py | 13 +++++++++ NodeToPython/ntp_operator.py | 2 +- NodeToPython/{options.py => ntp_options.py} | 0 4 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 NodeToPython/ntp_menu.py rename NodeToPython/{options.py => ntp_options.py} (100%) diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 681ee51..2de8800 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -13,34 +13,18 @@ importlib.reload(compositor) importlib.reload(geometry) importlib.reload(shader) - importlib.reload(options) + importlib.reload(ntp_menu) + importlib.reload(ntp_options) else: from . import compositor from . import geometry from . import shader - from . import options + from . import ntp_menu + from . import ntp_options import bpy - -class NodeToPythonMenu(bpy.types.Menu): - bl_idname = "NODE_MT_node_to_python" - bl_label = "Node To Python" - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - -# TODO: do away with this, separate out into more appropriate modules -classes: list[type] = [ - NodeToPythonMenu -] - -modules = [options] +modules = [ntp_menu, ntp_options] for parent_module in [compositor, geometry, shader]: if hasattr(parent_module, "modules"): modules += parent_module.modules @@ -48,9 +32,6 @@ def draw(self, context): raise Exception(f"Module {parent_module} does not have list of modules") def register(): - for cls in classes: - bpy.utils.register_class(cls) - for module in modules: if hasattr(module, "classes"): for cls in getattr(module, "classes"): @@ -59,9 +40,6 @@ def register(): getattr(module, "register_props")() def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) - for module in modules: if hasattr(module, "classes"): for cls in getattr(module, "classes"): diff --git a/NodeToPython/ntp_menu.py b/NodeToPython/ntp_menu.py new file mode 100644 index 0000000..d48cb34 --- /dev/null +++ b/NodeToPython/ntp_menu.py @@ -0,0 +1,13 @@ +import bpy + +class NodeToPythonMenu(bpy.types.Menu): + bl_idname = "NODE_MT_node_to_python" + bl_label = "Node To Python" + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout.column_flow(columns=1) + layout.operator_context = 'INVOKE_DEFAULT' \ No newline at end of file diff --git a/NodeToPython/ntp_operator.py b/NodeToPython/ntp_operator.py index 8b1d0c5..4ca91b2 100644 --- a/NodeToPython/ntp_operator.py +++ b/NodeToPython/ntp_operator.py @@ -17,7 +17,7 @@ from .license_templates import license_templates from .ntp_node_tree import NTP_NodeTree -from .options import NTP_PG_Options +from .ntp_options import NTP_PG_Options from .node_settings import NodeInfo, ST from .utils import * diff --git a/NodeToPython/options.py b/NodeToPython/ntp_options.py similarity index 100% rename from NodeToPython/options.py rename to NodeToPython/ntp_options.py From b2d4c652324d34a992070668f90e2aeeaf064952 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:44:14 -0500 Subject: [PATCH 10/15] refactor: main menu naming consistency --- NodeToPython/ntp_menu.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/NodeToPython/ntp_menu.py b/NodeToPython/ntp_menu.py index d48cb34..7ae0108 100644 --- a/NodeToPython/ntp_menu.py +++ b/NodeToPython/ntp_menu.py @@ -1,7 +1,7 @@ import bpy -class NodeToPythonMenu(bpy.types.Menu): - bl_idname = "NODE_MT_node_to_python" +class NTP_MT_NodeToPython(bpy.types.Menu): + bl_idname = "NTP_MT_node_to_python" bl_label = "Node To Python" @classmethod @@ -10,4 +10,8 @@ def poll(cls, context): def draw(self, context): layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' \ No newline at end of file + layout.operator_context = 'INVOKE_DEFAULT' + +classes = [ + NTP_MT_NodeToPython +] \ No newline at end of file From 4c24ec08a6ee8398586f7eac5519c7a168572322 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:59:48 -0500 Subject: [PATCH 11/15] cleanup: remove unnecessary menu class --- NodeToPython/__init__.py | 4 +--- NodeToPython/ntp_menu.py | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 NodeToPython/ntp_menu.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 2de8800..80f51b5 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -13,18 +13,16 @@ importlib.reload(compositor) importlib.reload(geometry) importlib.reload(shader) - importlib.reload(ntp_menu) importlib.reload(ntp_options) else: from . import compositor from . import geometry from . import shader - from . import ntp_menu from . import ntp_options import bpy -modules = [ntp_menu, ntp_options] +modules = [ntp_options] for parent_module in [compositor, geometry, shader]: if hasattr(parent_module, "modules"): modules += parent_module.modules diff --git a/NodeToPython/ntp_menu.py b/NodeToPython/ntp_menu.py deleted file mode 100644 index 7ae0108..0000000 --- a/NodeToPython/ntp_menu.py +++ /dev/null @@ -1,17 +0,0 @@ -import bpy - -class NTP_MT_NodeToPython(bpy.types.Menu): - bl_idname = "NTP_MT_node_to_python" - bl_label = "Node To Python" - - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout.column_flow(columns=1) - layout.operator_context = 'INVOKE_DEFAULT' - -classes = [ - NTP_MT_NodeToPython -] \ No newline at end of file From 96946a3e362a8500d927e544106f062a2f659869 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sat, 4 Oct 2025 20:39:47 -0500 Subject: [PATCH 12/15] feat: reorganize options panel --- NodeToPython/__init__.py | 9 +++-- NodeToPython/ntp_options.py | 55 +------------------------- NodeToPython/ui/__init__.py | 20 ++++++++++ NodeToPython/ui/addon_settings.py | 46 +++++++++++++++++++++ NodeToPython/ui/generation_settings.py | 46 +++++++++++++++++++++ NodeToPython/ui/main.py | 26 ++++++++++++ NodeToPython/ui/settings.py | 35 ++++++++++++++++ 7 files changed, 179 insertions(+), 58 deletions(-) create mode 100644 NodeToPython/ui/__init__.py create mode 100644 NodeToPython/ui/addon_settings.py create mode 100644 NodeToPython/ui/generation_settings.py create mode 100644 NodeToPython/ui/main.py create mode 100644 NodeToPython/ui/settings.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 80f51b5..1d86214 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -10,20 +10,21 @@ if "bpy" in locals(): import importlib + importlib.reload(ntp_options) + importlib.reload(ui) importlib.reload(compositor) importlib.reload(geometry) importlib.reload(shader) - importlib.reload(ntp_options) else: + from . import ntp_options + from . import ui from . import compositor from . import geometry from . import shader - from . import ntp_options - import bpy modules = [ntp_options] -for parent_module in [compositor, geometry, shader]: +for parent_module in [ui, compositor, geometry, shader]: if hasattr(parent_module, "modules"): modules += parent_module.modules else: diff --git a/NodeToPython/ntp_options.py b/NodeToPython/ntp_options.py index b40b90c..b0488c7 100644 --- a/NodeToPython/ntp_options.py +++ b/NodeToPython/ntp_options.py @@ -162,60 +162,7 @@ def __init__(self, *args, **kwargs): default = "" ) -class NTP_PT_Options(bpy.types.Panel): - bl_label = "Options" - bl_idname = "NODE_PT_ntp_options" - bl_space_type = 'NODE_EDITOR' - bl_region_type = 'UI' - bl_context = '' - bl_category = "NodeToPython" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @classmethod - def poll(cls, context): - return True - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_DEFAULT' - ntp_options = context.scene.ntp_options - - option_list = [ - "mode", - "set_group_defaults", - "set_node_sizes", - "indentation_type" - ] - if bpy.app.version >= (3, 4, 0): - option_list.append("set_unavailable_defaults") - - if ntp_options.mode == 'SCRIPT': - script_options = [ - "include_imports" - ] - option_list += script_options - elif ntp_options.mode == 'ADDON': - addon_options = [ - "dir_path", - "name_override", - "description", - "author_name", - "version", - "location", - "menu_id", - "license", - "should_create_license", - "category" - ] - option_list += addon_options - if ntp_options.category == 'Custom': - option_list.append("custom_category") - - for option in option_list: - layout.prop(ntp_options, option) classes = [ - NTP_PG_Options, - NTP_PT_Options + NTP_PG_Options ] \ No newline at end of file diff --git a/NodeToPython/ui/__init__.py b/NodeToPython/ui/__init__.py new file mode 100644 index 0000000..1a54699 --- /dev/null +++ b/NodeToPython/ui/__init__.py @@ -0,0 +1,20 @@ +if "bpy" in locals(): + import importlib + importlib.reload(main) + importlib.reload(settings) + importlib.reload(generation_settings) + importlib.reload(addon_settings) +else: + from . import main + from . import settings + from . import generation_settings + from . import addon_settings + +import bpy + +modules = [ + main, + settings, + generation_settings, + addon_settings +] \ No newline at end of file diff --git a/NodeToPython/ui/addon_settings.py b/NodeToPython/ui/addon_settings.py new file mode 100644 index 0000000..45224c8 --- /dev/null +++ b/NodeToPython/ui/addon_settings.py @@ -0,0 +1,46 @@ +import bpy + +from . import settings + +class NTP_PT_AddonSettings(bpy.types.Panel): + bl_idname = "NTP_PT_addon_settings" + bl_label = "Add-on Settings" + bl_parent_id = settings.NTP_PT_Settings.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return context.scene.ntp_options.mode == 'ADDON' + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + ntp_options = context.scene.ntp_options + + addon_options = [ + "dir_path", + "name_override", + "description", + "author_name", + "version", + "location", + "menu_id", + "license", + "should_create_license", + "category" + ] + if ntp_options.category == 'Custom': + addon_options.append("custom_category") + for option in addon_options: + layout.prop(ntp_options, option) + +classes: list[type] = [ + NTP_PT_AddonSettings +] \ No newline at end of file diff --git a/NodeToPython/ui/generation_settings.py b/NodeToPython/ui/generation_settings.py new file mode 100644 index 0000000..687a036 --- /dev/null +++ b/NodeToPython/ui/generation_settings.py @@ -0,0 +1,46 @@ +import bpy + +from . import settings + +class NTP_PT_GenerationSettings(bpy.types.Panel): + bl_idname = "NTP_PT_generation_settings" + bl_label = "Generation Settings" + bl_parent_id = settings.NTP_PT_Settings.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + ntp_options = context.scene.ntp_options + + generation_options = [ + "set_group_defaults", + "set_node_sizes", + "indentation_type" + ] + if bpy.app.version >= (3, 4, 0): + generation_options.append("set_unavailable_defaults") + + if ntp_options.mode == 'SCRIPT': + script_options = [ + "include_imports" + ] + generation_options += script_options + + for option in generation_options: + layout.prop(ntp_options, option) + +classes: list[type] = [ + NTP_PT_GenerationSettings +] \ No newline at end of file diff --git a/NodeToPython/ui/main.py b/NodeToPython/ui/main.py new file mode 100644 index 0000000..77e3837 --- /dev/null +++ b/NodeToPython/ui/main.py @@ -0,0 +1,26 @@ +import bpy + +class NTP_PT_Main(bpy.types.Panel): + bl_idname = "NTP_PT_main" + bl_label = "Node To Python" + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw_header(self, context): + layout = self.layout + + def draw(self, context): + layout = self.layout + +classes: list[type] = [ + NTP_PT_Main +] \ No newline at end of file diff --git a/NodeToPython/ui/settings.py b/NodeToPython/ui/settings.py new file mode 100644 index 0000000..e92d1f5 --- /dev/null +++ b/NodeToPython/ui/settings.py @@ -0,0 +1,35 @@ +import bpy + +from . import main + +class NTP_PT_Settings(bpy.types.Panel): + bl_idname = "NTP_PT_settings" + bl_label = "Settings" + bl_parent_id = main.NTP_PT_Main.bl_idname + bl_space_type = 'NODE_EDITOR' + bl_region_type = 'UI' + bl_context = '' + bl_category = "NodeToPython" + bl_description = "" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_DEFAULT' + ntp_options = context.scene.ntp_options + + option_list = [ + "mode" + ] + for option in option_list: + layout.prop(ntp_options, option) + +classes: list[type] = [ + NTP_PT_Settings +] \ No newline at end of file From a866f2468b910ff7050bda6ac2b828d8e418f138 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sun, 5 Oct 2025 13:41:29 -0500 Subject: [PATCH 13/15] feat: shrink default ui list size --- NodeToPython/compositor/ui/compositor_node_groups.py | 3 ++- NodeToPython/compositor/ui/scenes.py | 3 ++- NodeToPython/geometry/ui/geometry_node_groups.py | 3 ++- NodeToPython/shader/ui/lights.py | 3 ++- NodeToPython/shader/ui/line_styles.py | 3 ++- NodeToPython/shader/ui/materials.py | 3 ++- NodeToPython/shader/ui/shader_node_groups.py | 3 ++- NodeToPython/shader/ui/worlds.py | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/NodeToPython/compositor/ui/compositor_node_groups.py b/NodeToPython/compositor/ui/compositor_node_groups.py index 305a3cc..aaed0b8 100644 --- a/NodeToPython/compositor/ui/compositor_node_groups.py +++ b/NodeToPython/compositor/ui/compositor_node_groups.py @@ -103,7 +103,8 @@ def draw(self, context): row.template_list( NTP_UL_CompositorNodeGroup.bl_idname, "", context.scene, "ntp_compositor_node_group_slots", - context.scene, "ntp_compositor_node_group_slots_index" + context.scene, "ntp_compositor_node_group_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/compositor/ui/scenes.py b/NodeToPython/compositor/ui/scenes.py index 82ffe25..c1b7f46 100644 --- a/NodeToPython/compositor/ui/scenes.py +++ b/NodeToPython/compositor/ui/scenes.py @@ -95,7 +95,8 @@ def draw(self, context): row.template_list( NTP_UL_Scene.bl_idname, "", context.scene, "ntp_scene_slots", - context.scene, "ntp_scene_slots_index" + context.scene, "ntp_scene_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/geometry/ui/geometry_node_groups.py b/NodeToPython/geometry/ui/geometry_node_groups.py index b934e9c..4804408 100644 --- a/NodeToPython/geometry/ui/geometry_node_groups.py +++ b/NodeToPython/geometry/ui/geometry_node_groups.py @@ -102,7 +102,8 @@ def draw(self, context): row.template_list( NTP_UL_GeometryNodeGroup.bl_idname, "", context.scene, "ntp_geometry_node_group_slots", - context.scene, "ntp_geometry_node_group_slots_index" + context.scene, "ntp_geometry_node_group_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/lights.py b/NodeToPython/shader/ui/lights.py index 2ba0fda..811b80a 100644 --- a/NodeToPython/shader/ui/lights.py +++ b/NodeToPython/shader/ui/lights.py @@ -95,7 +95,8 @@ def draw(self, context): row.template_list( NTP_UL_Light.bl_idname, "", context.scene, "ntp_light_slots", - context.scene, "ntp_light_slots_index" + context.scene, "ntp_light_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/line_styles.py b/NodeToPython/shader/ui/line_styles.py index 7afd841..2f724c2 100644 --- a/NodeToPython/shader/ui/line_styles.py +++ b/NodeToPython/shader/ui/line_styles.py @@ -95,7 +95,8 @@ def draw(self, context): row.template_list( NTP_UL_LineStyle.bl_idname, "", context.scene, "ntp_line_style_slots", - context.scene, "ntp_line_style_slots_index" + context.scene, "ntp_line_style_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/materials.py b/NodeToPython/shader/ui/materials.py index 6c9c78b..999738d 100644 --- a/NodeToPython/shader/ui/materials.py +++ b/NodeToPython/shader/ui/materials.py @@ -95,7 +95,8 @@ def draw(self, context): row.template_list( NTP_UL_Material.bl_idname, "", context.scene, "ntp_material_slots", - context.scene, "ntp_material_slots_index" + context.scene, "ntp_material_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/shader_node_groups.py b/NodeToPython/shader/ui/shader_node_groups.py index 8c928d2..1f21351 100644 --- a/NodeToPython/shader/ui/shader_node_groups.py +++ b/NodeToPython/shader/ui/shader_node_groups.py @@ -103,7 +103,8 @@ def draw(self, context): row.template_list( NTP_UL_ShaderNodeGroup.bl_idname, "", context.scene, "ntp_shader_node_group_slots", - context.scene, "ntp_shader_node_group_slots_index" + context.scene, "ntp_shader_node_group_slots_index", + rows=1 ) col = row.column(align=True) diff --git a/NodeToPython/shader/ui/worlds.py b/NodeToPython/shader/ui/worlds.py index 34c012f..a9ee7a8 100644 --- a/NodeToPython/shader/ui/worlds.py +++ b/NodeToPython/shader/ui/worlds.py @@ -95,7 +95,8 @@ def draw(self, context): row.template_list( NTP_UL_World.bl_idname, "", context.scene, "ntp_world_slots", - context.scene, "ntp_world_slots_index" + context.scene, "ntp_world_slots_index", + rows=1 ) col = row.column(align=True) From afdc8c8f4dd9e0094c0e72e391e33aaf0a3512d4 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:39:50 -0500 Subject: [PATCH 14/15] feat: new export button and node group gatherer class --- NodeToPython/__init__.py | 4 +- NodeToPython/export_operator.py | 99 +++++++++++++++++++++++++++++++++ NodeToPython/ui/main.py | 40 ++++++++++++- 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 NodeToPython/export_operator.py diff --git a/NodeToPython/__init__.py b/NodeToPython/__init__.py index 1d86214..08394f9 100644 --- a/NodeToPython/__init__.py +++ b/NodeToPython/__init__.py @@ -10,12 +10,14 @@ if "bpy" in locals(): import importlib + importlib.reload(export_operator) importlib.reload(ntp_options) importlib.reload(ui) importlib.reload(compositor) importlib.reload(geometry) importlib.reload(shader) else: + from . import export_operator from . import ntp_options from . import ui from . import compositor @@ -23,7 +25,7 @@ from . import shader import bpy -modules = [ntp_options] +modules = [export_operator, ntp_options] for parent_module in [ui, compositor, geometry, shader]: if hasattr(parent_module, "modules"): modules += parent_module.modules diff --git a/NodeToPython/export_operator.py b/NodeToPython/export_operator.py new file mode 100644 index 0000000..1b871fe --- /dev/null +++ b/NodeToPython/export_operator.py @@ -0,0 +1,99 @@ +import bpy + +from enum import Enum, auto + +class NodeGroupType(Enum): + COMPOSITOR_NODE_GROUP = auto() + SCENE = auto() + GEOMETRY_NODE_GROUP = auto() + LIGHT = auto() + LINE_STYLE = auto() + MATERIAL = auto() + SHADER_NODE_GROUP = auto() + WORLD = auto() + +class NodeGroupGatherer: + def __init__(self): + self.node_groups : dict[NodeGroupType, list] = { + NodeGroupType.COMPOSITOR_NODE_GROUP : [], + NodeGroupType.SCENE : [], + NodeGroupType.GEOMETRY_NODE_GROUP : [], + NodeGroupType.LIGHT : [], + NodeGroupType.LINE_STYLE : [], + NodeGroupType.MATERIAL : [], + NodeGroupType.SHADER_NODE_GROUP : [], + NodeGroupType.WORLD : [], + } + + def gather_node_groups(self, context: bpy.types.Context): + for group_slot in context.scene.ntp_compositor_node_group_slots: + if group_slot.node_tree is not None: + self.node_groups[NodeGroupType.COMPOSITOR_NODE_GROUP].append( + group_slot.node_tree + ) + for scene_slot in context.scene.ntp_scene_slots: + if scene_slot.scene is not None: + self.node_groups[NodeGroupType.SCENE].append(scene_slot.scene) + + for group_slot in context.scene.ntp_geometry_node_group_slots: + if group_slot.node_tree is not None: + self.node_groups[NodeGroupType.GEOMETRY_NODE_GROUP].append( + group_slot.node_tree + ) + + for light_slot in context.scene.ntp_light_slots: + if light_slot.light is not None: + self.node_groups[NodeGroupType.LIGHT].append(light_slot.light) + + for line_style_slot in context.scene.ntp_line_style_slots: + if line_style_slot.line_style is not None: + self.node_groups[NodeGroupType.LINE_STYLE].append( + line_style_slot.line_style + ) + + for material_slot in context.scene.ntp_material_slots: + if material_slot.material is not None: + self.node_groups[NodeGroupType.MATERIAL].append( + material_slot.material + ) + + for group_slot in context.scene.ntp_shader_node_group_slots: + if group_slot.node_tree is not None: + self.node_groups[NodeGroupType.SHADER_NODE_GROUP].append( + group_slot.node_tree + ) + + for world_slot in context.scene.ntp_world_slots: + if world_slot.world is not None: + self.node_groups[NodeGroupType.WORLD].append(world_slot.world) + + def get_number_node_groups(self) -> int: + result = 0 + for lst in self.node_groups.values(): + result += len(lst) + return result + + def get_single_node_group(self): + # TODO: better name/ergonomics in general + if self.get_number_node_groups() != 1: + raise ValueError("Expected just one node group") + + for lst in self.node_groups.values(): + if len(lst) == 1: + return lst[0] + + raise AssertionError("Expected this to be unreachable") + + +class NTP_OT_Export(bpy.types.Operator): + bl_idname = "ntp.export" + bl_label = "Export" + + + def execute(self, context): + print("Hello world!") + return {'FINISHED'} + +classes = [ + NTP_OT_Export +] \ No newline at end of file diff --git a/NodeToPython/ui/main.py b/NodeToPython/ui/main.py index 77e3837..96ff8eb 100644 --- a/NodeToPython/ui/main.py +++ b/NodeToPython/ui/main.py @@ -1,5 +1,9 @@ import bpy +from ..export_operator import NTP_OT_Export, NodeGroupGatherer + +import pathlib + class NTP_PT_Main(bpy.types.Panel): bl_idname = "NTP_PT_main" bl_label = "Node To Python" @@ -18,8 +22,42 @@ def poll(cls, context): def draw_header(self, context): layout = self.layout - def draw(self, context): + def draw(self, context: bpy.types.Context): layout = self.layout + col = layout.column(align=True) + row = col.row() + + ntp_options = context.scene.ntp_options + location = "" + export_icon = '' + if ntp_options.mode == 'SCRIPT': + location = "clipboard" + export_icon = 'COPYDOWN' + elif ntp_options.mode == 'ADDON': + location = pathlib.PurePath(ntp_options.dir_path).name + export_icon = 'CONSOLE' + + gatherer = NodeGroupGatherer() + gatherer.gather_node_groups(context) + num_node_groups = gatherer.get_number_node_groups() + + if num_node_groups == 1: + node_group = gatherer.get_single_node_group().name + else: + node_group = f"{num_node_groups} node groups" + + export_text = f"Export {node_group} to {location}" + + if num_node_groups == 0: + export_text = f"0 node groups selected!" + export_icon = 'WARNING_LARGE' + + export_button = row.operator( + NTP_OT_Export.bl_idname, + text=export_text, + icon=export_icon + ) + row.enabled = num_node_groups > 0 classes: list[type] = [ NTP_PT_Main From 9362e7bee65c72bbe7d1c1626fc8fb26580245a0 Mon Sep 17 00:00:00 2001 From: BrendanParmer <51296046+BrendanParmer@users.noreply.github.com> Date: Sun, 5 Oct 2025 14:58:15 -0500 Subject: [PATCH 15/15] feat: button actually exports node groups --- NodeToPython/export_operator.py | 34 ++++++++++++++++++++++++------ NodeToPython/geometry/node_tree.py | 11 +--------- NodeToPython/geometry/operator.py | 20 ++++++++++++------ NodeToPython/shader/operator.py | 6 +++++- NodeToPython/ui/main.py | 2 +- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/NodeToPython/export_operator.py b/NodeToPython/export_operator.py index 1b871fe..48a577a 100644 --- a/NodeToPython/export_operator.py +++ b/NodeToPython/export_operator.py @@ -78,20 +78,42 @@ def get_single_node_group(self): if self.get_number_node_groups() != 1: raise ValueError("Expected just one node group") - for lst in self.node_groups.values(): + for group_type, lst in self.node_groups.items(): if len(lst) == 1: - return lst[0] + return group_type, lst[0] raise AssertionError("Expected this to be unreachable") - class NTP_OT_Export(bpy.types.Operator): bl_idname = "ntp.export" bl_label = "Export" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context: bpy.types.Context): + gatherer = NodeGroupGatherer() + gatherer.gather_node_groups(context) + + if gatherer.get_number_node_groups() != 1: + self.report({'ERROR'}, "Can only export one node group currently") + return {'CANCELLED'} + + group_type, obj = gatherer.get_single_node_group() + + if group_type == NodeGroupType.COMPOSITOR_NODE_GROUP: + bpy.ops.ntp.compositor(compositor_name=obj.name, is_scene=False) + elif group_type == NodeGroupType.SCENE: + bpy.ops.ntp.compositor(compositor_name=obj.name, is_scene=True) + elif group_type == NodeGroupType.GEOMETRY_NODE_GROUP: + bpy.ops.ntp.geometry_nodes(geo_nodes_group_name=obj.name) + elif group_type == NodeGroupType.LIGHT: + pass + elif group_type == NodeGroupType.MATERIAL: + bpy.ops.ntp.shader(material_name=obj.name) + elif group_type == NodeGroupType.SHADER_NODE_GROUP: + pass + elif group_type == NodeGroupType.WORLD: + pass - - def execute(self, context): - print("Hello world!") return {'FINISHED'} classes = [ diff --git a/NodeToPython/geometry/node_tree.py b/NodeToPython/geometry/node_tree.py index 8129276..9dcd02c 100644 --- a/NodeToPython/geometry/node_tree.py +++ b/NodeToPython/geometry/node_tree.py @@ -1,21 +1,12 @@ import bpy from bpy.types import GeometryNodeTree, GeometryNode -if bpy.app.version >= (3, 6, 0): - from bpy.types import GeometryNodeSimulationInput - -if bpy.app.version >= (4, 0, 0): - from bpy.types import GeometryNodeRepeatInput - -if bpy.app.version >= (4, 3, 0): - from bpy.types import GeometryNodeForeachGeometryElementInput - from ..ntp_node_tree import NTP_NodeTree class NTP_GeoNodeTree(NTP_NodeTree): def __init__(self, node_tree: GeometryNodeTree, var: str): super().__init__(node_tree, var) - self.zone_inputs: dict[list[GeometryNode]] = {} + self.zone_inputs: dict[str, list[GeometryNode]] = {} if bpy.app.version >= (3, 6, 0): self.zone_inputs["GeometryNodeSimulationInput"] = [] if bpy.app.version >= (4, 0, 0): diff --git a/NodeToPython/geometry/operator.py b/NodeToPython/geometry/operator.py index f991b6d..f3bf146 100644 --- a/NodeToPython/geometry/operator.py +++ b/NodeToPython/geometry/operator.py @@ -94,12 +94,14 @@ 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_object", - "is_mode_edit", - "is_mode_sculpt", - "is_type_curve", - "is_type_mesh", - "is_type_point_cloud"] + 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: if hasattr(node_tree, flag) is True: @@ -220,4 +222,8 @@ def execute(self, context): self._report_finished("geometry node group") - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} + +classes = [ + NTP_OT_GeometryNodes +] \ No newline at end of file diff --git a/NodeToPython/shader/operator.py b/NodeToPython/shader/operator.py index efefd91..831ca9e 100644 --- a/NodeToPython/shader/operator.py +++ b/NodeToPython/shader/operator.py @@ -189,4 +189,8 @@ def execute(self, context): self._report_finished("material") - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} + +classes = [ + NTP_OT_Shader +] \ No newline at end of file diff --git a/NodeToPython/ui/main.py b/NodeToPython/ui/main.py index 96ff8eb..fb33206 100644 --- a/NodeToPython/ui/main.py +++ b/NodeToPython/ui/main.py @@ -42,7 +42,7 @@ def draw(self, context: bpy.types.Context): num_node_groups = gatherer.get_number_node_groups() if num_node_groups == 1: - node_group = gatherer.get_single_node_group().name + node_group = gatherer.get_single_node_group()[1].name else: node_group = f"{num_node_groups} node groups"