import _ from 'es-toolkit/compat' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { app } from '@/scripts/app' import { ComfyApp } from '@/scripts/app' import { useMaskEditorStore } from '@/stores/maskEditorStore' import { useDialogStore } from '@/stores/dialogStore' import { MaskEditorDialogOld } from './maskEditorOld' import { ClipspaceDialog } from './clipspace' import { useMaskEditor } from '@/composables/maskeditor/useMaskEditor' function openMaskEditor(node: LGraphNode): void { if (!node) { console.error('[MaskEditor] No node provided') return } if (!node.imgs?.length && node.previewMediaType !== 'image') { console.error('[MaskEditor] Node has no images') return } const useNewEditor = app.extensionManager.setting.get( 'Comfy.MaskEditor.UseNewEditor' ) if (useNewEditor) { useMaskEditor().openMaskEditor(node) } else { // Use old editor ComfyApp.copyToClipspace(node) // @ts-expect-error clipspace_return_node is an extension property added at runtime ComfyApp.clipspace_return_node = node const dlg = MaskEditorDialogOld.getInstance() as any if (dlg?.isOpened && !dlg.isOpened()) { dlg.show() } } } // Check if the dialog is already opened function isOpened(): boolean { const useNewEditor = app.extensionManager.setting.get( 'Comfy.MaskEditor.UseNewEditor' ) if (useNewEditor) { return useDialogStore().isDialogOpen('global-mask-editor') } else { return (MaskEditorDialogOld.instance as any)?.isOpened?.() ?? false } } app.registerExtension({ name: 'Comfy.MaskEditor', settings: [ { id: 'Comfy.MaskEditor.UseNewEditor', category: ['Mask Editor', 'NewEditor'], name: 'Use new mask editor', tooltip: 'Switch to the new mask editor interface', type: 'boolean', defaultValue: true, experimental: true }, { id: 'Comfy.MaskEditor.BrushAdjustmentSpeed', category: ['Mask Editor', 'BrushAdjustment', 'Sensitivity'], name: 'Brush adjustment speed multiplier', tooltip: 'Controls how quickly the brush size and hardness change when adjusting. Higher values mean faster changes.', experimental: true, type: 'slider', attrs: { min: 0.1, max: 2.0, step: 0.1 }, defaultValue: 1.0, versionAdded: '1.0.0' }, { id: 'Comfy.MaskEditor.UseDominantAxis', category: ['Mask Editor', 'BrushAdjustment', 'UseDominantAxis'], name: 'Lock brush adjustment to dominant axis', tooltip: 'When enabled, brush adjustments will only affect size OR hardness based on which direction you move more', type: 'boolean', defaultValue: true, experimental: true } ], commands: [ { id: 'Comfy.MaskEditor.OpenMaskEditor', icon: 'pi pi-pencil', label: 'Open Mask Editor for Selected Node', function: () => { const selectedNodes = app.canvas.selected_nodes if (!selectedNodes || Object.keys(selectedNodes).length !== 1) return const selectedNode = selectedNodes[Object.keys(selectedNodes)[0]] openMaskEditor(selectedNode) } }, { id: 'Comfy.MaskEditor.BrushSize.Increase', icon: 'pi pi-plus-circle', label: 'Increase Brush Size in MaskEditor', function: () => changeBrushSize((old) => _.clamp(old + 4, 1, 100)) }, { id: 'Comfy.MaskEditor.BrushSize.Decrease', icon: 'pi pi-minus-circle', label: 'Decrease Brush Size in MaskEditor', function: () => changeBrushSize((old) => _.clamp(old - 4, 1, 100)) } ], init() { // Support for old editor clipspace integration const openMaskEditorFromClipspace = () => { const useNewEditor = app.extensionManager.setting.get( 'Comfy.MaskEditor.UseNewEditor' ) if (!useNewEditor) { const dlg = MaskEditorDialogOld.getInstance() as any if (dlg?.isOpened && !dlg.isOpened()) { dlg.show() } } } const context_predicate = (): boolean => { return !!( ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0 ) } ClipspaceDialog.registerButton( 'MaskEditor', context_predicate, openMaskEditorFromClipspace ) } }) const changeBrushSize = async (sizeChanger: (oldSize: number) => number) => { if (!isOpened()) return const store = useMaskEditorStore() const oldBrushSize = store.brushSettings.size const newBrushSize = sizeChanger(oldBrushSize) store.setBrushSize(newBrushSize) }