Skip to content

Commit e03db69

Browse files
Merge pull request #72 from griptape-ai/fix/issue-69-preserve-shared-griptape-menu
fix: stop wiping the shared Nodes > Griptape menu on refresh
2 parents af1c46f + 448a8ac commit e03db69

2 files changed

Lines changed: 53 additions & 6 deletions

File tree

publish_gizmo/nuke_gizmo_publisher.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ def _regenerate_menu_py(self, griptape_dir: Path) -> None:
366366
``nuke.plugins()`` to discover ALL versioned ``.gizmo`` files at call time,
367367
calls ``nuke.load()`` on each to force Nuke to re-read the TCL from disk,
368368
and rebuilds the Griptape node-creation entries on the Nodes toolbar.
369+
The refresh tracks the menu items it adds (module-level list in the
370+
generated code) and removes only those on each rescan, so the
371+
Nodes > Griptape menu can be shared with other plugins (e.g. Nuke's
372+
built-in Griptape workflow node).
369373
370374
"Refresh Griptape Gizmos" lives on the main Nuke menu bar (not the Nodes
371375
toolbar) so that clicking it never triggers Nuke's node-placement mode.
@@ -394,12 +398,20 @@ def _regenerate_menu_py(self, griptape_dir: Path) -> None:
394398
# Module-level reference keeps the watcher alive (Python GC would drop a local).
395399
_GRIPTAPE_WATCHER = None
396400
401+
# Labels of items THIS script added to Nodes > Griptape. The menu is shared --
402+
# other plugins (e.g. Nuke's built-in Griptape workflow node) may add items --
403+
# so a refresh must only remove entries we created, never the whole menu.
404+
_GRIPTAPE_MENU_ITEMS = []
405+
397406
398407
def _refresh_griptape_menu():
399408
\"\"\"Rescan the griptape directory and rebuild the Griptape node-creation menu.
400409
401410
Call this after publishing a new or updated gizmo to make it available
402411
without restarting Nuke.
412+
413+
Only entries added by this script are removed and re-added; items other
414+
plugins placed in the Griptape menu are left untouched.
403415
\"\"\"
404416
# Remove then re-add the path to force Nuke to re-walk the directory.
405417
# pluginAddPath is idempotent on an already-registered path; the remove
@@ -429,15 +441,23 @@ def _refresh_griptape_menu():
429441
for stem in workflows:
430442
workflows[stem].sort()
431443
432-
# Rebuild the Griptape submenu on the Nodes toolbar.
433-
# Remove the existing entry first so repeated calls don't accumulate duplicates.
444+
# Get-or-create the Griptape submenu on the Nodes toolbar. addMenu()
445+
# returns the existing menu when one with this name already exists, so
446+
# items added by other plugins (e.g. Nuke's built-in Griptape workflow
447+
# node) are preserved. Never remove the menu itself.
434448
nodes_toolbar = nuke.menu('Nodes')
435-
try:
436-
nodes_toolbar.removeItem('Griptape')
437-
except Exception:
438-
pass
439449
griptape_nodes = nodes_toolbar.addMenu('Griptape')
440450
451+
# Remove only the entries added by a previous refresh so repeated calls
452+
# don't accumulate duplicates and deleted gizmos disappear from the menu.
453+
global _GRIPTAPE_MENU_ITEMS
454+
for _item_name in _GRIPTAPE_MENU_ITEMS:
455+
try:
456+
griptape_nodes.removeItem(_item_name)
457+
except Exception:
458+
pass
459+
_GRIPTAPE_MENU_ITEMS = []
460+
441461
for stem in sorted(workflows):
442462
label = stem.replace('_', ' ').title()
443463
versions = workflows[stem]
@@ -450,6 +470,7 @@ def _refresh_griptape_menu():
450470
workflow_submenu = griptape_nodes.addMenu(label)
451471
for ver, node_name in versions:
452472
workflow_submenu.addCommand('v{}'.format(ver), "nuke.createNode('{}')".format(node_name))
473+
_GRIPTAPE_MENU_ITEMS.append(label)
453474
454475
455476
# "Refresh Griptape Gizmos" lives on the main Nuke menu bar so that clicking

tests/unit/test_nuke_gizmo_publisher_menu.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ def test_menu_code_skips_watcher_when_qt_unavailable() -> None:
6565
assert guard_idx < watcher_idx
6666

6767

68+
def test_menu_code_never_removes_entire_griptape_menu() -> None:
69+
"""Wiping Nodes > Griptape deletes third-party items (e.g. Nuke's built-in
70+
Griptape workflow node) — refresh must only touch entries it added (issue #69)."""
71+
code = _extract_menu_code()
72+
assert "nodes_toolbar.removeItem('Griptape')" not in code
73+
assert 'nodes_toolbar.removeItem("Griptape")' not in code
74+
75+
76+
def test_menu_code_tracks_and_removes_only_own_menu_items() -> None:
77+
"""Refresh must record added labels and remove only tracked items before re-populating."""
78+
code = _extract_menu_code()
79+
assert "_GRIPTAPE_MENU_ITEMS = []" in code
80+
assert "griptape_nodes.removeItem(" in code
81+
assert "_GRIPTAPE_MENU_ITEMS.append(" in code
82+
# Tracked-item removal must precede re-population.
83+
assert code.index("griptape_nodes.removeItem(") < code.index("griptape_nodes.addCommand(")
84+
85+
86+
def test_menu_code_reuses_existing_griptape_menu() -> None:
87+
"""addMenu returns the existing menu when present — get-or-create, never recreate."""
88+
code = _extract_menu_code()
89+
assert "nodes_toolbar.addMenu('Griptape')" in code
90+
# addMenu (get-or-create) must come before any removeItem on the submenu.
91+
assert code.index("nodes_toolbar.addMenu('Griptape')") < code.index("griptape_nodes.removeItem(")
92+
93+
6894
def test_menu_code_warns_on_remote_mount() -> None:
6995
"""Must include remote-mount heuristic and print warning inside the Qt guard block.
7096

0 commit comments

Comments
 (0)