mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Compare commits
11 Commits
pysssss/ap
...
rizumu/ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6c5a72d6 | ||
|
|
f1e3b80b43 | ||
|
|
650603dc0c | ||
|
|
1fd94cbe4d | ||
|
|
ec04d5ee70 | ||
|
|
c985fff434 | ||
|
|
6001600700 | ||
|
|
40dd82ca41 | ||
|
|
f1cb6dad20 | ||
|
|
f1856b7a17 | ||
|
|
bb2c99749a |
@@ -9,7 +9,10 @@ const config: KnipConfig = {
|
||||
'src/main.ts',
|
||||
'src/scripts/ui/menu/index.ts',
|
||||
'src/types/index.ts',
|
||||
'src/storybook/mocks/**/*.ts'
|
||||
'src/storybook/mocks/**/*.ts',
|
||||
// Discovered at runtime via import.meta.glob in src/extensions/core/index.ts
|
||||
'src/extensions/core/extensions/*/index.ts',
|
||||
'src/extensions/core/extensions/*/comfy.ext.config.ts'
|
||||
],
|
||||
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}', '!.claude/**']
|
||||
},
|
||||
@@ -61,6 +64,16 @@ const config: KnipConfig = {
|
||||
'src/components/sidebar/tabs/nodeLibrary/CustomNodesPanel.vue',
|
||||
// Agent review check config, not part of the build
|
||||
'.agents/checks/eslint.strict.config.js',
|
||||
// Pending migration to extensions/*/index.ts glob pattern
|
||||
'src/extensions/core/customWidgets.ts',
|
||||
'src/extensions/core/imageCompare.ts',
|
||||
'src/extensions/core/imageCrop.ts',
|
||||
'src/extensions/core/nightlyBadges.ts',
|
||||
'src/extensions/core/painter.ts',
|
||||
'src/extensions/core/load3dLazy.ts',
|
||||
// Duplicates of old-path files, pending import path migration
|
||||
'src/extensions/core/extensions/maskeditor/constants.ts',
|
||||
'src/extensions/core/extensions/maskeditor/types.ts',
|
||||
// Loaded via @plugin directive in CSS, not detected by knip
|
||||
'packages/design-system/src/css/lucideStrokePlugin.js'
|
||||
],
|
||||
|
||||
@@ -98,7 +98,7 @@ import type {
|
||||
LightConfig,
|
||||
ModelConfig,
|
||||
SceneConfig
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { computed } from 'vue'
|
||||
|
||||
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { CameraType } from '@/extensions/core/load3d/interfaces'
|
||||
import type { CameraType } from '@/extensions/core/extensions/load3d/interfaces'
|
||||
|
||||
const cameraType = defineModel<CameraType>('cameraType')
|
||||
const fov = defineModel<number>('fov')
|
||||
|
||||
@@ -36,7 +36,7 @@ import Slider from 'primevue/slider'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { MaterialMode } from '@/extensions/core/load3d/interfaces'
|
||||
import type { MaterialMode } from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
|
||||
const lightIntensity = defineModel<number>('lightIntensity')
|
||||
|
||||
@@ -100,7 +100,7 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
import type {
|
||||
MaterialMode,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -109,7 +109,7 @@ import { computed, ref } from 'vue'
|
||||
|
||||
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { BackgroundRenderModeType } from '@/extensions/core/load3d/interfaces'
|
||||
import type { BackgroundRenderModeType } from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -32,7 +32,7 @@ import Slider from 'primevue/slider'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { CameraType } from '@/extensions/core/load3d/interfaces'
|
||||
import type { CameraType } from '@/extensions/core/extensions/load3d/interfaces'
|
||||
|
||||
const { t } = useI18n()
|
||||
const cameras = [
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import type {
|
||||
MaterialMode,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { hideMaterialMode = false, isPlyModel = false } = defineProps<{
|
||||
|
||||
@@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick, ref, shallowRef } from 'vue'
|
||||
|
||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import Load3d from '@/extensions/core/extensions/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/extensions/load3d/Load3dUtils'
|
||||
import type { Size } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
@@ -15,11 +15,11 @@ import {
|
||||
createMockLGraphNode
|
||||
} from '@/utils/__tests__/litegraphTestUtils'
|
||||
|
||||
vi.mock('@/extensions/core/load3d/Load3d', () => ({
|
||||
vi.mock('@/extensions/core/extensions/load3d/Load3d', () => ({
|
||||
default: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/extensions/core/load3d/Load3dUtils', () => ({
|
||||
vi.mock('@/extensions/core/extensions/load3d/Load3dUtils', () => ({
|
||||
default: {
|
||||
splitFilePath: vi.fn(),
|
||||
getResourceURL: vi.fn(),
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { MaybeRef } from 'vue'
|
||||
import { toRef } from '@vueuse/core'
|
||||
import { nextTick, ref, toRaw, watch } from 'vue'
|
||||
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import Load3d from '@/extensions/core/extensions/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/extensions/load3d/Load3dUtils'
|
||||
import {
|
||||
isAssetPreviewSupported,
|
||||
persistThumbnail
|
||||
@@ -20,7 +20,7 @@ import type {
|
||||
ModelConfig,
|
||||
SceneConfig,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||
import { SUPPORTED_EXTENSIONS } from '@/extensions/core/load3d/constants'
|
||||
import { SUPPORTED_EXTENSIONS } from '@/extensions/core/extensions/load3d/constants'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import { createMockFileList } from '@/utils/__tests__/litegraphTestUtils'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref, toValue } from 'vue'
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
|
||||
import { SUPPORTED_EXTENSIONS } from '@/extensions/core/load3d/constants'
|
||||
import { SUPPORTED_EXTENSIONS } from '@/extensions/core/extensions/load3d/constants'
|
||||
import { t } from '@/i18n'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import Load3d from '@/extensions/core/extensions/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/extensions/load3d/Load3dUtils'
|
||||
import type { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
@@ -18,7 +18,7 @@ vi.mock('@/platform/updates/common/toastStore', () => ({
|
||||
useToastStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/extensions/core/load3d/Load3dUtils', () => ({
|
||||
vi.mock('@/extensions/core/extensions/load3d/Load3dUtils', () => ({
|
||||
default: {
|
||||
uploadFile: vi.fn()
|
||||
}
|
||||
@@ -28,7 +28,7 @@ vi.mock('@/i18n', () => ({
|
||||
t: vi.fn((key) => key)
|
||||
}))
|
||||
|
||||
vi.mock('@/extensions/core/load3d/Load3d', () => ({
|
||||
vi.mock('@/extensions/core/extensions/load3d/Load3d', () => ({
|
||||
default: vi.fn()
|
||||
}))
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ref, toRaw, watch } from 'vue'
|
||||
import QuickLRU from '@alloc/quick-lru'
|
||||
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import Load3d from '@/extensions/core/extensions/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/extensions/load3d/Load3dUtils'
|
||||
import type {
|
||||
AnimationItem,
|
||||
BackgroundRenderModeType,
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
ModelConfig,
|
||||
SceneConfig,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyApp } from '../../scripts/app'
|
||||
import { $el, ComfyDialog } from '../../scripts/ui'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyApp } from '@/scripts/app'
|
||||
import { $el, ComfyDialog } from '@/scripts/ui'
|
||||
|
||||
export class ClipspaceDialog extends ComfyDialog {
|
||||
static items: Array<
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Cloud.Badges',
|
||||
activationEvents: ['*'],
|
||||
comfyCloud: true
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Cloud.FeedbackButton',
|
||||
activationEvents: ['*'],
|
||||
comfyCloud: true
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Cloud.RemoteConfig',
|
||||
activationEvents: ['*'],
|
||||
comfyCloud: true
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Cloud.SessionCookie',
|
||||
activationEvents: ['*'],
|
||||
comfyCloud: true
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Cloud.Subscription',
|
||||
activationEvents: ['*'],
|
||||
comfyCloud: {
|
||||
subscriptionRequired: true
|
||||
}
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
isComboWidget
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Adds filtering to combo context menus
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Allows you to edit the attention weight by holding ctrl (or cmd) and using the up/down arrow keys
|
||||
|
||||
@@ -27,10 +27,10 @@ import { ExecutableGroupNodeChildDTO } from '@/utils/executableGroupNodeChildDTO
|
||||
import { GROUP } from '@/utils/executableGroupNodeDto'
|
||||
import { deserialiseAndCreate, serialise } from '@/utils/vintageClipboard'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { ManageGroupDialog } from './groupNodeManage'
|
||||
import { mergeIfValid } from './widgetInputs'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ManageGroupDialog } from '../groupNodeManage'
|
||||
import { mergeIfValid } from '../widgetInputs'
|
||||
|
||||
type GroupNodeLink = SerialisedLLinkArray
|
||||
type LinksFromMap = Record<number, Record<number, GroupNodeLink[]>>
|
||||
@@ -8,11 +8,11 @@ import type {
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { type ComfyApp, app } from '../../scripts/app'
|
||||
import { $el } from '../../scripts/ui'
|
||||
import { ComfyDialog } from '../../scripts/ui/dialog'
|
||||
import { DraggableList } from '../../scripts/ui/draggableList'
|
||||
import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
||||
import { type ComfyApp, app } from '@/scripts/app'
|
||||
import { $el } from '@/scripts/ui'
|
||||
import { ComfyDialog } from '@/scripts/ui/dialog'
|
||||
import { DraggableList } from '@/scripts/ui/draggableList'
|
||||
import { GroupNodeConfig, GroupNodeHandler } from '../groupNode'
|
||||
import './groupNodeManage.css'
|
||||
|
||||
const ORDER: symbol = Symbol()
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
function setNodeMode(node: LGraphNode, mode: number) {
|
||||
node.mode = mode
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
AnimationItem,
|
||||
AnimationManagerInterface,
|
||||
EventManagerInterface
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from './interfaces'
|
||||
|
||||
export class AnimationManager implements AnimationManagerInterface {
|
||||
currentAnimation: THREE.AnimationMixer | null = null
|
||||
@@ -1,12 +1,12 @@
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import Load3d from './Load3d'
|
||||
import Load3dUtils from './Load3dUtils'
|
||||
import type {
|
||||
CameraConfig,
|
||||
CameraState,
|
||||
LightConfig,
|
||||
ModelConfig,
|
||||
SceneConfig
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from './interfaces'
|
||||
import type { Dictionary } from '@/lib/litegraph/src/interfaces'
|
||||
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
126
src/extensions/core/extensions/load3d/comfy.ext.config.ts
Normal file
126
src/extensions/core/extensions/load3d/comfy.ext.config.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.Load3D',
|
||||
activationEvents: [
|
||||
'onWidgets:contributes',
|
||||
'onCommands:contributes',
|
||||
'onSettings:contributes'
|
||||
],
|
||||
contributes: [
|
||||
{
|
||||
name: 'Comfy.Preview3D',
|
||||
widgets: ['PREVIEW_3D']
|
||||
},
|
||||
{
|
||||
name: 'Comfy.Load3D',
|
||||
widgets: ['LOAD_3D'],
|
||||
settings: [
|
||||
{
|
||||
id: 'Comfy.Load3D.ShowGrid',
|
||||
category: ['3D', 'Scene', 'Initial Grid Visibility'],
|
||||
name: 'Initial Grid Visibility',
|
||||
tooltip:
|
||||
'Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.BackgroundColor',
|
||||
category: ['3D', 'Scene', 'Initial Background Color'],
|
||||
name: 'Initial Background Color',
|
||||
tooltip:
|
||||
'Controls the default background color of the 3D scene. This setting determines the background appearance when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
|
||||
type: 'color',
|
||||
defaultValue: '282828',
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.CameraType',
|
||||
category: ['3D', 'Camera', 'Initial Camera Type'],
|
||||
name: 'Initial Camera Type',
|
||||
tooltip:
|
||||
'Controls whether the camera is perspective or orthographic by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
|
||||
type: 'combo',
|
||||
options: ['perspective', 'orthographic'],
|
||||
defaultValue: 'perspective',
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensity',
|
||||
category: ['3D', 'Light', 'Initial Light Intensity'],
|
||||
name: 'Initial Light Intensity',
|
||||
tooltip:
|
||||
'Sets the default brightness level of lighting in the 3D scene. This value determines how intensely lights illuminate objects when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
|
||||
type: 'number',
|
||||
defaultValue: 3,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensityMaximum',
|
||||
category: ['3D', 'Light', 'Light Intensity Maximum'],
|
||||
name: 'Light Intensity Maximum',
|
||||
tooltip:
|
||||
'Sets the maximum allowable light intensity value for 3D scenes. This defines the upper brightness limit that can be set when adjusting lighting in any 3D widget.',
|
||||
type: 'number',
|
||||
defaultValue: 10,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensityMinimum',
|
||||
category: ['3D', 'Light', 'Light Intensity Minimum'],
|
||||
name: 'Light Intensity Minimum',
|
||||
tooltip:
|
||||
'Sets the minimum allowable light intensity value for 3D scenes. This defines the lower brightness limit that can be set when adjusting lighting in any 3D widget.',
|
||||
type: 'number',
|
||||
defaultValue: 1,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightAdjustmentIncrement',
|
||||
category: ['3D', 'Light', 'Light Adjustment Increment'],
|
||||
name: 'Light Adjustment Increment',
|
||||
tooltip:
|
||||
'Controls the increment size when adjusting light intensity in 3D scenes. A smaller step value allows for finer control over lighting adjustments, while a larger value results in more noticeable changes per adjustment.',
|
||||
type: 'slider',
|
||||
attrs: {
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.1
|
||||
},
|
||||
defaultValue: 0.5,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.3DViewerEnable',
|
||||
category: ['3D', '3DViewer', 'Enable'],
|
||||
name: 'Enable 3D Viewer (Beta)',
|
||||
tooltip:
|
||||
'Enables the 3D Viewer (Beta) for selected nodes. This feature allows you to visualize and interact with 3D models directly within the full size 3d viewer.',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.PLYEngine',
|
||||
category: ['3D', 'PLY', 'PLY Engine'],
|
||||
name: 'PLY Engine',
|
||||
tooltip:
|
||||
'Select the engine for loading PLY files. "threejs" uses the native Three.js PLYLoader (best for mesh PLY files). "fastply" uses an optimized loader for ASCII point cloud PLY files. "sparkjs" uses Spark.js for 3D Gaussian Splatting PLY files.',
|
||||
type: 'combo',
|
||||
options: ['threejs', 'fastply', 'sparkjs'],
|
||||
defaultValue: 'threejs',
|
||||
experimental: true
|
||||
}
|
||||
],
|
||||
commands: [
|
||||
{
|
||||
id: 'Comfy.3DViewer.Open3DViewer',
|
||||
icon: 'pi pi-pencil',
|
||||
label: 'Open 3D Viewer (Beta) for Selected Node'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@/i18n'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3d from './Load3d'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
const EXPORT_FORMATS = [
|
||||
@@ -3,14 +3,11 @@ import { nextTick } from 'vue'
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue'
|
||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
||||
import type {
|
||||
CameraConfig,
|
||||
CameraState
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||
import { SUPPORTED_EXTENSIONS_ACCEPT } from '@/extensions/core/load3d/constants'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
import { SUPPORTED_EXTENSIONS_ACCEPT } from './constants'
|
||||
import { createExportMenuItems } from './exportMenuHelper'
|
||||
import type { CameraConfig, CameraState } from './interfaces'
|
||||
import Load3DConfiguration from './Load3DConfiguration'
|
||||
import Load3dUtils from './Load3dUtils'
|
||||
import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
@@ -123,105 +120,6 @@ function createFileInput(
|
||||
|
||||
useExtensionService().registerExtension({
|
||||
name: 'Comfy.Load3D',
|
||||
settings: [
|
||||
{
|
||||
id: 'Comfy.Load3D.ShowGrid',
|
||||
category: ['3D', 'Scene', 'Initial Grid Visibility'],
|
||||
name: 'Initial Grid Visibility',
|
||||
tooltip:
|
||||
'Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.BackgroundColor',
|
||||
category: ['3D', 'Scene', 'Initial Background Color'],
|
||||
name: 'Initial Background Color',
|
||||
tooltip:
|
||||
'Controls the default background color of the 3D scene. This setting determines the background appearance when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
|
||||
type: 'color',
|
||||
defaultValue: '282828',
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.CameraType',
|
||||
category: ['3D', 'Camera', 'Initial Camera Type'],
|
||||
name: 'Initial Camera Type',
|
||||
tooltip:
|
||||
'Controls whether the camera is perspective or orthographic by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.',
|
||||
type: 'combo',
|
||||
options: ['perspective', 'orthographic'],
|
||||
defaultValue: 'perspective',
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensity',
|
||||
category: ['3D', 'Light', 'Initial Light Intensity'],
|
||||
name: 'Initial Light Intensity',
|
||||
tooltip:
|
||||
'Sets the default brightness level of lighting in the 3D scene. This value determines how intensely lights illuminate objects when a new 3D widget is created, but can be adjusted individually for each widget after creation.',
|
||||
type: 'number',
|
||||
defaultValue: 3,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensityMaximum',
|
||||
category: ['3D', 'Light', 'Light Intensity Maximum'],
|
||||
name: 'Light Intensity Maximum',
|
||||
tooltip:
|
||||
'Sets the maximum allowable light intensity value for 3D scenes. This defines the upper brightness limit that can be set when adjusting lighting in any 3D widget.',
|
||||
type: 'number',
|
||||
defaultValue: 10,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightIntensityMinimum',
|
||||
category: ['3D', 'Light', 'Light Intensity Minimum'],
|
||||
name: 'Light Intensity Minimum',
|
||||
tooltip:
|
||||
'Sets the minimum allowable light intensity value for 3D scenes. This defines the lower brightness limit that can be set when adjusting lighting in any 3D widget.',
|
||||
type: 'number',
|
||||
defaultValue: 1,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.LightAdjustmentIncrement',
|
||||
category: ['3D', 'Light', 'Light Adjustment Increment'],
|
||||
name: 'Light Adjustment Increment',
|
||||
tooltip:
|
||||
'Controls the increment size when adjusting light intensity in 3D scenes. A smaller step value allows for finer control over lighting adjustments, while a larger value results in more noticeable changes per adjustment.',
|
||||
type: 'slider',
|
||||
attrs: {
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.1
|
||||
},
|
||||
defaultValue: 0.5,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.3DViewerEnable',
|
||||
category: ['3D', '3DViewer', 'Enable'],
|
||||
name: 'Enable 3D Viewer (Beta)',
|
||||
tooltip:
|
||||
'Enables the 3D Viewer (Beta) for selected nodes. This feature allows you to visualize and interact with 3D models directly within the full size 3d viewer.',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
experimental: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Load3D.PLYEngine',
|
||||
category: ['3D', 'PLY', 'PLY Engine'],
|
||||
name: 'PLY Engine',
|
||||
tooltip:
|
||||
'Select the engine for loading PLY files. "threejs" uses the native Three.js PLYLoader (best for mesh PLY files). "fastply" uses an optimized loader for ASCII point cloud PLY files. "sparkjs" uses Spark.js for 3D Gaussian Splatting PLY files.',
|
||||
type: 'combo',
|
||||
options: ['threejs', 'fastply', 'sparkjs'],
|
||||
defaultValue: 'threejs',
|
||||
experimental: true
|
||||
}
|
||||
],
|
||||
commands: [
|
||||
{
|
||||
id: 'Comfy.3DViewer.Open3DViewer',
|
||||
36
src/extensions/core/extensions/maskeditor/constants.ts
Normal file
36
src/extensions/core/extensions/maskeditor/constants.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Tools } from './types'
|
||||
|
||||
export const iconsHtml: Record<Tools, string> = {
|
||||
[Tools.MaskPen]: `
|
||||
<svg viewBox="0 0 44 44">
|
||||
<path class="cls-1" d="M10.97,15.98v14.04c0,.825.675,1.5,1.5,1.5h23.07c.825,0,1.5-.675,1.5-1.5V15.98c0-.825-.675-1.5-1.5-1.5H12.47c-.825,0-1.5.675-1.5,1.5ZM25.79,28.16c-4.365,1.41-8.355-2.58-6.945-6.945.51-1.575,1.785-2.85,3.36-3.36,4.365-1.41,8.355,2.58,6.945,6.945-.51,1.575-1.785,2.85-3.36,3.36Z"/>
|
||||
</svg>
|
||||
`,
|
||||
[Tools.Eraser]: `
|
||||
<svg viewBox="0 0 44 44">
|
||||
<g>
|
||||
<rect class="cls-2" x="16.68" y="10" width="10.63" height="24" rx="1.16" ry="1.16" transform="translate(22 -9.11) rotate(45)"/>
|
||||
<path class="cls-1" d="M17.27,34.27c-.42,0-.85-.16-1.17-.48l-5.88-5.88c-.31-.31-.48-.73-.48-1.17s.17-.86.48-1.17l15.34-15.34c.62-.62,1.72-.62,2.34,0l5.88,5.88c.65.65.65,1.7,0,2.34l-15.34,15.34c-.32.32-.75.48-1.17.48ZM26.73,10.73c-.18,0-.34.07-.46.19l-15.34,15.34c-.12.12-.19.29-.19.46s.07.34.19.46l5.88,5.88c.26.26.67.26.93,0l15.34-15.34c.26-.26.26-.67,0-.93l-5.88-5.88c-.12-.12-.29-.19-.46-.19Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M20.33,11.03h8.32c.64,0,1.16.52,1.16,1.16v15.79h-10.63v-15.79c0-.64.52-1.16,1.16-1.16Z" transform="translate(20.97 -11.61) rotate(45)"/>
|
||||
</svg>
|
||||
`,
|
||||
[Tools.MaskBucket]: `
|
||||
<svg viewBox="0 0 44 44">
|
||||
<path class="cls-1" d="M33.4,21.76l-11.42,11.41-.04.05c-.61.61-1.6.61-2.21,0l-8.91-8.91c-.61-.61-.61-1.6,0-2.21l.04-.05.3-.29h22.24Z"/>
|
||||
<path class="cls-1" d="M20.83,34.17c-.55,0-1.07-.21-1.46-.6l-8.91-8.91c-.8-.8-.8-2.11,0-2.92l11.31-11.31c.8-.8,2.11-.8,2.92,0l8.91,8.91c.39.39.6.91.6,1.46s-.21,1.07-.6,1.46l-11.31,11.31c-.39.39-.91.6-1.46.6ZM23.24,10.83c-.27,0-.54.1-.75.31l-11.31,11.31c-.41.41-.41,1.09,0,1.5l8.91,8.91c.4.4,1.1.4,1.5,0l11.31-11.31c.2-.2.31-.47.31-.75s-.11-.55-.31-.75l-8.91-8.91c-.21-.21-.48-.31-.75-.31Z"/>
|
||||
<path class="cls-1" d="M34.28,26.85c0,.84-.68,1.52-1.52,1.52s-1.52-.68-1.52-1.52,1.52-2.86,1.52-2.86c0,0,1.52,2.02,1.52,2.86Z"/>
|
||||
</svg>
|
||||
`,
|
||||
[Tools.MaskColorFill]: `
|
||||
<svg viewBox="0 0 44 44">
|
||||
<path class="cls-1" d="M30.29,13.72c-1.09-1.1-2.85-1.09-3.94,0l-2.88,2.88-.75-.75c-.2-.19-.51-.19-.71,0-.19.2-.19.51,0,.71l1.4,1.4-9.59,9.59c-.35.36-.54.82-.54,1.32,0,.14,0,.28.05.41-.05.04-.1.08-.15.13-.39.39-.39,1.01,0,1.4.38.39,1.01.39,1.4,0,.04-.04.08-.09.11-.13.14.04.3.06.45.06.5,0,.97-.19,1.32-.55l9.59-9.59,1.38,1.38c.1.09.22.14.35.14s.26-.05.35-.14c.2-.2.2-.52,0-.71l-.71-.72,2.88-2.89c1.08-1.08,1.08-2.85-.01-3.94ZM19.43,25.82h-2.46l7.15-7.15,1.23,1.23-5.92,5.92Z"/>
|
||||
</svg>
|
||||
`,
|
||||
[Tools.PaintPen]: `
|
||||
<svg viewBox="0 0 44 44">
|
||||
<path class="cls-1" d="M34,13.93c0,.47-.19.94-.55,1.31l-13.02,13.04c-.09.07-.18.15-.27.22-.07-1.39-1.21-2.48-2.61-2.49.07-.12.16-.24.27-.34l13.04-13.04c.72-.72,1.89-.72,2.6,0,.35.35.55.83.55,1.3Z"/>
|
||||
<path class="cls-1" d="M19.64,29.03c0,4.46-6.46,3.18-9.64,0,3.3-.47,4.75-2.58,7.06-2.58,1.43,0,2.58,1.16,2.58,2.58Z"/>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
65
src/extensions/core/extensions/maskeditor/types.ts
Normal file
65
src/extensions/core/extensions/maskeditor/types.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export enum BrushShape {
|
||||
Arc = 'arc',
|
||||
Rect = 'rect'
|
||||
}
|
||||
|
||||
export enum Tools {
|
||||
MaskPen = 'pen',
|
||||
PaintPen = 'rgbPaint',
|
||||
Eraser = 'eraser',
|
||||
MaskBucket = 'paintBucket',
|
||||
MaskColorFill = 'colorSelect'
|
||||
}
|
||||
|
||||
export const allTools = [
|
||||
Tools.MaskPen,
|
||||
Tools.PaintPen,
|
||||
Tools.Eraser,
|
||||
Tools.MaskBucket,
|
||||
Tools.MaskColorFill
|
||||
]
|
||||
|
||||
const allImageLayers = ['mask', 'rgb'] as const
|
||||
|
||||
export type ImageLayer = (typeof allImageLayers)[number]
|
||||
|
||||
export interface ToolInternalSettings {
|
||||
container: HTMLElement
|
||||
cursor?: string
|
||||
newActiveLayerOnSet?: ImageLayer
|
||||
}
|
||||
|
||||
export enum CompositionOperation {
|
||||
SourceOver = 'source-over',
|
||||
DestinationOut = 'destination-out'
|
||||
}
|
||||
|
||||
export enum MaskBlendMode {
|
||||
Black = 'black',
|
||||
White = 'white',
|
||||
Negative = 'negative'
|
||||
}
|
||||
|
||||
export enum ColorComparisonMethod {
|
||||
Simple = 'simple',
|
||||
HSL = 'hsl',
|
||||
LAB = 'lab'
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface Offset {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface Brush {
|
||||
type: BrushShape
|
||||
size: number
|
||||
opacity: number
|
||||
hardness: number
|
||||
stepSize: number
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import { useDialogService } from '@/services/dialogService'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { deserialiseAndCreate } from '@/utils/vintageClipboard'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { $el, ComfyDialog } from '../../scripts/ui'
|
||||
import { GroupNodeConfig, GroupNodeHandler } from './groupNode'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { $el, ComfyDialog } from '@/scripts/ui'
|
||||
import { GroupNodeConfig, GroupNodeHandler } from '../groupNode'
|
||||
|
||||
// Adds the ability to save and add multiple nodes as a template
|
||||
// To save:
|
||||
@@ -1,8 +1,8 @@
|
||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyWidgets } from '../../scripts/widgets'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyWidgets } from '@/scripts/widgets'
|
||||
|
||||
// Node that add notes to your project
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { ISlotType } from '@/lib/litegraph/src/interfaces'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs'
|
||||
import { app } from '@/scripts/app'
|
||||
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from '../widgetInputs'
|
||||
|
||||
// Node that allows you to redirect connections for cleaner graphs
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import { applyTextReplacements } from '@/utils/searchAndReplace'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const saveNodeTypes = new Set([
|
||||
'SaveImage',
|
||||
16
src/extensions/core/extensions/saveMesh/comfy.ext.config.ts
Normal file
16
src/extensions/core/extensions/saveMesh/comfy.ext.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineComfyExtConfig } from '@/extensions/utils'
|
||||
|
||||
export default defineComfyExtConfig({
|
||||
name: 'Comfy.SaveGLB',
|
||||
activationEvents: [
|
||||
'onWidgets:contributes',
|
||||
'onCommands:contributes',
|
||||
'onSettings:contributes'
|
||||
],
|
||||
contributes: [
|
||||
{
|
||||
name: 'Comfy.SaveGLB',
|
||||
widgets: ['SAVE_GLB']
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -2,8 +2,8 @@ import { nextTick } from 'vue'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
import { useLoad3d } from '@/composables/useLoad3d'
|
||||
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||
import { createExportMenuItems } from '../load3d/exportMenuHelper'
|
||||
import Load3DConfiguration from '../load3d/Load3DConfiguration'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||
import type { NodeOutputWith, ResultItem } from '@/schemas/apiSchema'
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { ComfyWidgets } from '../../scripts/widgets'
|
||||
import { app } from '@/scripts/app'
|
||||
import { ComfyWidgets } from '@/scripts/widgets'
|
||||
|
||||
// Adds defaults for quickly adding nodes with middle click on the input/output
|
||||
|
||||
@@ -22,8 +22,8 @@ import { useAudioService } from '@/services/audioService'
|
||||
import { type NodeLocatorId } from '@/types'
|
||||
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useWidgetValueStore } from '@/stores/widgetValueStore'
|
||||
|
||||
function updateUIWidget(
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
isMediaUploadComboInput
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
// Adds an upload button to the nodes
|
||||
|
||||
@@ -2,8 +2,8 @@ import { t } from '@/i18n'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { api } from '../../scripts/api'
|
||||
import { app } from '../../scripts/app'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const WEBCAM_READY = Symbol()
|
||||
|
||||
@@ -1,54 +1,15 @@
|
||||
import { isCloud, isNightly } from '@/platform/distribution/types'
|
||||
import { dispatchComfyExtensions } from '../dispatch'
|
||||
import type { ComfyExtensionConfigs, ComfyExtensionEntrance } from '../types'
|
||||
|
||||
import './clipspace'
|
||||
import './contextMenuFilter'
|
||||
import './customWidgets'
|
||||
import './dynamicPrompts'
|
||||
import './editAttention'
|
||||
import './electronAdapter'
|
||||
import './groupNode'
|
||||
import './groupNodeManage'
|
||||
import './groupOptions'
|
||||
import './imageCompare'
|
||||
import './imageCrop'
|
||||
// load3d and saveMesh are loaded on-demand to defer THREE.js (~1.8MB)
|
||||
// The lazy loader triggers loading when a 3D node is used
|
||||
import './load3dLazy'
|
||||
import './maskeditor'
|
||||
if (!isCloud) {
|
||||
await import('./nodeTemplates')
|
||||
}
|
||||
import './noteNode'
|
||||
import './painter'
|
||||
import './previewAny'
|
||||
import './rerouteNode'
|
||||
import './saveImageExtraOutput'
|
||||
// saveMesh is loaded on-demand with load3d (see load3dLazy.ts)
|
||||
import './selectionBorder'
|
||||
import './simpleTouchSupport'
|
||||
import './slotDefaults'
|
||||
import './uploadAudio'
|
||||
import './uploadImage'
|
||||
import './webcamCapture'
|
||||
import './widgetInputs'
|
||||
|
||||
// Cloud-only extensions - tree-shaken in OSS builds
|
||||
if (isCloud) {
|
||||
await import('./cloudRemoteConfig')
|
||||
await import('./cloudBadges')
|
||||
await import('./cloudSessionCookie')
|
||||
|
||||
if (window.__CONFIG__?.subscription_required) {
|
||||
await import('./cloudSubscription')
|
||||
}
|
||||
}
|
||||
|
||||
// Feedback button for cloud and nightly builds
|
||||
if (isCloud || isNightly) {
|
||||
await import('./cloudFeedbackTopbarButton')
|
||||
}
|
||||
|
||||
// Nightly-only extensions
|
||||
if (isNightly && !isCloud) {
|
||||
await import('./nightlyBadges')
|
||||
export async function registerExtensions() {
|
||||
const extConfigs = import.meta.glob(`./extensions/*/comfy.ext.config.ts`, {
|
||||
// Since each config is small, we only import the default export and use eager mode for better tree-shaking and performance.
|
||||
import: 'default',
|
||||
eager: true
|
||||
}) as ComfyExtensionConfigs
|
||||
const extensionEntrance = import.meta.glob(
|
||||
`./extensions/*/index.ts`
|
||||
) as ComfyExtensionEntrance
|
||||
|
||||
dispatchComfyExtensions({ configs: extConfigs, entrance: extensionEntrance })
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ async function loadLoad3dExtensions(): Promise<ComfyExtension[]> {
|
||||
load3dExtensionsLoading = (async () => {
|
||||
const before = new Set(useExtensionStore().enabledExtensions)
|
||||
// Import both extensions - they will self-register via useExtensionService()
|
||||
await Promise.all([import('./load3d'), import('./saveMesh')])
|
||||
await Promise.all([
|
||||
import('./extensions/load3d'),
|
||||
import('./extensions/saveMesh')
|
||||
])
|
||||
load3dExtensionsLoaded = true
|
||||
return useExtensionStore().enabledExtensions.filter(
|
||||
(ext) => !before.has(ext)
|
||||
|
||||
99
src/extensions/dispatch.ts
Normal file
99
src/extensions/dispatch.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import type {
|
||||
ComfyExtensionConfigs,
|
||||
ComfyExtensionEntrance,
|
||||
ComfyExtensionLoadContext,
|
||||
StaticComfyCommand,
|
||||
StaticComfyKeybinding,
|
||||
StaticComfyMenuCommandGroup,
|
||||
StaticComfySettingParams
|
||||
} from './types'
|
||||
import {
|
||||
defineProcessQueue,
|
||||
formatExtensions,
|
||||
normalizationActivationEvents
|
||||
} from './utils'
|
||||
|
||||
const extLoadContext: ComfyExtensionLoadContext = {
|
||||
get isCloud() {
|
||||
return isCloud
|
||||
},
|
||||
get subscriptionRequired() {
|
||||
return !!window.__CONFIG__?.subscription_required
|
||||
}
|
||||
}
|
||||
|
||||
export async function dispatchComfyExtensions(options: {
|
||||
configs: ComfyExtensionConfigs
|
||||
entrance: ComfyExtensionEntrance
|
||||
}) {
|
||||
const { configs, entrance } = options
|
||||
const extensions = formatExtensions(entrance, configs)
|
||||
for (const extension of Object.values(extensions)) {
|
||||
const activationEvents = normalizationActivationEvents(
|
||||
extLoadContext,
|
||||
extension.config
|
||||
)
|
||||
activationEvents.forEach((event) =>
|
||||
onceExtImportEvent(event, async () => void (await extension.entry()))
|
||||
)
|
||||
|
||||
let contributes = extension.config?.contributes
|
||||
if (contributes && !Array.isArray(contributes)) contributes = [contributes]
|
||||
if (contributes && contributes.length) {
|
||||
for (const contribute of contributes) {
|
||||
const { settings, commands, keybindings, menuCommands } = contribute
|
||||
if (settings) pushExtensionSettings(settings)
|
||||
if (commands) pushExtensionCommands(commands)
|
||||
if (keybindings) pushExtensionKeybindings(keybindings)
|
||||
if (menuCommands) pushExtensionMenuCommands(menuCommands)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type EventCallback = (ctx: { event: string }) => void | Promise<void>
|
||||
|
||||
const eventMap = new Map<string, Set<EventCallback>>()
|
||||
|
||||
export async function importExtensionsByEvent(event: string) {
|
||||
const callbacks = eventMap.get(event)
|
||||
if (!callbacks) return
|
||||
eventMap.delete(event)
|
||||
await Promise.all([...callbacks].map((cb) => cb({ event })))
|
||||
}
|
||||
|
||||
export function extensionsImportEventHas(event: string) {
|
||||
return eventMap.has(event)
|
||||
}
|
||||
|
||||
function onceExtImportEvent(event: string, callback: EventCallback) {
|
||||
if (eventMap.has(event)) {
|
||||
eventMap.get(event)!.add(callback)
|
||||
} else {
|
||||
eventMap.set(event, new Set([callback]))
|
||||
}
|
||||
}
|
||||
|
||||
const { process: _processExtensionSettings, push: pushExtensionSettings } =
|
||||
defineProcessQueue<StaticComfySettingParams>()
|
||||
export const processExtensionSettings = _processExtensionSettings
|
||||
|
||||
const { process: _processExtensionCommands, push: pushExtensionCommands } =
|
||||
defineProcessQueue<StaticComfyCommand>()
|
||||
/** @knipIgnoreUsedByStackedPR */
|
||||
export const processExtensionCommands = _processExtensionCommands
|
||||
|
||||
const {
|
||||
process: _processExtensionMenuCommands,
|
||||
push: pushExtensionMenuCommands
|
||||
} = defineProcessQueue<StaticComfyMenuCommandGroup>()
|
||||
/** @knipIgnoreUsedByStackedPR */
|
||||
export const processExtensionMenuCommands = _processExtensionMenuCommands
|
||||
|
||||
const {
|
||||
process: _processExtensionKeybindings,
|
||||
push: pushExtensionKeybindings
|
||||
} = defineProcessQueue<StaticComfyKeybinding>()
|
||||
/** @knipIgnoreUsedByStackedPR */
|
||||
export const processExtensionKeybindings = _processExtensionKeybindings
|
||||
74
src/extensions/types.ts
Normal file
74
src/extensions/types.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ComfyExtension } from '@/types'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
type ExcludeFunctions<T> = T extends Function ? never : T
|
||||
type StaticOnly<T> = {
|
||||
[K in keyof T as ExcludeFunctions<T[K]> extends never
|
||||
? never
|
||||
: K]: ExcludeFunctions<T[K]>
|
||||
}
|
||||
|
||||
export type StaticComfyCommand = StaticOnly<
|
||||
NonNullable<ComfyExtension['commands']>[number]
|
||||
>
|
||||
export type StaticComfySettingParams = StaticOnly<
|
||||
NonNullable<ComfyExtension['settings']>[number]
|
||||
>
|
||||
export type StaticComfyKeybinding = StaticOnly<
|
||||
NonNullable<ComfyExtension['keybindings']>[number]
|
||||
>
|
||||
export type StaticComfyMenuCommandGroup = StaticOnly<
|
||||
NonNullable<ComfyExtension['menuCommands']>[number]
|
||||
>
|
||||
|
||||
type ComfyExtensionActivationEvent =
|
||||
| '*'
|
||||
| 'onWidgets:contributes'
|
||||
| 'onCommands:contributes'
|
||||
| 'onSettings:contributes'
|
||||
|
||||
type ComfyExtensionContributes = {
|
||||
name: string
|
||||
widgets?: string[]
|
||||
commands?: StaticComfyCommand[]
|
||||
menuCommands?: StaticComfyMenuCommandGroup[]
|
||||
settings?: StaticComfySettingParams[]
|
||||
keybindings?: StaticComfyKeybinding[]
|
||||
}
|
||||
|
||||
export interface ComfyExtensionConfig {
|
||||
name?: string
|
||||
|
||||
activationEvents: ComfyExtensionActivationEvent[]
|
||||
|
||||
contributes?: ComfyExtensionContributes | ComfyExtensionContributes[]
|
||||
|
||||
comfyCloud?:
|
||||
| boolean
|
||||
| {
|
||||
subscriptionRequired: boolean
|
||||
}
|
||||
}
|
||||
export type ComfyExtensionConfigs = Record<
|
||||
string,
|
||||
ComfyExtensionConfig | undefined
|
||||
>
|
||||
|
||||
export interface ComfyExtensionLoadContext {
|
||||
readonly isCloud: boolean
|
||||
readonly subscriptionRequired: boolean
|
||||
}
|
||||
|
||||
type ComfyExtensionEntry = () => Promise<Record<string, unknown>>
|
||||
export type ComfyExtensionEntrance = Record<
|
||||
string,
|
||||
ComfyExtensionEntry | undefined
|
||||
>
|
||||
|
||||
interface ComfyExtensionPackage {
|
||||
name?: string
|
||||
path?: string
|
||||
config?: ComfyExtensionConfig
|
||||
entry: ComfyExtensionEntry
|
||||
}
|
||||
export type ComfyExtensionPackages = Record<string, ComfyExtensionPackage>
|
||||
147
src/extensions/utils.ts
Normal file
147
src/extensions/utils.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import type {
|
||||
ComfyExtensionConfig,
|
||||
ComfyExtensionConfigs,
|
||||
ComfyExtensionEntrance,
|
||||
ComfyExtensionLoadContext,
|
||||
ComfyExtensionPackages
|
||||
} from './types'
|
||||
|
||||
export function defineComfyExtConfig(
|
||||
config: ComfyExtensionConfig
|
||||
): ComfyExtensionConfig {
|
||||
return config
|
||||
}
|
||||
|
||||
export function formatExtensions(
|
||||
entrance: ComfyExtensionEntrance,
|
||||
configs: ComfyExtensionConfigs
|
||||
): ComfyExtensionPackages {
|
||||
const pkgs: ComfyExtensionPackages = {}
|
||||
for (const [entryPath, entry] of Object.entries(entrance)) {
|
||||
const pathArr = entryPath.split('/')
|
||||
const name = pathArr.at(-2)!
|
||||
const path = pathArr.slice(0, -1).join('/')
|
||||
if (!name) {
|
||||
console.error(`Extension`, path, `has no name`)
|
||||
continue
|
||||
}
|
||||
if (!entry) {
|
||||
console.error(`Extension`, path, `has no entrance`)
|
||||
continue
|
||||
}
|
||||
|
||||
const config = configs[`${path}/comfy.ext.config.ts`]
|
||||
// if (!config) {
|
||||
// console.warn(`Extension`, path, `has no config`)
|
||||
// }
|
||||
pkgs[name] = { name, path, config, entry }
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
export function normalizationActivationEvents(
|
||||
ctx: ComfyExtensionLoadContext,
|
||||
config: ComfyExtensionConfig | undefined
|
||||
): string[] {
|
||||
if (!config) return ['*']
|
||||
|
||||
if (!checkAboutCloud(ctx, config)) return []
|
||||
|
||||
const { activationEvents, contributes: _contributes } = config
|
||||
|
||||
if (activationEvents.includes('*')) return ['*']
|
||||
|
||||
const contributes = _contributes
|
||||
? Array.isArray(_contributes)
|
||||
? _contributes
|
||||
: [_contributes]
|
||||
: []
|
||||
|
||||
const events: string[] = []
|
||||
|
||||
if (activationEvents.includes('onCommands:contributes')) {
|
||||
for (const contribute of contributes) {
|
||||
if (contribute.commands) {
|
||||
for (const command of contribute.commands) {
|
||||
events.push(`onCommands:${command.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activationEvents.includes('onSettings:contributes')) {
|
||||
for (const contribute of contributes) {
|
||||
if (contribute.settings) {
|
||||
for (const setting of contribute.settings) {
|
||||
events.push(`onSettings:${setting.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activationEvents.includes('onWidgets:contributes')) {
|
||||
for (const contribute of contributes) {
|
||||
if (contribute.widgets) {
|
||||
for (const widget of contribute.widgets) {
|
||||
events.push(`onWidgets:${widget}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
function checkAboutCloud(
|
||||
ctx: ComfyExtensionLoadContext,
|
||||
extConfig: ComfyExtensionConfig
|
||||
): boolean {
|
||||
// Cloud Only Extension
|
||||
const { comfyCloud } = extConfig
|
||||
if (comfyCloud) {
|
||||
if (!ctx.isCloud) return false
|
||||
if (comfyCloud === true) return true
|
||||
// comfyCloud.subscriptionRequired: extension declares it needs a subscription
|
||||
// ctx.subscriptionRequired: platform confirms the user has an active subscription
|
||||
return comfyCloud.subscriptionRequired && ctx.subscriptionRequired
|
||||
}
|
||||
|
||||
// Default Extension -> Load Extension
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a queue processing function for handling elements in a queue.
|
||||
* When elements are present in the queue, all existing elements are automatically retrieved
|
||||
* and passed to the callback function for processing.
|
||||
* Use the process method to register a callback function, and use the push method to add elements to the queue.
|
||||
* @returns Returns an object containing process and push methods
|
||||
*/
|
||||
export function defineProcessQueue<T>(): {
|
||||
process: (worker: (items: T[]) => void) => void
|
||||
push: (items: T[]) => void
|
||||
} {
|
||||
let worker: ((items: T[]) => void) | undefined = undefined
|
||||
const items: T[] = []
|
||||
function push(newItems: T[]) {
|
||||
items.push(...newItems)
|
||||
consume()
|
||||
}
|
||||
function process(newWorker: (items: T[]) => void) {
|
||||
worker = newWorker
|
||||
consume()
|
||||
}
|
||||
|
||||
function consume() {
|
||||
if (worker !== undefined && items.length > 0) {
|
||||
const itemsToProcess = items.slice()
|
||||
items.length = 0
|
||||
worker(itemsToProcess)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
process,
|
||||
push
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import type { Settings } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { TreeNode } from '@/types/treeExplorerTypes'
|
||||
import { processExtensionSettings } from '@/extensions/dispatch'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
|
||||
export const getSettingInfo = (setting: SettingParams) => {
|
||||
const parts = setting.category || setting.id.split('.')
|
||||
@@ -82,6 +84,14 @@ export const useSettingStore = defineStore('setting', () => {
|
||||
await loadSettingValues()
|
||||
}
|
||||
|
||||
const { wrapWithErrorHandling } = useErrorHandling()
|
||||
processExtensionSettings((settings) => {
|
||||
const _addSetting = wrapWithErrorHandling(addSetting)
|
||||
for (const setting of settings) {
|
||||
_addSetting(setting)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Check if a setting's value exists, i.e. if the user has set it manually.
|
||||
* @param key - The key of the setting to check.
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
|
||||
import { appendCloudResParam } from '@/platform/distribution/cloudPreviewUtil'
|
||||
import { SUPPORTED_EXTENSIONS_ACCEPT } from '@/extensions/core/load3d/constants'
|
||||
import { SUPPORTED_EXTENSIONS_ACCEPT } from '@/extensions/core/extensions/load3d/constants'
|
||||
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
|
||||
import { useMediaAssets } from '@/platform/assets/composables/media/useMediaAssets'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PrimitiveNode } from '@/extensions/core/widgetInputs'
|
||||
import type { PrimitiveNode } from '@/extensions/core/extensions/widgetInputs'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
export const isPrimitiveNode = (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
import { importExtensionsByEvent } from '@/extensions/dispatch'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
|
||||
@@ -37,7 +38,12 @@ export const useExtensionService = () => {
|
||||
|
||||
// Need to load core extensions first as some custom extensions
|
||||
// may depend on them.
|
||||
await import('../extensions/core/index')
|
||||
const { registerExtensions } = await import('../extensions/core/index')
|
||||
await registerExtensions()
|
||||
|
||||
// Import Immediately Loaded Extensions
|
||||
await importExtensionsByEvent('*')
|
||||
|
||||
extensionStore.captureCoreExtensions()
|
||||
await Promise.all(
|
||||
extensions
|
||||
|
||||
@@ -73,6 +73,10 @@ import {
|
||||
import { getOrderedInputSpecs } from '@/workbench/utils/nodeDefOrderingUtil'
|
||||
|
||||
import { useExtensionService } from './extensionService'
|
||||
import {
|
||||
extensionsImportEventHas,
|
||||
importExtensionsByEvent
|
||||
} from '@/extensions/dispatch'
|
||||
import { useMaskEditor } from '@/composables/maskeditor/useMaskEditor'
|
||||
|
||||
export interface HasInitialMinSize {
|
||||
@@ -82,6 +86,38 @@ export interface HasInitialMinSize {
|
||||
export const CONFIG = Symbol()
|
||||
export const GET_CONFIG = Symbol()
|
||||
|
||||
function addInputsAndimportWidgetsAsNeeded(options: {
|
||||
orderedInputSpecs: InputSpec[]
|
||||
addInputSocket: (inputSpec: InputSpec) => void
|
||||
addInputWidget: (inputSpec: InputSpec) => void
|
||||
}) {
|
||||
const { orderedInputSpecs, addInputSocket, addInputWidget } = options
|
||||
const awaitedInputSpecs: InputSpec[] = []
|
||||
const syncInputSpecs: InputSpec[] = []
|
||||
const importJobs: Promise<void>[] = []
|
||||
for (const inputSpec of orderedInputSpecs) {
|
||||
const widgetType = inputSpec.widgetType ?? inputSpec.type
|
||||
if (extensionsImportEventHas(`onWidgets:${widgetType}`)) {
|
||||
importJobs.push(importExtensionsByEvent(`onWidgets:${widgetType}`))
|
||||
awaitedInputSpecs.push(inputSpec)
|
||||
} else {
|
||||
syncInputSpecs.push(inputSpec)
|
||||
}
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
await Promise.all(importJobs)
|
||||
for (const inputSpec of awaitedInputSpecs) addInputSocket(inputSpec)
|
||||
for (const inputSpec of awaitedInputSpecs) addInputWidget(inputSpec)
|
||||
})().catch((error) => {
|
||||
console.error('Failed to load widget extensions', error)
|
||||
})
|
||||
|
||||
// Create sockets and widgets in the determined order
|
||||
for (const inputSpec of syncInputSpecs) addInputSocket(inputSpec)
|
||||
for (const inputSpec of syncInputSpecs) addInputWidget(inputSpec)
|
||||
}
|
||||
|
||||
export function getExtraOptionsForWidget(
|
||||
node: LGraphNode,
|
||||
widget: IBaseWidget
|
||||
@@ -249,10 +285,20 @@ export const useLitegraphService = () => {
|
||||
) ?? {}
|
||||
|
||||
if (widget) {
|
||||
widget.label = st(
|
||||
nameKey,
|
||||
widget.label ?? widgetInputSpec.display_name ?? inputName
|
||||
)
|
||||
// Check if this is an Asset Browser button widget
|
||||
const isAssetBrowserButton =
|
||||
widget.type === 'button' && widget.value === 'Select model'
|
||||
|
||||
if (isAssetBrowserButton) {
|
||||
// Preserve Asset Browser button label (don't translate)
|
||||
widget.label = String(widget.value)
|
||||
} else {
|
||||
// Apply normal translation for other widgets
|
||||
widget.label = st(
|
||||
nameKey,
|
||||
widget.label ?? widgetInputSpec.display_name ?? inputName
|
||||
)
|
||||
}
|
||||
widget.options ??= {}
|
||||
Object.assign(widget.options, {
|
||||
advanced: inputSpec.advanced,
|
||||
@@ -289,9 +335,11 @@ export const useLitegraphService = () => {
|
||||
const nodeDefImpl = node.constructor.nodeData as ComfyNodeDefImpl
|
||||
const orderedInputSpecs = getOrderedInputSpecs(nodeDefImpl, inputs)
|
||||
|
||||
// Create sockets and widgets in the determined order
|
||||
for (const inputSpec of orderedInputSpecs) addInputSocket(node, inputSpec)
|
||||
for (const inputSpec of orderedInputSpecs) addInputWidget(node, inputSpec)
|
||||
addInputsAndimportWidgetsAsNeeded({
|
||||
orderedInputSpecs,
|
||||
addInputSocket: (inputSpec) => addInputSocket(node, inputSpec),
|
||||
addInputWidget: (inputSpec) => addInputWidget(node, inputSpec)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
*/
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
import type Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import type Load3d from '@/extensions/core/extensions/load3d/Load3d'
|
||||
import type {
|
||||
AnimationItem,
|
||||
BackgroundRenderModeType,
|
||||
CameraType,
|
||||
MaterialMode,
|
||||
UpDirection
|
||||
} from '@/extensions/core/load3d/interfaces'
|
||||
} from '@/extensions/core/extensions/load3d/interfaces'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { Object3D } from 'three'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { GroupNodeHandler } from '@/extensions/core/groupNode'
|
||||
import type { GroupNodeHandler } from '@/extensions/core/extensions/groupNode'
|
||||
import type {
|
||||
ExecutableLGraphNode,
|
||||
ExecutionId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GroupNodeHandler } from '@/extensions/core/groupNode'
|
||||
import type { GroupNodeHandler } from '@/extensions/core/extensions/groupNode'
|
||||
import { ExecutableNodeDTO } from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
ExecutableLGraphNode,
|
||||
|
||||
Reference in New Issue
Block a user