fix: an unlinked H3 latent length follows the project's frames; export shows it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
b036a533aa
commit
389fbba275
@@ -3,6 +3,8 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- An H3 latent node (Reference-to-Video / Empty AV Latent) whose `length` is not linked now follows the project's frames, instead of keeping an old typed number that made the sampler refuse the run; the settings export shows it as "follows project".
|
||||
- A view added by Shot views (POV...) no longer makes the shot look like it already has a camera move.
|
||||
- Export settings no longer prints a loader input the node no longer has (a stale `sla_enabled: on`) or SLA's settings while `sla` is off.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -185,6 +185,10 @@ FAMILY_FRAME_GRID: dict[str, dict] = {
|
||||
}
|
||||
|
||||
|
||||
#: nodes that make the H3 latent template; their `length` must equal the sampler's frames.
|
||||
H3_LENGTH_NODES = frozenset({"MiniMaxH3ReferenceToVideo", "EmptyMiniMaxH3LatentAV"})
|
||||
|
||||
|
||||
def family_frames(family: str, frames) -> int:
|
||||
"""`frames` snapped UP to `family`'s pixel-frame grid (never below one whole step)."""
|
||||
grid = FAMILY_FRAME_GRID.get(family) or FAMILY_FRAME_GRID[DEFAULT_FAMILY]
|
||||
@@ -683,7 +687,19 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No
|
||||
for s in slots:
|
||||
sid = slot_node_id[s["id"]]
|
||||
nd_s = slot_def[s["id"]]
|
||||
for ci_name, source in (s.get("input_sources") or {}).items():
|
||||
sources = dict(s.get("input_sources") or {})
|
||||
# An H3 latent node has to be built for the SAME length the sampler is given or the run
|
||||
# dies at sampling ("latent node's `length` must be the SAME ... frame count"). Left
|
||||
# unlinked it keeps whatever number it was last typed with, so changing the project's
|
||||
# length silently stranded it. Unlinked = follows the project; a choice stays a choice.
|
||||
if (s.get("node_class") in H3_LENGTH_NODES and not sources.get("length")
|
||||
and "frames" in graph and "length" in (graph[sid].get("inputs") or {})
|
||||
and not isinstance(graph[sid]["inputs"]["length"], list)):
|
||||
was = graph[sid]["inputs"]["length"]
|
||||
sources["length"] = "core:frames:0"
|
||||
report["wired"].append(f"{s.get('node_class')} length ({was}) -> Project frames "
|
||||
f"({graph['frames']['inputs'].get('value')})")
|
||||
for ci_name, source in sources.items():
|
||||
ci_name = _canonical_input(nd_s, ci_name)
|
||||
if not source or source == "auto":
|
||||
continue
|
||||
|
||||
@@ -550,6 +550,11 @@ def _sampling_sections(studio_inputs: dict, sampler_inputs: dict) -> list:
|
||||
return out
|
||||
|
||||
|
||||
def _h3_length_nodes():
|
||||
from .builder import H3_LENGTH_NODES
|
||||
return H3_LENGTH_NODES
|
||||
|
||||
|
||||
def collect(models: dict, host: dict, *, project_name=None, version=None,
|
||||
codename=None, render=None, studio_inputs=None, sampler_inputs=None) -> dict:
|
||||
"""The card's content, as data. Rendering is a separate step so this is testable."""
|
||||
@@ -580,6 +585,12 @@ def collect(models: dict, host: dict, *, project_name=None, version=None,
|
||||
rows.append((name, ""))
|
||||
rows.extend((f" {k}", v) for k, v in listed)
|
||||
continue
|
||||
if (name == "length" and slot.get("node_class") in _h3_length_nodes()
|
||||
and not (slot.get("input_sources") or {}).get("length")
|
||||
and (render or {}).get("frames per scene")):
|
||||
# The builder links an unlinked H3 latent length to the project's frames, so
|
||||
# the typed number is not what runs.
|
||||
value = f"follows project ({render['frames per scene']})"
|
||||
rows.append((name, _short(value)))
|
||||
sections.append({
|
||||
"title": str(title),
|
||||
|
||||
@@ -1613,3 +1613,28 @@ def test_studio_skips_its_own_enhancement_when_nothing_reads_its_conditioning():
|
||||
"slots": [{"id": "r2v", "node_class": "R2V", "inputs": {}, "wires": {}}],
|
||||
"core_overrides": {"sampler": {"positive": "out:r2v:CONDITIONING"}}}
|
||||
assert rf_of(bypassed)["prompt_enhance_scenes"] is False
|
||||
|
||||
|
||||
def _lat(graph):
|
||||
return next(n for n in graph.values() if n["class_type"] == "EmptyMiniMaxH3LatentAV")
|
||||
|
||||
|
||||
def test_an_unlinked_h3_latent_length_follows_the_projects_frames():
|
||||
"""The project length was changed to 362 and the latent node kept its typed 260: the
|
||||
sampler then refuses the run. Unlinked = follows the project."""
|
||||
models = {"model_family": "minimax_h3", "slots": [
|
||||
dict(s, inputs={"length": 260}) if s["id"] == "lat" else s for s in H3_MODELS["slots"]]}
|
||||
graph, report = builder.build(H3_OI, models, {"prompt": "a shot", "num_frames_per_scene": 362})
|
||||
length = _lat(graph)["inputs"]["length"]
|
||||
assert isinstance(length, list) and length[0] == "frames"
|
||||
assert graph["frames"]["inputs"]["value"] == 362
|
||||
assert any("length (260)" in w and "362" in w for w in report["wired"]), report["wired"]
|
||||
|
||||
|
||||
def test_a_linked_h3_latent_length_is_left_as_the_user_linked_it():
|
||||
models = {"model_family": "minimax_h3", "slots": [
|
||||
dict(s, inputs={"length": 260}, input_sources={"length": "core:frames:0"})
|
||||
if s["id"] == "lat" else s for s in H3_MODELS["slots"]]}
|
||||
graph, report = builder.build(H3_OI, models, {"prompt": "a shot", "num_frames_per_scene": 362})
|
||||
assert _lat(graph)["inputs"]["length"][0] == "frames"
|
||||
assert not any("length (260)" in w for w in report["wired"])
|
||||
|
||||
@@ -254,3 +254,15 @@ def test_a_model_slot_does_not_print_a_stale_input_or_sla_settings_with_sla_off(
|
||||
rows = dict(next(s for s in sc.collect({"slots": [slot]}, {})["sections"]
|
||||
if s["title"] == "Diffusion model")["rows"])
|
||||
assert "sla_sparsity" in rows and "sla_enabled" not in rows
|
||||
|
||||
|
||||
def test_an_unlinked_h3_latent_length_is_shown_as_following_the_project():
|
||||
slot = {"id": "r", "node_class": "MiniMaxH3ReferenceToVideo", "label": "R2V",
|
||||
"inputs": {"width": 1344, "length": 260}, "input_sources": {"length": ""}}
|
||||
card = sc.collect({"slots": [slot]}, {}, render={"frames per scene": 362})
|
||||
rows = dict(next(s for s in card["sections"] if s["title"] == "R2V")["rows"])
|
||||
assert rows["length"] == "follows project (362)"
|
||||
slot["input_sources"]["length"] = "core:frames:0"
|
||||
rows = dict(next(s for s in sc.collect({"slots": [slot]}, {}, render={"frames per scene": 362})["sections"]
|
||||
if s["title"] == "R2V")["rows"])
|
||||
assert rows["length"] != "follows project (362)"
|
||||
|
||||
Reference in New Issue
Block a user