From c0dff7893d87374f0daa8a97598049cfc4e48cd4 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:56:27 +0300 Subject: [PATCH] Movie Editor: emit widget defaults, wireable STRING/forceInput sockets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - builder now emits a value for EVERY widget input (type-appropriate empty when no default is declared), matching ComfyUI's frontend. Fixes generation refusing to start on required widgets with no default (e.g. ImageTransform's 'bboxes', refinement key fields) — they were dropped from the graph and ComfyUI rejected the prompt for a missing required input. Empty required widgets are now a warning, not a blocking error, in the Models modal. - connection_inputs + ports_from_input_types now include forceInput STRING sockets, so refinement_key_input on Studio and the Chain Sampler is visible and can be wired manually (was invisible because STRING is a widget type). Lets a custom STRING source override the default keyloader wire. - Refinement key stays optional: the keyloader defaults to the keyless 'none' key. Co-Authored-By: Claude Opus 4.8 --- movie_editor/backend/builder.py | 12 ++++++++++-- movie_editor/backend/nodes.py | 19 +++++++++++++++---- movie_editor/frontend/models.js | 4 +++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index b6f61c61..dbd6405b 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -92,6 +92,8 @@ CORE_PRODUCERS: list[tuple[str, int, str]] = [ # (core_id, output_index, type) ] CONTROL_VALUES = {"fixed", "randomize", "increment", "decrement"} +# Type-appropriate empty values for widgets that declare no default. +_WIDGET_EMPTY = {"STRING": "", "INT": 0, "FLOAT": 0.0, "BOOLEAN": False} _VHS_NON_INPUT = {"videopreview"} @@ -156,9 +158,15 @@ def _widget_defaults(node_def: Optional[dict]) -> dict: continue if isinstance(t, list): out[name] = opts.get("default", t[0] if t else None) + elif t == "COMBO": + choices = opts.get("options") or [] + out[name] = opts.get("default", choices[0] if choices else "") elif t in WIDGET_PRIMITIVES: - if "default" in opts: - out[name] = opts["default"] + # Always emit a value for every widget — ComfyUI's frontend does, and a + # required widget with no declared default (e.g. ImageTransform's bboxes) + # otherwise goes missing and ComfyUI rejects the prompt. Fall back to a + # type-appropriate empty so generation isn't blocked. + out[name] = opts.get("default", _WIDGET_EMPTY.get(t, "")) return out diff --git a/movie_editor/backend/nodes.py b/movie_editor/backend/nodes.py index dc9dd5a1..7c68cf0c 100644 --- a/movie_editor/backend/nodes.py +++ b/movie_editor/backend/nodes.py @@ -113,9 +113,14 @@ def connection_inputs(node_def: dict) -> list[dict]: if not isinstance(spec, list) or not spec: continue t = spec[0] - if not isinstance(t, str) or t in _WIDGET_TYPES: + if not isinstance(t, str): continue opts = spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {} + # A widget-typed input is a SOCKET (wireable) only when forceInput is set — + # e.g. refinement_key_input is ("STRING", {"forceInput": True}). Plain widgets + # are handled by widget_inputs, not here. + if t in _WIDGET_TYPES and not opts.get("forceInput"): + continue # V3 nodes can mark a required-group input as optional via the flag. is_required = group == "required" and not opts.get("optional", False) out.append({"name": name, "type": _normalize_type(t), "required": is_required}) @@ -254,9 +259,15 @@ def ports_from_input_types(label: str, node_key: str, input_types: dict) -> list if not isinstance(spec, (list, tuple)) or not spec: continue t = spec[0] - if isinstance(t, str) and t not in WIDGET_PRIMITIVES: - ports.append({"id": f"{node_key}.{name}", "node": label, "input": name, - "type": t, "label": f"{label} · {name}"}) + if not isinstance(t, str): + continue + opts = spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {} + # Expose typed sockets, plus forceInput widget sockets (e.g. the STRING + # refinement_key_input on Studio/Sampler) so they can be wired manually. + if t in WIDGET_PRIMITIVES and not opts.get("forceInput"): + continue + ports.append({"id": f"{node_key}.{name}", "node": label, "input": name, + "type": t, "label": f"{label} · {name}"}) return ports diff --git a/movie_editor/frontend/models.js b/movie_editor/frontend/models.js index 11130e4f..2f40b724 100644 --- a/movie_editor/frontend/models.js +++ b/movie_editor/frontend/models.js @@ -124,7 +124,9 @@ if (w.kind === "combo" && (!w.choices || !w.choices.length)) { issues.push({ level: "error", msg: `"${w.name}" has no installed options to pick from.` }); } else if (v == null || v === "") { - issues.push({ level: "error", msg: `Required field "${w.name}" is empty.` }); + // Not blocking: the builder emits a type-appropriate empty for any widget left + // blank (matching ComfyUI's frontend), so an empty field won't stall generation. + issues.push({ level: "warn", msg: `"${w.name}" is empty — using its default.` }); } }); const outs = spec.outputs || [];