Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions invokeai/app/invocations/metadata_linked.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from pydantic import model_validator

from invokeai.app.invocations.anima_denoise import AnimaDenoiseInvocation
from invokeai.app.invocations.baseinvocation import (
BaseInvocation,
BaseInvocationOutput,
Expand Down Expand Up @@ -34,6 +35,7 @@
LoRAField,
LoRALoaderOutput,
ModelIdentifierField,
Qwen3EncoderField,
SDXLLoRALoaderOutput,
UNetField,
VAEField,
Expand Down Expand Up @@ -793,6 +795,82 @@ def _loras_to_json(obj: Union[Any, list[Any]]):
return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))


@invocation(
"anima_denoise_meta",
title=f"{AnimaDenoiseInvocation.UIConfig.title} + Metadata",
tags=["anima", "latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
category="metadata",
version="1.0.0",
classification=Classification.Prototype,
)
class AnimaDenoiseMetaInvocation(AnimaDenoiseInvocation, WithMetadata):
"""Run denoising process with an Anima transformer model + metadata."""

# Anima loads its VAE and text encoder as standalone models, so - unlike SD/FLUX, where they come out of
# the main model - they cannot be derived from the transformer field. Accept them here so a workflow can
# record the full component set the recall UI expects, without chaining extra Metadata Item Linked nodes.
vae: Optional[VAEField] = InputField(
default=None,
description="Optional. The Anima VAE, recorded to metadata so recall can restore the VAE selection.",
input=Input.Connection,
title="VAE",
)
qwen3_encoder: Optional[Qwen3EncoderField] = InputField(
default=None,
description="Optional. The Anima Qwen3 encoder, recorded to metadata so recall can restore the encoder "
"selection.",
input=Input.Connection,
title="Qwen3 Encoder",
)

def invoke(self, context: InvocationContext) -> LatentsMetaOutput:
def _loras_to_json(obj: Union[Any, list[Any]]):
if not isinstance(obj, list):
obj = [obj]

output: list[dict[str, Any]] = []
for item in obj:
output.append(
LoRAMetadataField(
model=item.lora,
weight=item.weight,
).model_dump(exclude_none=True, exclude={"id", "type", "is_intermediate", "use_cache"})
)
return output

obj = super().invoke(context)

md: Dict[str, Any] = {} if self.metadata is None else self.metadata.root
md.update({"width": obj.width})
md.update({"height": obj.height})
md.update({"steps": self.steps})
# Anima's CFG value is recorded as `cfg_scale`, matching what the Anima graph builder writes and what
# the UI's CFGScale recall handler reads - not as `guidance`, which is FLUX's guidance-embeds scale.
md.update({"cfg_scale": self.guidance_scale})
md.update({"denoising_start": self.denoising_start})
md.update({"denoising_end": self.denoising_end})
md.update({"scheduler": self.scheduler})
md.update({"model": self.transformer.transformer})
md.update(
{
"seed": self.noise.seed
if self.noise is not None and self.noise.seed is not None and (self.latents is None or self.add_noise)
else self.seed
}
)
if self.vae is not None:
md.update({"vae": self.vae.vae})
if self.qwen3_encoder is not None:
md.update({"qwen3_encoder": self.qwen3_encoder.text_encoder})
if len(self.transformer.loras) > 0:
md.update({"loras": _loras_to_json(self.transformer.loras)})

params = obj.__dict__.copy()
del params["type"]

return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))


@invocation(
"metadata_to_vae",
title="Metadata To VAE",
Expand Down
Loading
Loading