Improve Model Resolver dependency marker node compatibility and rendering
- Adopt ComfyUI's ComfyExtension/comfy_entrypoint API in __init__.py when available, falling back to legacy NODE_CLASS_MAPPINGS/DISPLAY_NAME_MAPPINGS on older runtimes so the node loads across ComfyUI versions. - Hook into beforeRegisterNodeDef, loadedGraphNode, and afterConfigureGraph in web/resolver.js so marker nodes are patched during registration, graph load, and initial render. - Teach model_resolver.js to recognise the marker node by every known class property shape, patch its type on creation, enforce a minimum size with setSize() when present, and dirty the canvas so changes are reflected.
This commit is contained in:
+59
-21
@@ -31,35 +31,73 @@ from .core.log_system.config import LOG_LEVEL as BACKEND_DEFAULT_LOG_LEVEL
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_TYPE = "ModelResolverDependency"
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME = "Model Resolver Opener"
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY = "Model Resolver/Workflow"
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION = (
|
||||
"A passive opener node for workflows that intentionally depend on "
|
||||
"Model Resolver metadata. It does not process images, models, or prompts."
|
||||
)
|
||||
|
||||
try:
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
except Exception:
|
||||
ComfyExtension = None
|
||||
io = None
|
||||
|
||||
|
||||
class ModelResolverDependencyNode:
|
||||
"""Canvas node that declares Model Resolver and opens its workflow tools."""
|
||||
if ComfyExtension is not None and io is not None:
|
||||
|
||||
DESCRIPTION = (
|
||||
"A passive marker node for workflows that intentionally depend on "
|
||||
"Model Resolver metadata. It does not process images, models, or prompts."
|
||||
)
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "noop"
|
||||
CATEGORY = "Model Resolver/Workflow"
|
||||
class ModelResolverDependencyNode(io.ComfyNode):
|
||||
"""Canvas node that declares Model Resolver and opens its workflow tools."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id=MODEL_RESOLVER_DEPENDENCY_NODE_TYPE,
|
||||
display_name=MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME,
|
||||
category=MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY,
|
||||
description=MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION,
|
||||
inputs=[],
|
||||
outputs=[],
|
||||
)
|
||||
|
||||
def noop(self):
|
||||
return ()
|
||||
@classmethod
|
||||
def execute(cls) -> io.NodeOutput:
|
||||
return io.NodeOutput()
|
||||
|
||||
class ModelResolverNodeExtension(ComfyExtension):
|
||||
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||
return [ModelResolverDependencyNode]
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: ModelResolverDependencyNode,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: "Model Resolver Opener",
|
||||
}
|
||||
async def comfy_entrypoint() -> ComfyExtension:
|
||||
return ModelResolverNodeExtension()
|
||||
|
||||
__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
__all__ = ["WEB_DIRECTORY", "comfy_entrypoint"]
|
||||
else:
|
||||
|
||||
class ModelResolverDependencyNode:
|
||||
"""Legacy fallback for ComfyUI builds without the v3 custom-node API."""
|
||||
|
||||
DESCRIPTION = MODEL_RESOLVER_DEPENDENCY_NODE_DESCRIPTION
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "noop"
|
||||
CATEGORY = MODEL_RESOLVER_DEPENDENCY_NODE_CATEGORY
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
def noop(self):
|
||||
return ()
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: ModelResolverDependencyNode,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
MODEL_RESOLVER_DEPENDENCY_NODE_TYPE: MODEL_RESOLVER_DEPENDENCY_NODE_DISPLAY_NAME,
|
||||
}
|
||||
|
||||
__all__ = ["WEB_DIRECTORY", "NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
|
||||
class JobProgressTracker:
|
||||
|
||||
@@ -29,7 +29,16 @@ app.registerExtension({
|
||||
MODEL_RESOLVER_OPEN_DEFAULT_KEYBINDING,
|
||||
],
|
||||
setup: modelResolver.setup,
|
||||
beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
modelResolver.configureWorkflowDependencyMarkerNodeType(nodeType, nodeData);
|
||||
},
|
||||
nodeCreated(node) {
|
||||
modelResolver.configureWorkflowDependencyMarkerNode(node);
|
||||
},
|
||||
loadedGraphNode(node) {
|
||||
modelResolver.configureWorkflowDependencyMarkerNode(node);
|
||||
},
|
||||
afterConfigureGraph() {
|
||||
modelResolver.configureWorkflowDependencyMarkerNodes();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -543,6 +543,7 @@ export class ModelResolver {
|
||||
window.__ModelResolverWorkflowHashMetadataOwner = this;
|
||||
if (window.__ModelResolverWorkflowHashMetadataPatched) {
|
||||
this.scheduleWorkflowHashMetadataRefresh();
|
||||
this.configureWorkflowDependencyMarkerNodes();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -559,6 +560,7 @@ export class ModelResolver {
|
||||
return workflow;
|
||||
};
|
||||
window.__ModelResolverWorkflowHashMetadataPatched = true;
|
||||
this.configureWorkflowDependencyMarkerNodes();
|
||||
this.scheduleWorkflowHashMetadataRefresh();
|
||||
}
|
||||
|
||||
@@ -568,8 +570,60 @@ export class ModelResolver {
|
||||
return workflow;
|
||||
}
|
||||
|
||||
isWorkflowDependencyMarkerNode(node) {
|
||||
if (!node) return false;
|
||||
const candidates = [
|
||||
node.type,
|
||||
node.comfyClass,
|
||||
node.comfy_class,
|
||||
node.constructor?.ComfyClass,
|
||||
node.constructor?.comfyClass,
|
||||
node.constructor?.nodeData?.name,
|
||||
];
|
||||
return candidates.some((candidate) => candidate === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE);
|
||||
}
|
||||
|
||||
isWorkflowDependencyMarkerNodeType(nodeType, nodeData) {
|
||||
const candidates = [
|
||||
nodeData?.name,
|
||||
nodeData?.node_id,
|
||||
nodeData?.class_type,
|
||||
nodeType?.ComfyClass,
|
||||
nodeType?.comfyClass,
|
||||
nodeType?.type,
|
||||
nodeType?.prototype?.comfyClass,
|
||||
];
|
||||
return candidates.some((candidate) => candidate === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE);
|
||||
}
|
||||
|
||||
configureWorkflowDependencyMarkerNodeType(nodeType, nodeData) {
|
||||
if (!this.isWorkflowDependencyMarkerNodeType(nodeType, nodeData)) return;
|
||||
const proto = nodeType?.prototype;
|
||||
if (!proto || proto.__modelResolverDependencyMarkerTypePatched) return;
|
||||
|
||||
const resolver = this;
|
||||
const originalOnNodeCreated = proto.onNodeCreated;
|
||||
proto.onNodeCreated = function(...args) {
|
||||
const result = originalOnNodeCreated?.apply(this, args);
|
||||
resolver.configureWorkflowDependencyMarkerNode(this);
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalComputeSize = proto.computeSize;
|
||||
proto.computeSize = function(...args) {
|
||||
const size = typeof originalComputeSize === 'function'
|
||||
? originalComputeSize.apply(this, args)
|
||||
: this.size;
|
||||
const width = Math.max(Number(size?.[0]) || 0, 260);
|
||||
const height = Math.max(Number(size?.[1]) || 0, 110);
|
||||
return [width, height];
|
||||
};
|
||||
|
||||
proto.__modelResolverDependencyMarkerTypePatched = true;
|
||||
}
|
||||
|
||||
configureWorkflowDependencyMarkerNode(node) {
|
||||
if (!node || node.type !== WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) return node;
|
||||
if (!this.isWorkflowDependencyMarkerNode(node)) return node;
|
||||
|
||||
node.properties = node.properties && typeof node.properties === 'object' ? node.properties : {};
|
||||
delete node.properties.cnr_id;
|
||||
@@ -587,6 +641,7 @@ export class ModelResolver {
|
||||
);
|
||||
if (existingOpenButton) {
|
||||
existingOpenButton.callback = () => this.activateResolverButton();
|
||||
existingOpenButton.value = WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME;
|
||||
existingOpenButton.__modelResolverOpenButton = true;
|
||||
existingOpenButton.options = existingOpenButton.options && typeof existingOpenButton.options === 'object'
|
||||
? existingOpenButton.options
|
||||
@@ -596,7 +651,7 @@ export class ModelResolver {
|
||||
const button = node.addWidget(
|
||||
"button",
|
||||
WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME,
|
||||
null,
|
||||
WORKFLOW_DEPENDENCY_MARKER_BUTTON_NAME,
|
||||
() => this.activateResolverButton(),
|
||||
{ serialize: false }
|
||||
);
|
||||
@@ -608,8 +663,15 @@ export class ModelResolver {
|
||||
}
|
||||
|
||||
if (Array.isArray(node.size)) {
|
||||
node.size[0] = Math.max(Number(node.size[0]) || 0, 260);
|
||||
node.size[1] = Math.max(Number(node.size[1]) || 0, 90);
|
||||
const size = [
|
||||
Math.max(Number(node.size[0]) || 0, 260),
|
||||
Math.max(Number(node.size[1]) || 0, 110),
|
||||
];
|
||||
if (typeof node.setSize === 'function') {
|
||||
node.setSize(size);
|
||||
} else {
|
||||
node.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!node.__modelResolverDependencySerializePatched) {
|
||||
@@ -624,9 +686,19 @@ export class ModelResolver {
|
||||
node.__modelResolverDependencySerializePatched = true;
|
||||
}
|
||||
|
||||
node.graph?.setDirtyCanvas?.(true, true);
|
||||
app.canvas?.setDirty?.(true, true);
|
||||
return node;
|
||||
}
|
||||
|
||||
configureWorkflowDependencyMarkerNodes(graph = app?.graph) {
|
||||
const nodes = graph?._nodes || graph?.nodes || [];
|
||||
if (!Array.isArray(nodes)) return;
|
||||
for (const node of nodes) {
|
||||
this.configureWorkflowDependencyMarkerNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
configureSerializedDependencyMarkerNode(node) {
|
||||
if (!node || node.type !== WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) return node;
|
||||
|
||||
@@ -654,7 +726,7 @@ export class ModelResolver {
|
||||
findWorkflowDependencyMarkerNode(graph = app?.graph) {
|
||||
const nodes = graph?._nodes || graph?.nodes || [];
|
||||
return Array.isArray(nodes)
|
||||
? nodes.find((node) => node?.type === WORKFLOW_DEPENDENCY_MARKER_NODE_TYPE) || null
|
||||
? nodes.find((node) => this.isWorkflowDependencyMarkerNode(node)) || null
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user