Files
EnragedAntelope 04d56deed2 v4.0.0: behind-subject occlusion, a schema that says what it means, and only-live controls
- 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
2026-09-04 14:50:33 -04:00

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())