From c642ed57032ddd916b6341eea10e0779025efcc0 Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:26:05 +0100 Subject: [PATCH] Complete context menu migration for Load3D, Preview3D, and SaveGLB (#6454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request refactors how export menu options are added to 3D-related nodes, updating the API to use a new `getNodeMenuItems` hook and simplifying the menu item creation logic. The changes improve consistency and maintainability across extensions handling 3D nodes, and clarify the expected return types for menu item hooks. **Refactoring and API updates for node menu items:** * Replaced usage of the legacy `createExportMenuOptions` function with the new `createExportMenuItems` function in `load3d.ts`, `saveMesh.ts`, and related imports, aligning all 3D node extensions to the new API. [[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L6-R12) [[2]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43L4-R7) * Introduced and implemented the `getNodeMenuItems` hook in the extension registration for `Load3D`, `Preview3D`, and `SaveGLB` nodes, ensuring export menu items are only shown for the appropriate node types. [[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3R293-R302) [[2]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3R521-R530) [[3]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43R48-R57) * Removed assignment of `getExtraMenuOptions` to nodes, fully migrating to the new menu item hook approach for context menus. [[1]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L301-L302) [[2]](diffhunk://#diff-a5c612d9322ca4cbbeda097d13e2fa1ef017d4c3076d23fc228afee5a79a56e3L548-L549) [[3]](diffhunk://#diff-7dede72060130d77ce5191fc86d115bd9b93311cb0438400730d8e20b2aa8e43L64-L67) **Menu item creation logic simplification:** * Refactored `createExportMenuOptions` to `createExportMenuItems` in `exportMenuHelper.ts`, changing it to directly return an array of menu items (with separators) instead of a function, simplifying its usage and reducing boilerplate. [[1]](diffhunk://#diff-42404da1a87a52d304371a13d5f021bdad837765b94d86d186abb2d99a8cb707L14-R22) [[2]](diffhunk://#diff-42404da1a87a52d304371a13d5f021bdad837765b94d86d186abb2d99a8cb707L59-R58) **Type and documentation improvements:** * Updated the `ComfyExtension` interface in `comfy.ts` to clarify that menu item hooks (`getCanvasMenuItems`, `getNodeMenuItems`) now return arrays that may include `null` values as separators, improving type safety and documentation. See before and after below Screenshot 2025-10-30 at 07 08 04 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6454-Complete-context-menu-migration-for-Load3D-Preview3D-and-SaveGLB-29c6d73d3650819995b4c4dc1582cd86) by [Unito](https://www.unito.io) --- src/extensions/core/load3d.ts | 28 +++++++++++++++---- .../core/load3d/exportMenuHelper.ts | 25 ++++++++--------- src/extensions/core/saveMesh.ts | 18 ++++++++---- src/types/comfy.ts | 8 +++--- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index 5bc4cdeee..7cbc489d3 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -3,11 +3,13 @@ import { nextTick } from 'vue' import Load3D from '@/components/load3d/Load3D.vue' import Load3DAnimation from '@/components/load3d/Load3DAnimation.vue' import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue' -import { createExportMenuOptions } from '@/extensions/core/load3d/exportMenuHelper' +import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import { t } from '@/i18n' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import type { IStringWidget } from '@/lib/litegraph/src/types/widgets' import { useToastStore } from '@/platform/updates/common/toastStore' import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' @@ -288,6 +290,16 @@ useExtensionService().registerExtension({ } }, + getNodeMenuItems(node: LGraphNode): (IContextMenuValue | null)[] { + // Only show menu items for Load3D nodes + if (node.constructor.comfyClass !== 'Load3D') return [] + + const load3d = useLoad3dService().getLoad3d(node) + if (!load3d) return [] + + return createExportMenuItems(load3d) + }, + async nodeCreated(node) { if (node.constructor.comfyClass !== 'Load3D') return @@ -298,8 +310,6 @@ useExtensionService().registerExtension({ await nextTick() useLoad3dService().waitForLoad3d(node, (load3d) => { - node.getExtraMenuOptions = createExportMenuOptions(load3d) - let cameraState = node.properties['Camera Info'] const config = new Load3DConfiguration(load3d) @@ -508,6 +518,16 @@ useExtensionService().registerExtension({ } }, + getNodeMenuItems(node: LGraphNode): (IContextMenuValue | null)[] { + // Only show menu items for Preview3D nodes + if (node.constructor.comfyClass !== 'Preview3D') return [] + + const load3d = useLoad3dService().getLoad3d(node) + if (!load3d) return [] + + return createExportMenuItems(load3d) + }, + getCustomWidgets() { return { PREVIEW_3D(node) { @@ -545,8 +565,6 @@ useExtensionService().registerExtension({ const onExecuted = node.onExecuted useLoad3dService().waitForLoad3d(node, (load3d) => { - node.getExtraMenuOptions = createExportMenuOptions(load3d) - const config = new Load3DConfiguration(load3d) const modelWidget = node.widgets?.find((w) => w.name === 'model_file') diff --git a/src/extensions/core/load3d/exportMenuHelper.ts b/src/extensions/core/load3d/exportMenuHelper.ts index a47ec5eb5..d87146cc3 100644 --- a/src/extensions/core/load3d/exportMenuHelper.ts +++ b/src/extensions/core/load3d/exportMenuHelper.ts @@ -1,5 +1,4 @@ import { t } from '@/i18n' -import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import { useToastStore } from '@/platform/updates/common/toastStore' import Load3d from '@/extensions/core/load3d/Load3d' @@ -11,17 +10,16 @@ const EXPORT_FORMATS = [ { label: 'STL', value: 'stl' } ] as const -export function createExportMenuOptions( +/** + * Creates export menu items for a 3D node using the new extension API. + * Returns an array of context menu items including a separator and export submenu. + */ +export function createExportMenuItems( load3d: Load3d -): ( - canvas: LGraphCanvas, - options: (IContextMenuValue | null)[] -) => (IContextMenuValue | null)[] { - return function ( - _canvas: LGraphCanvas, - options: (IContextMenuValue | null)[] - ): (IContextMenuValue | null)[] { - options.push(null, { +): (IContextMenuValue | null)[] { + return [ + null, // Separator + { content: 'Save', has_submenu: true, callback: (_value, _options, event, prev_menu) => { @@ -56,7 +54,6 @@ export function createExportMenuOptions( parentMenu: prev_menu }) } - }) - return options - } + } + ] } diff --git a/src/extensions/core/saveMesh.ts b/src/extensions/core/saveMesh.ts index a7ffbac89..6babf9c42 100644 --- a/src/extensions/core/saveMesh.ts +++ b/src/extensions/core/saveMesh.ts @@ -1,8 +1,10 @@ import { nextTick } from 'vue' import Load3D from '@/components/load3d/Load3D.vue' -import { createExportMenuOptions } from '@/extensions/core/load3d/exportMenuHelper' +import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget' import { useExtensionService } from '@/services/extensionService' @@ -43,6 +45,16 @@ useExtensionService().registerExtension({ } }, + getNodeMenuItems(node: LGraphNode): (IContextMenuValue | null)[] { + // Only show menu items for SaveGLB nodes + if (node.constructor.comfyClass !== 'SaveGLB') return [] + + const load3d = useLoad3dService().getLoad3d(node) + if (!load3d) return [] + + return createExportMenuItems(load3d) + }, + async nodeCreated(node) { if (node.constructor.comfyClass !== 'SaveGLB') return @@ -61,10 +73,6 @@ useExtensionService().registerExtension({ const load3d = useLoad3dService().getLoad3d(node) - if (load3d) { - node.getExtraMenuOptions = createExportMenuOptions(load3d) - } - const modelWidget = node.widgets?.find((w) => w.name === 'image') if (load3d && modelWidget) { diff --git a/src/types/comfy.ts b/src/types/comfy.ts index e8652c94f..db0760bbd 100644 --- a/src/types/comfy.ts +++ b/src/types/comfy.ts @@ -140,16 +140,16 @@ export interface ComfyExtension { /** * Allows the extension to add context menu items to canvas right-click menus * @param canvas The canvas instance - * @returns An array of context menu items to add + * @returns An array of context menu items to add (null values represent separators) */ - getCanvasMenuItems?(canvas: LGraphCanvas): IContextMenuValue[] + getCanvasMenuItems?(canvas: LGraphCanvas): (IContextMenuValue | null)[] /** * Allows the extension to add context menu items to node right-click menus * @param node The node being right-clicked - * @returns An array of context menu items to add + * @returns An array of context menu items to add (null values represent separators) */ - getNodeMenuItems?(node: LGraphNode): IContextMenuValue[] + getNodeMenuItems?(node: LGraphNode): (IContextMenuValue | null)[] /** * Allows the extension to add additional handling to the node before it is registered with **LGraph**