fix(characters): revert per-view turnaround renders to the single triptych

Drawing front/side/rear separately with the edit model reproduced the exact
failure the triptych was introduced to fix: three near-identical front views,
mixed styles between panels, cropped bodies, and the wireframe pose guide
rendered into the costume.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-21 09:33:23 +02:00
parent 69b5e06f02
commit 9edafe8f4a
2 changed files with 19 additions and 37 deletions

View File

@ -2385,7 +2385,7 @@ def _character_design_variation(name: str, sheet: dict) -> str:
def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
book_profile: dict, visual_style: str,
saved_profile: str, saved_reference: str) -> tuple[str, str, str, str, str, list[str], str]:
saved_profile: str, saved_reference: str) -> tuple[str, str, str, str, str, list[str]]:
"""Short, layout-first prompts for the local Qwen/Flux encoder.
The detailed masters remain visible/editable in the UI. These render
@ -2564,20 +2564,12 @@ def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
f"All three figures are the identical {identical_noun} with identical face, species, age, body proportions, hair, facial hair, costume construction, seams, palette, material wear, and footwear. {proportion_lock}Keep the exact anatomically established facial structure and species traits from Subject in every view. Use the same full-length 50mm camera distance in every panel. Each figure is a tall, upright, fully grown adult whose height is about four times the width of the body at the shoulders. Align all three crowns and all three soles horizontally; each complete head-to-boots silhouette occupies the same height and remains centered in its equal-width third. Use a neutral stance, relaxed arms, visibly empty hands, unobstructed belts, and unobstructed backs. "
"Plain empty warm-white seamless studio background, neutral museum-soft light, soft contact shadows. Keep generous margin above every head and below every foot. The frame contains exactly the three required figures and the plain seamless background. Keep every surface unlettered and free of graphic overlays. Output 1536x1024."
)
# Per-view edit prompts: each view is drawn from the identity portrait as the reference person, so
# the turnaround shows the same face as the profile picture.
same_person = (
"Use the person in the reference image exactly: the same face, hair, age, build, clothing, colours and materials. "
+ head_lock
side_view = (
"Unused: the side view is generated in the controlled turnaround triptych."
)
view_tail = (
f"{proportion_lock}One complete full-body figure from crown to boots with margin above and below, neutral relaxed stance, "
"arms at the sides, visibly empty hands, plain empty warm-white seamless studio background, soft even museum light. "
"Output 512x1024."
back_view = (
"Unused: the rear view is generated in the controlled turnaround triptych."
)
front_edit = same_person + "CAMERA: strict front view, shoulders hips knees and feet square to the camera. " + view_tail
side_view = same_person + "CAMERA: strict left side profile, the whole body turned exactly 90 degrees toward image-left. " + view_tail
back_view = same_person + "CAMERA: seen from directly behind, showing the back of the head, the back of the garments and the heels. " + view_tail
scene_prompt = (
("HEAD CONTINUITY: Every visible person's scalp, crown, hairline, ears, and natural hairstyle are clearly visible and unobstructed. " if bareheaded else "")
+ "CREATE ONE HORIZONTAL PHOTOREALISTIC SOURCE-GROUNDED STORY MOMENT WITH NATURAL ACTION. "
@ -2598,7 +2590,7 @@ def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
)
for item in props
]
return portrait, front_view, side_view, back_view, scene_prompt, prop_prompts, front_edit
return portrait, front_view, side_view, back_view, scene_prompt, prop_prompts
@router.post("/api/character-generate-prompts")
@ -3304,7 +3296,7 @@ def _comfyui_run_consistent_images(comfyui_url: str, workflow_json: str,
front_prompt: str, side_prompt: str,
back_prompt: str, scene_prompt: str,
prop_prompts: list[str], style_key: str = "",
progress=None, front_edit_prompt: str = "") -> dict:
progress=None) -> dict:
"""Generate independently controlled views, scene and unique prop tiles."""
try:
workflow = json.loads(workflow_json)
@ -3339,18 +3331,6 @@ def _comfyui_run_consistent_images(comfyui_url: str, workflow_json: str,
"big",
)
graph["68"].setdefault("inputs", {})["seed"] = view_seed("identity-turnaround")
if front_edit_prompt:
# Front, side and rear are drawn separately by the edit model with the identity portrait as the only
# reference (no pose guides), then stitched into the same 3-panel image the compositor expects.
for text_node, sampler, seed_label, text in (
("10", "12", "front", front_edit_prompt), ("14", "16", "side", side_prompt), ("18", "40", "back", back_prompt),
):
graph[text_node]["inputs"].pop("image2", None)
graph[text_node]["inputs"]["prompt"] = text
graph[sampler]["inputs"]["seed"] = view_seed("edit-" + seed_label)
graph["turnaround_save"]["inputs"]["images"] = ["32", 0]
for dead in ("turnaround_decode", "68", "69", "66", "67", "42", "43", "44", "45", "front_save", "side_save", "back_save"):
graph.pop(dead, None)
for node_id, label in (("26", "prop-1"), ("52", "prop-2"), ("56", "prop-3")):
graph[node_id].setdefault("inputs", {})["seed"] = view_seed(label)
client_id = str(uuid.uuid4())
@ -3574,7 +3554,7 @@ async def character_generate_consistent_images(request: Request):
# The UI stores the full editable production masters. The local image
# encoder receives compact, layout-first versions so its limited context
# cannot truncate FRONT/SIDE/BACK or the period/headwear constraints.
profile_prompt, front_prompt, side_prompt, back_prompt, scene_prompt, prop_prompts, front_edit_prompt = _build_compact_character_render_prompts(
profile_prompt, front_prompt, side_prompt, back_prompt, scene_prompt, prop_prompts = _build_compact_character_render_prompts(
name, book, sheet, book_profile, visual_style,
profile_direction or portrait_master,
reference_direction or sheet_master,
@ -3657,7 +3637,7 @@ async def character_generate_consistent_images(request: Request):
result = _comfyui_run_consistent_images(
comfy_url, _bundled_consistent_comfyui_workflow(), identity_image,
profile_prompt, front_prompt, side_prompt, back_prompt, scene_prompt, prop_prompts,
generation_key, front_edit_prompt=front_edit_prompt,
generation_key,
progress=lambda elapsed: (_cij_check_cancelled(job_id), _cij_set(job_id)),
)
result = _consistent_image_result_metadata(sheet, result)

View File

@ -226,16 +226,18 @@ class TurnaroundIdentityTests(unittest.TestCase):
from routes.conversation import _build_compact_character_render_prompts
return _build_compact_character_render_prompts("Test", "Buch", sheet, {}, "cinematic", "", "")
def test_each_view_is_drawn_from_the_portrait_as_the_reference_person(self):
_, _front_triptych, side, back, _scene, _props, front_edit = self.prompts(clothing="Wollrock")
for view in (front_edit, side, back):
self.assertIn("reference image exactly", view)
self.assertIn("front view", front_edit)
self.assertIn("side profile", side)
self.assertIn("directly behind", back)
def test_the_three_views_are_generated_as_one_triptych(self):
"""Per-view edit renders returned three front views and copied the pose guide into the costume."""
_portrait, triptych, side, back, *_rest = self.prompts(clothing="Wollrock")
self.assertIn("THREE-PANEL ORTHOGRAPHIC CHARACTER TURNAROUND", triptych)
self.assertIn("LEFT THIRD", triptych)
self.assertIn("CENTER THIRD", triptych)
self.assertIn("RIGHT THIRD", triptych)
for unused in (side, back):
self.assertIn("Unused", unused)
def test_prop_prompts_are_object_only(self):
*_, props, _front_edit = self.prompts(clothing="Rüstung", inventory="Schwert")
*_, props = self.prompts(clothing="Rüstung", inventory="Schwert")
self.assertTrue(props)
for prompt in props:
for person_word in ("scalp", "expression", "CLOTHING LOCK", "hairstyle"):