Files
DemonGatanjieu-Anomalous_Mo…/api/block_chain.py
T
DemonGatanjieuandClaude Opus 5.5 8ecc261949 Let ready-made blocks say what they hand the next one, and build them as a chain
Every block in api/blocks names what it gives a block after it ("gives": its sampler and its
output node; {} for one that changes the step it is put on) and its own model loader
("own_model"). api/block_chain.py builds a list of steps on a new tab: the picture first, each
next block anchored to what the step before gave, by near_node_id and a new page-only
save_node_id (canvas_roles.js findRoles pins the output node), so a canvas with several
samplers and outputs is no guess. add_nodes' answer names the block it built.

Redraw with another model and face/hand detailing now take the step before's picture
("$save.images") instead of the sampler's decode, which after an enlargement is the older
picture. The two-model routes are built as two-step chains; routes.json no longer names the
loaders.

Checked through the real page on the test ComfyUI: the Tile route on two Anima models (base on
Harem 1.5, Tile redraw on Kirazuri v4, from the base's picture and prompts), then face and hand
detailing after it took the Tile step's colour-matched picture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 00:42:56 +08:00

56 lines
3.3 KiB
Python

"""A chain of ready-made blocks (reference_blocks.py) built on a new tab, one after another: the plan an AI
lays out in big steps (make the picture, redraw it with another model, fix the faces, enlarge), built by
Anomalous so the AI wires nothing. The first step makes the picture (a block that stands alone, on the
model it names). Each next block goes on from what the step before gave (a block's "gives": the sampler
a next one takes its model, prompts and settings from, and the output node whose picture it works on),
both named to the page, so nothing is guessed when the canvas holds several samplers and outputs. A block
that gives nothing changes the step it is put on (a pose to follow, a split between two models), and the
next goes on from the same place. A step may name a model for the block's own loader ("own_model"), and
LoRAs: on the picture's model, or on a bare second loader (a copied model line has them already)."""
from . import reference_blocks
from .mcp_tools import ToolError
def _loader(step, block, nodes):
"""The node id of the block's own model loader in `nodes` (add_nodes' key -> id), or None."""
own = reference_blocks.BLOCKS[block].get("own_model") or {}
found = next((nodes[key] for key in own.get("nodes") or () if key in nodes), None)
if step.get("model") and found is None:
raise ToolError(f"Step {block} has no loader of its own for a second model; leave model out.")
return None if found is None else int(found)
def build(ctx, steps, name="plan"):
"""`steps` ([{use, want | block, model, loras, image}], the first a picture) on a new tab named `name`.
{"steps": [{"block", "nodes"}], "nodes": every step's nodes by "<step>.<key>"} for what was built."""
from . import mcp_actions, mcp_canvas_edit
if not steps:
raise ToolError("A plan has at least its first step, the picture.")
mcp_canvas_edit.new_workflow(ctx, name)
at, built = {"sampler": None, "save": None}, []
for index, step in enumerate(steps):
how = {key: step[key] for key in ("use", "want", "block", "image") if step.get(key) is not None}
if index == 0:
how["model"] = step.get("model")
else:
how.update(near_node_id=at["sampler"], save_node_id=at["save"])
added = mcp_canvas_edit.add_nodes(ctx, **how)
block, nodes = added.get("block") or step.get("block"), added.get("nodes") or {}
if index == 0 and not reference_blocks.BLOCKS.get(block, {}).get("standalone"):
raise ToolError("A plan starts with the picture (use=picture).")
loader = int(nodes["model"]) if index == 0 else _loader(step, block, nodes)
if index and step.get("model"):
mcp_actions.set_model(ctx, loader, step["model"])
copied = (reference_blocks.BLOCKS[block].get("own_model") or {}).get("copies_loras")
if loader is not None and not copied:
for lora in step.get("loras") or ():
mcp_actions.add_lora(ctx, lora, after_node_id=loader)
for role, key in (reference_blocks.BLOCKS[block].get("gives") or {}).items():
if key in nodes:
at[role] = int(nodes[key])
built.append({"block": block, "nodes": nodes})
return {"steps": built, "nodes": {f"{at_step}.{key}": node for at_step, item in enumerate(built, 1)
for key, node in item["nodes"].items()}}