- Fix "Behind Subject" not occluding: blur the two halves separately and re-apply the silhouette after, add a traced cast shadow (shadow_strength, shadow_length), and give the background glow real radial falloff - Replace use_colored_lights with lighting_mode, adding "Both"; three presets set a colour and a grading block, so 12 values in each were inert - Rename use_gradient_mode to mask_shape, and collapse apply_3d_lighting plus light_direction into subject_interaction - Remove show_debug_info: wiring debug_image is the whole gesture, tracked through a hidden debug_output_connected input so one run is enough - Scale everything drawn on the debug view with the frame, and flip a light's label to the left of the marker when it would run off the right edge - Show only the controls that are doing something; grey out what a preset owns and put the preset's value in the label - Ship a working "Fix node (recreate)" and remove ComfyUI-Manager's broken one - Remap pre-v4 saves by name in web/relight_migrate.js - Make "Rim Light (Behind)" a neutral white rim instead of (200,255,200) - Drop requirements.txt; numpy, Pillow, scipy and torch come with ComfyUI - Add a frontend test suite driving the real web/*.js outside a browser, and a CI job for it - Bump to 4.0.0
113 lines
4.5 KiB
Python
113 lines
4.5 KiB
Python
"""Generate the two files the frontend derives from the live node schema.
|
|
|
|
Both would otherwise be hand-maintained copies of what ``relight.py`` already
|
|
says, and a silent drift in either is a frontend that lies about the node:
|
|
|
|
* ``tests/frontend/fixtures/schema.json`` - widget names, order and defaults,
|
|
which the JS tests build their fake nodes from.
|
|
* ``web/relight_presets.js`` - which widgets each preset overrides, so
|
|
``relight_ui.js`` can grey exactly those and no others.
|
|
|
|
``--check`` fails if either committed file is stale, and
|
|
``tests/test_relight.py`` runs that check.
|
|
|
|
python scripts/dump_frontend_fixture.py # rewrite both
|
|
python scripts/dump_frontend_fixture.py --check # fail if either is stale
|
|
"""
|
|
import argparse
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
FIXTURE = REPO_ROOT / "tests" / "frontend" / "fixtures" / "schema.json"
|
|
PRESETS_MODULE = REPO_ROOT / "web" / "relight_presets.js"
|
|
|
|
# The stub must win over any real ComfyUI on the path, exactly as in conftest.
|
|
sys.path.insert(0, str(REPO_ROOT / "tests" / "stubs"))
|
|
sys.path.insert(1, str(REPO_ROOT))
|
|
|
|
from relight import ReLight # noqa: E402
|
|
|
|
|
|
def build():
|
|
schema = ReLight.define_schema()
|
|
widgets = []
|
|
for spec in schema.inputs:
|
|
if spec.id in ("image", "mask"):
|
|
continue # link inputs, never widgets
|
|
entry = {"name": spec.id, "default": spec.default}
|
|
options = spec.kwargs.get("options")
|
|
if options is not None:
|
|
entry["options"] = list(options)
|
|
widgets.append(entry)
|
|
return {"node_class": schema.node_id, "widgets": widgets}
|
|
|
|
|
|
def build_presets_module():
|
|
"""`web/relight_presets.js`: which widgets each preset takes control of."""
|
|
keys = {name: sorted(preset) for name, preset in ReLight.PRESETS.items()}
|
|
values = {
|
|
name: {key: preset[key] for key in sorted(preset)}
|
|
for name, preset in ReLight.PRESETS.items()
|
|
}
|
|
keys_body = json.dumps(keys, indent=4, sort_keys=False)
|
|
values_body = json.dumps(values, indent=4, sort_keys=False)
|
|
return (
|
|
"/*\n"
|
|
" * GENERATED by scripts/dump_frontend_fixture.py - do not edit.\n"
|
|
" *\n"
|
|
" * Which widgets each preset overrides, and what it sets them to.\n"
|
|
" * relight_ui.js greys exactly these while a preset is selected, because a\n"
|
|
" * control a preset has taken over is one whose value on the node is being\n"
|
|
" * ignored - and a widget you can still drag that changes nothing is worse\n"
|
|
" * than one that looks unavailable.\n"
|
|
" *\n"
|
|
" * The values are here because a greyed widget cannot show its own: the\n"
|
|
" * ComfyUI frontend blanks `_displayValue` for any disabled widget. Without\n"
|
|
" * them a greyed row is an empty bar. relight_ui.js writes the preset's\n"
|
|
" * value into the widget's label instead, which still paints.\n"
|
|
" *\n"
|
|
" * Derived from ReLight.PRESETS so it cannot drift; the --check run in\n"
|
|
" * tests/test_relight.py fails if this file is stale.\n"
|
|
" */\n\n"
|
|
f"export const PRESET_KEYS = {keys_body};\n\n"
|
|
"/** What each preset sets those widgets to, for the greyed-out labels. */\n"
|
|
f"export const PRESET_VALUES = {values_body};\n\n"
|
|
"/** Widgets a preset may name but that `preserve_positioning` hands back. */\n"
|
|
f"export const GEOMETRY_KEYS = {json.dumps(sorted(ReLight.GEOMETRY_KEYS), indent=4)};\n\n"
|
|
"/** The widget a preset scales rather than replaces. */\n"
|
|
f"export const STRENGTH_KEY = {json.dumps(ReLight.STRENGTH_KEY)};\n"
|
|
)
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--check", action="store_true", help="fail if a generated file is stale")
|
|
args = parser.parse_args(argv)
|
|
|
|
generated = {
|
|
FIXTURE: json.dumps(build(), indent=2) + "\n",
|
|
PRESETS_MODULE: build_presets_module(),
|
|
}
|
|
|
|
if args.check:
|
|
stale = [
|
|
path
|
|
for path, payload in generated.items()
|
|
if not path.exists() or path.read_text(encoding="utf-8") != payload
|
|
]
|
|
for path in stale:
|
|
print(f"{path.relative_to(REPO_ROOT).as_posix()} is stale; re-run this script")
|
|
return 1 if stale else 0
|
|
|
|
for path, payload in generated.items():
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(payload, encoding="utf-8")
|
|
print(f"wrote {path.relative_to(REPO_ROOT).as_posix()}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|