feat(gamedev): comfyui_build_sprite_from_sketch_workflow — boceto→sprite vía ControlNet
Tercer eje del catálogo gamedev-2d: partir del DIBUJO del dev. Recibe un boceto/lineart + un prompt de qué es y construye un workflow txt2img guiado por ControlNet (lineart/scribble/canny) que pinta el sprite conservando la forma dibujada. Distinto de los builders txt2img (inventan la forma desde texto) y de asset_variant img2img (reescribe una imagen ya pintada conservando forma+color): aquí el dev marca la silueta y la IA pone material/color/acabado, conservando solo la forma. Función pura (API format). Compone comfyui_build_txt2img_workflow + comfyui_inject_controlnet + comfyui_inject_lora; el único código propio es el helper que interpone el preprocesador (LineArt/Scribble/Canny) entre el boceto y el ControlNet, análogo a _inject_image_scale del hermano asset_variant. control_type selecciona preprocesador y modelo CN emparejado; controlnet_name y preprocess dan override para degradar al modelo disponible. Gotcha documentado: el server 8GB solo tiene modelos CN SD1.5 canny/depth/openpose — para lineart/scribble usar override a canny o control_type=canny (pendiente humano descargar los modelos lineart/scribble dedicados). Verificación: tests offline verdes (cableado txt2img guiado, 3 control_types, clamps, errores). E2E real GPU SD1.5: boceto del goblin → CannyEdgePreprocessor → ControlNet canny → sprite que respeta pose/orejas/hombrera/lanza/espada del dibujo (prompt_id ea6fc372, edge corr 0.545, luminance corr -0.19 confirmando repintado). Report en reports/0182. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
---
|
||||
name: comfyui_build_sprite_from_sketch_workflow
|
||||
kind: function
|
||||
lang: py
|
||||
domain: ml
|
||||
purity: pure
|
||||
version: 1.0.0
|
||||
signature: "def comfyui_build_sprite_from_sketch_workflow(sketch_image: str, subject: str, *, control_type: str = \"lineart\", checkpoint: str = \"dreamshaper_8.safetensors\", style: str = \"game asset, clean, centered\", strength: float = 0.8, size: int = 512, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, preprocess: bool = True, controlnet_name: str | None = None, negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"sprite_from_sketch\") -> dict"
|
||||
description: "Construye el dict (API format) del workflow ComfyUI que PINTA un sprite a partir del BOCETO del dev guiado por ControlNet: recibe el dibujo tosco (boceto, lineart o garabato) + un prompt de QUE es, y genera un sprite en estilo de juego que CONSERVA la forma dibujada. Es el tercer eje del catalogo gamedev-2d, distinto de txt2img (enemy_creature, item_icon: inventan la forma desde texto en blanco) y de img2img (asset_variant: reescribe una imagen YA pintada): aqui el dev marca la silueta con su dibujo y la IA pone material/color/acabado. Mecanismo: txt2img base (ruido, denoise 1.0) condicionado por un ControlNet atado al mapa de lineas del boceto. control_type elige el preprocesador (LineArtPreprocessor / ScribblePreprocessor / CannyEdgePreprocessor) y, por defecto, el modelo CN emparejado. Compone comfyui_build_txt2img_workflow + comfyui_inject_controlnet + comfyui_inject_lora (estilo opcional); el unico codigo propio es el helper que interpone el preprocesador entre el boceto y el ControlNet. Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
|
||||
tags: [comfyui, ml, gamedev-2d, controlnet, sketch, lineart, scribble, canny, sprite, asset-transform, stable-diffusion, workflow]
|
||||
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_inject_controlnet_py_ml, comfyui_inject_lora_py_ml]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
params:
|
||||
- name: sketch_image
|
||||
desc: "Nombre del archivo del boceto dentro de la carpeta input/ del servidor ComfyUI (lo carga el nodo LoadImage). Subelo antes con POST /upload/image o copialo a ~/ComfyUI/input/. Puede ser un boceto a lapiz, un lineart limpio o un garabato; el preprocesador lo normaliza a un mapa de lineas. No puede estar vacio."
|
||||
- name: subject
|
||||
desc: "Descripcion de QUE representa el boceto, para que la IA sepa que pintar sobre la forma (ej. 'armored knight', 'treasure chest', 'fire goblin'). Es el prompt positivo: NO inventa la silueta (eso lo fija el ControlNet), pone material, color y acabado. No puede estar vacio."
|
||||
- name: control_type
|
||||
desc: "Preprocesador y modelo ControlNet a usar. 'lineart' (default, LineArtPreprocessor) para dibujos a lapiz/tinta; 'scribble' (ScribblePreprocessor) para garabatos sueltos con mas libertad a la IA; 'canny' (CannyEdgePreprocessor) para bocetos con bordes nitidos y es el unico cuyo modelo CN dedicado esta instalado en el server 8GB actual (ver Gotchas). keyword-only."
|
||||
- name: checkpoint
|
||||
desc: "Checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto. keyword-only."
|
||||
- name: style
|
||||
desc: "Descriptor de estilo que mantiene coherentes los sprites de un set (ej. 'game asset, clean, centered', 'dark fantasy creature', 'pixel art'). Mismo style + checkpoint + (lora) para todos los sprites del mismo juego. keyword-only."
|
||||
- name: strength
|
||||
desc: "Fuerza del ControlNet (cuanto respeta el boceto). 0.0 = ignora el dibujo (txt2img puro); 1.0 = se ciñe estrictamente a las lineas; 0.8 (recomendado) deja algo de margen a la IA. Se clampa a [0.0, 2.0]. keyword-only."
|
||||
- name: size
|
||||
desc: "Lado en px del sprite generado (EmptyLatentImage size x size). El preprocesador normaliza el boceto a esta resolucion. 512 por defecto (SD1.5). keyword-only."
|
||||
- name: seed
|
||||
desc: "Semilla del KSampler. keyword-only."
|
||||
- name: lora
|
||||
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors'). None = sin LoRA. keyword-only."
|
||||
- name: lora_strength
|
||||
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
|
||||
- name: preprocess
|
||||
desc: "Si True (default), inserta el preprocesador del control_type entre el boceto y el ControlNet (recomendado: el boceto crudo casi nunca es un mapa de control limpio). Si False, el boceto se pasa DIRECTO al ControlNet como mapa de lineas (util solo cuando sketch_image YA es un lineart blanco-sobre-negro limpio). keyword-only."
|
||||
- name: controlnet_name
|
||||
desc: "Override explicito del modelo ControlNet en models/controlnet. None = usa el modelo emparejado al control_type. Pasa 'control_v11p_sd15_canny_fp16.safetensors' aqui para degradar lineart/scribble al modelo canny disponible en el server actual (ver Gotchas). keyword-only."
|
||||
- name: negative
|
||||
desc: "Prompt negativo. None usa el negativo por defecto pensado para sprites (una figura, fondo limpio, sin texto). keyword-only."
|
||||
- name: steps
|
||||
desc: "Pasos de sampling del KSampler. keyword-only."
|
||||
- name: cfg
|
||||
desc: "Classifier-free guidance scale. keyword-only."
|
||||
- name: sampler_name
|
||||
desc: "Nombre del sampler (ej. 'dpmpp_2m', 'euler'). keyword-only."
|
||||
- name: scheduler
|
||||
desc: "Scheduler del sampler (ej. 'karras', 'normal'). keyword-only."
|
||||
- name: filename_prefix
|
||||
desc: "Prefijo del archivo de salida en SaveImage. keyword-only."
|
||||
output: "dict en API format listo para comfyui_submit_workflow: txt2img base (CheckpointLoaderSimple '4', EmptyLatentImage '5', CLIPTextEncode '6'/'7', KSampler '3' denoise 1.0, VAEDecode '8', SaveImage '9') + rama ControlNet (LoadImage del boceto -> [Preprocessor del control_type si preprocess] -> ControlNetApply -> KSampler.positive, con ControlNetLoader del modelo CN) + LoraLoader si lora. Es UN sprite; varios objetos del mismo set -> llamar por subject/sketch_image con el mismo style/checkpoint/(lora)."
|
||||
tested: false
|
||||
file_path: python/functions/ml/comfyui_build_sprite_from_sketch_workflow.py
|
||||
---
|
||||
|
||||
Construye el dict (API format) del workflow ComfyUI que **pinta un sprite a partir del
|
||||
dibujo del dev**, guiado por ControlNet. Es el **tercer eje** del catálogo `gamedev-2d`:
|
||||
|
||||
| Eje | Builders | Punto de partida |
|
||||
|---|---|---|
|
||||
| **txt2img** | `enemy_creature`, `item_icon`, `ui_hud`... | TEXTO en blanco — la IA inventa la forma desde ruido |
|
||||
| **img2img** | `asset_variant` | una imagen YA pintada — la reescribe (material/tier) conservando composición |
|
||||
| **sketch→ControlNet** | **este builder** | el DIBUJO TOSCO del dev — lo pinta conservando la silueta dibujada |
|
||||
|
||||
El dolor real que cubre: el dev hace un boceto/lineart de un personaje u objeto y quiere que
|
||||
la IA lo pinte en estilo de juego **manteniendo la forma**. El dev marca la silueta; la IA
|
||||
pone material, color y acabado.
|
||||
|
||||
Mecanismo: es `txt2img` (arranca de ruido, `EmptyLatentImage`, `denoise 1.0`) pero su
|
||||
condicionamiento positivo pasa por un `ControlNetApply` atado al mapa de líneas extraído del
|
||||
boceto. El resultado tiene la **silueta y estructura del dibujo**, pintadas con el prompt de
|
||||
`subject` + `style`.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from ml.comfyui_build_sprite_from_sketch_workflow import comfyui_build_sprite_from_sketch_workflow
|
||||
|
||||
# Boceto 'knight_sketch.png' ya subido al input/ del servidor (POST /upload/image).
|
||||
wf = comfyui_build_sprite_from_sketch_workflow(
|
||||
"knight_sketch.png",
|
||||
"armored knight, fantasy hero",
|
||||
control_type="canny", # canny = preproc + modelo, todo instalado hoy
|
||||
style="game asset, clean, centered, painted",
|
||||
strength=0.85,
|
||||
size=512,
|
||||
seed=123,
|
||||
)
|
||||
# wf -> comfyui_submit_workflow(wf) -> comfyui_wait_result(prompt_id)
|
||||
# El sprite resultante conserva la silueta del boceto, repintada en estilo de juego.
|
||||
```
|
||||
|
||||
Probado end-to-end (27/06/2026): boceto del goblin → `CannyEdgePreprocessor` → ControlNet
|
||||
canny → sprite que respeta pose, orejas, hombrera, lanza dentada y espada del dibujo.
|
||||
`prompt_id ea6fc372-0467-4b4e-9927-069419633eab`, salida `sprite_from_sketch_test_00001_.png`.
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando el dev TIENE un dibujo (boceto, lineart, garabato) y quiere que la IA lo pinte
|
||||
respetando la forma — no inventar desde cero (`txt2img`) ni reescribir un asset ya pintado
|
||||
(`img2img` / `asset_variant`).
|
||||
- Para iterar diseño: dibujas la silueta a mano, la IA prueba 5 acabados distintos (varía
|
||||
`seed`/`style`/`lora`) sobre la MISMA forma.
|
||||
- Elige `control_type`: `lineart` para dibujos a lápiz/tinta, `scribble` para garabatos sueltos
|
||||
(más libertad), `canny` para bocetos con bordes nítidos (y único 100% instalado hoy).
|
||||
- Sube el set de un juego usando el mismo `style` + `checkpoint` + (`lora`) para coherencia.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **El server 8GB lowvram actual SOLO tiene los modelos ControlNet SD1.5 `canny` / `depth` /
|
||||
`openpose`.** Los modelos dedicados `control_v11p_sd15_lineart_fp16` y
|
||||
`control_v11p_sd15_scribble_fp16` NO están instalados (pendiente humano descargarlos a
|
||||
`~/ComfyUI/models/controlnet/`). Mientras tanto, para `control_type="lineart"`/`"scribble"`:
|
||||
o pasa `controlnet_name="control_v11p_sd15_canny_fp16.safetensors"` como override (el modelo
|
||||
canny tolera mapas de líneas de cualquier preprocesador, con algo menos de fidelidad que el
|
||||
dedicado), o usa `control_type="canny"` que funciona out-of-the-box. Los **preprocesadores**
|
||||
(LineArt/Scribble/Canny) SÍ están todos instalados (custom node `comfyui_controlnet_aux`).
|
||||
- La función es **pura**: NO consulta el servidor ni valida que `sketch_image`, `checkpoint`,
|
||||
`controlnet_name` o `lora` existan. Si el modelo CN falta, ComfyUI rechaza el workflow con
|
||||
HTTP 400 al enviarlo (`comfyui_submit_workflow` propaga el detalle por nodo). Valida con
|
||||
`/object_info` antes de enviar a un server desconocido.
|
||||
- `preprocess=False` solo tiene sentido si `sketch_image` YA es un mapa de líneas limpio
|
||||
(blanco-sobre-negro). Con un boceto a lápiz crudo y `preprocess=False`, el ControlNet recibe
|
||||
una foto, no líneas, y el resultado se degrada.
|
||||
- `strength` alto (~1.0) se ciñe tanto al boceto que puede arrastrar artefactos del dibujo;
|
||||
~0.8 deja a la IA limpiar. `strength=0` desactiva el ControlNet (vuelve a txt2img puro).
|
||||
- Es `txt2img` guiado (genera desde ruido respetando líneas), NO `img2img`: el sprite NO hereda
|
||||
los colores del boceto, solo su FORMA. Para conservar también los colores de una imagen ya
|
||||
pintada, usa `comfyui_build_asset_variant_workflow` (img2img).
|
||||
@@ -0,0 +1,331 @@
|
||||
"""Construye el workflow ComfyUI que PINTA un sprite a partir del BOCETO del dev (ControlNet).
|
||||
|
||||
Eje distinto al de los builders gamedev hermanos:
|
||||
|
||||
- txt2img (enemy_creature, item_icon, ui_hud...) -> parte de TEXTO en blanco: la IA
|
||||
inventa la forma desde ruido. No hay dibujo de partida.
|
||||
- img2img (asset_variant) -> parte de una imagen YA PINTADA y la reescribe (material,
|
||||
paleta, tier) conservando la composicion via denoise medio.
|
||||
- sketch -> ControlNet (ESTE builder) -> parte del DIBUJO TOSCO del dev (un boceto,
|
||||
un lineart a lapiz, un garabato) y lo PINTA en estilo de juego RESPETANDO las lineas
|
||||
dibujadas. El dev marca la forma; la IA pone el material, el color y el acabado.
|
||||
|
||||
Es txt2img guiado por ControlNet: el KSampler arranca de ruido (EmptyLatentImage, denoise
|
||||
1.0, como txt2img) pero su condicionamiento positivo pasa por un ControlNetApply que lo
|
||||
ata al mapa de lineas extraido del boceto. El resultado tiene la SILUETA y la estructura
|
||||
del dibujo del dev, pintadas con el prompt de `subject` + `style`.
|
||||
|
||||
Cableado:
|
||||
|
||||
CheckpointLoaderSimple -> [LoraLoader opcional] -> KSampler.model
|
||||
EmptyLatentImage ----------------------------------> KSampler.latent (ruido, txt2img)
|
||||
CLIPTextEncode(positive) -> ControlNetApply.conditioning
|
||||
LoadImage(boceto) -> [Preprocessor lineart/scribble/canny] -> ControlNetApply.image
|
||||
ControlNetLoader(modelo CN) -> ControlNetApply.control_net
|
||||
ControlNetApply -> KSampler.positive
|
||||
KSampler -> VAEDecode -> SaveImage
|
||||
|
||||
Compone (registry-first, sin reescribir nada):
|
||||
- comfyui_build_txt2img_workflow -> base txt2img (ruido + KSampler denoise 1.0)
|
||||
- comfyui_inject_controlnet -> rama LoadImage + ControlNetLoader + ControlNetApply
|
||||
repuntando KSampler.positive
|
||||
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia con el set)
|
||||
|
||||
El unico codigo propio es `_inject_preprocessor`: inserta el nodo preprocesador
|
||||
(LineArtPreprocessor / ScribblePreprocessor / CannyEdgePreprocessor) entre el LoadImage
|
||||
del boceto y el ControlNetApply.image, de modo que el ControlNet reciba un MAPA DE LINEAS
|
||||
limpio en lugar del boceto crudo. Es el analogo a `_inject_image_scale` del builder
|
||||
hermano comfyui_build_asset_variant_workflow (mismo patron de helper privado especifico
|
||||
del builder).
|
||||
|
||||
`control_type` selecciona el preprocesador y, por defecto, el modelo ControlNet emparejado:
|
||||
|
||||
control_type preprocesador (extrae lineas) modelo CN ideal
|
||||
----------- ----------------------------- ---------------------------------------
|
||||
lineart LineArtPreprocessor control_v11p_sd15_lineart_fp16.safetensors
|
||||
scribble ScribblePreprocessor control_v11p_sd15_scribble_fp16.safetensors
|
||||
canny CannyEdgePreprocessor control_v11p_sd15_canny_fp16.safetensors
|
||||
|
||||
`canny` se elige cuando el boceto tiene bordes nitidos; `lineart` para dibujos a lapiz/tinta;
|
||||
`scribble` para garabatos sueltos donde se quiere que la IA tenga mas libertad de interpretacion.
|
||||
|
||||
GOTCHA del servidor 8GB lowvram actual: SOLO estan instalados los modelos ControlNet SD1.5
|
||||
canny / depth / openpose. Los modelos dedicados control_v11p_sd15_lineart_fp16 y
|
||||
control_v11p_sd15_scribble_fp16 NO estan presentes (pendiente humano descargarlos). Mientras
|
||||
tanto: usa `control_type="canny"` (preprocesador + modelo, todo instalado), o pasa
|
||||
`controlnet_name="control_v11p_sd15_canny_fp16.safetensors"` como override para lineart/scribble
|
||||
(el modelo canny tolera mapas de lineas de cualquier preprocesador, con algo menos de fidelidad
|
||||
que el modelo dedicado). Los PREPROCESADORES (LineArt/Scribble/Canny) SI estan todos instalados
|
||||
(custom node comfyui_controlnet_aux).
|
||||
|
||||
class_types/inputs verificados contra /object_info del servidor (8GB lowvram):
|
||||
CheckpointLoaderSimple, EmptyLatentImage, CLIPTextEncode, KSampler, VAEDecode, SaveImage,
|
||||
LoadImage, ControlNetLoader, ControlNetApply, LineArtPreprocessor, ScribblePreprocessor,
|
||||
CannyEdgePreprocessor, LoraLoader.
|
||||
|
||||
Funcion pura: sin red, sin I/O. No muta dicts de entrada (copia profunda al insertar el
|
||||
preprocesador). NO valida que sketch_image/checkpoint/controlnet_name/lora existan en el
|
||||
servidor (eso es responsabilidad de comfyui_validate_workflow antes de enviar). Determinista
|
||||
para los mismos argumentos.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
# Mapa control_type -> (clase del preprocesador, modelo ControlNet ideal emparejado).
|
||||
# El preprocesador extrae el mapa de lineas del boceto; el modelo lo aplica al
|
||||
# condicionamiento. Ver el GOTCHA del modulo: en el server 8GB solo canny tiene
|
||||
# ambos lados instalados; lineart/scribble requieren override del modelo o descarga.
|
||||
_CONTROL_MAP = {
|
||||
"lineart": ("LineArtPreprocessor", "control_v11p_sd15_lineart_fp16.safetensors"),
|
||||
"scribble": ("ScribblePreprocessor", "control_v11p_sd15_scribble_fp16.safetensors"),
|
||||
"canny": ("CannyEdgePreprocessor", "control_v11p_sd15_canny_fp16.safetensors"),
|
||||
}
|
||||
|
||||
# Negativo por defecto para un sprite pintado desde boceto: UNA figura entera, bien
|
||||
# formada, fondo limpio, sin texto/marcas ni objetos extra. El ControlNet ya impone la
|
||||
# forma; el negativo solo limpia ruido tipico de SD1.5.
|
||||
_SKETCH_NEGATIVE = (
|
||||
"blurry, lowres, deformed, disfigured, bad anatomy, extra limbs, "
|
||||
"duplicate, multiple subjects, text, watermark, signature, logo, "
|
||||
"cluttered background, cropped, cut off, out of frame, jpeg artifacts"
|
||||
)
|
||||
|
||||
|
||||
def _inject_preprocessor(workflow: dict, *, control_type: str, resolution: int) -> dict:
|
||||
"""Inserta un nodo preprocesador entre el LoadImage del boceto y el ControlNetApply.
|
||||
|
||||
Tras comfyui_inject_controlnet, el ControlNetApply recibe la imagen directamente del
|
||||
LoadImage del boceto. Este helper interpone el preprocesador correspondiente al
|
||||
control_type (LineArt/Scribble/Canny), que convierte el boceto crudo en un mapa de
|
||||
lineas limpio, y repunta ControlNetApply.image a la salida del preprocesador.
|
||||
|
||||
Pura: trabaja sobre una copia profunda. Determinista.
|
||||
|
||||
Raises:
|
||||
ValueError: si no se encuentra ControlNetApply, o si el ControlNetApply no recibe
|
||||
su imagen de un LoadImage (cableado inesperado).
|
||||
"""
|
||||
wf = copy.deepcopy(workflow)
|
||||
|
||||
apply_id = next(
|
||||
(nid for nid, n in wf.items() if n.get("class_type") == "ControlNetApply"), None
|
||||
)
|
||||
if apply_id is None:
|
||||
raise ValueError(
|
||||
"comfyui_build_sprite_from_sketch_workflow: no se encontro ControlNetApply "
|
||||
"para insertar el preprocesador"
|
||||
)
|
||||
|
||||
# Fuente de la imagen de control que hoy alimenta el ControlNetApply (el LoadImage).
|
||||
src = wf[apply_id]["inputs"].get("image")
|
||||
if not (isinstance(src, list) and len(src) == 2):
|
||||
raise ValueError(
|
||||
"comfyui_build_sprite_from_sketch_workflow: ControlNetApply.image no apunta a "
|
||||
"un nodo (cableado inesperado del boceto)"
|
||||
)
|
||||
|
||||
preproc_class = _CONTROL_MAP[control_type][0]
|
||||
inputs: dict = {"image": list(src), "resolution": int(resolution)}
|
||||
if control_type == "lineart":
|
||||
inputs["coarse"] = "disable"
|
||||
elif control_type == "canny":
|
||||
inputs["low_threshold"] = 100
|
||||
inputs["high_threshold"] = 200
|
||||
# scribble no tiene parametros extra mas alla de resolution.
|
||||
|
||||
numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
|
||||
preproc_id = str((max(numeric) + 1) if numeric else len(wf) + 1)
|
||||
wf[preproc_id] = {"class_type": preproc_class, "inputs": inputs}
|
||||
|
||||
# Repunta el ControlNetApply para que reciba el MAPA DE LINEAS preprocesado.
|
||||
wf[apply_id]["inputs"]["image"] = [preproc_id, 0]
|
||||
return wf
|
||||
|
||||
|
||||
def comfyui_build_sprite_from_sketch_workflow(
|
||||
sketch_image: str,
|
||||
subject: str,
|
||||
*,
|
||||
control_type: str = "lineart",
|
||||
checkpoint: str = "dreamshaper_8.safetensors",
|
||||
style: str = "game asset, clean, centered",
|
||||
strength: float = 0.8,
|
||||
size: int = 512,
|
||||
seed: int = 0,
|
||||
lora: str | None = None,
|
||||
lora_strength: float = 1.0,
|
||||
preprocess: bool = True,
|
||||
controlnet_name: str | None = None,
|
||||
negative: str | None = None,
|
||||
steps: int = 28,
|
||||
cfg: float = 7.0,
|
||||
sampler_name: str = "dpmpp_2m",
|
||||
scheduler: str = "karras",
|
||||
filename_prefix: str = "sprite_from_sketch",
|
||||
) -> dict:
|
||||
"""Construye el dict (API format) del workflow boceto->sprite guiado por ControlNet.
|
||||
|
||||
A partir del DIBUJO TOSCO del dev (boceto, lineart, garabato) + un prompt de QUE es,
|
||||
genera un sprite pintado en estilo de juego que CONSERVA la forma dibujada. Es txt2img
|
||||
(arranca de ruido) condicionado por un ControlNet atado al mapa de lineas del boceto.
|
||||
|
||||
Args:
|
||||
sketch_image: nombre del archivo del boceto dentro de la carpeta input/ del servidor
|
||||
ComfyUI (lo carga el nodo LoadImage). Subelo antes con POST /upload/image o
|
||||
copialo a ~/ComfyUI/input/. Puede ser un boceto a lapiz, un lineart limpio o un
|
||||
garabato; el preprocesador lo normaliza a un mapa de lineas. No puede estar vacio.
|
||||
subject: descripcion de QUE representa el boceto, para que la IA sepa que pintar
|
||||
sobre la forma (ej. "armored knight", "treasure chest", "fire goblin"). Es el
|
||||
prompt positivo: NO inventa la silueta (eso lo fija el ControlNet), pone material,
|
||||
color y acabado. No puede estar vacio.
|
||||
control_type: preprocesador y modelo ControlNet a usar. keyword-only.
|
||||
- "lineart" (default): LineArtPreprocessor. Para dibujos a lapiz/tinta.
|
||||
- "scribble": ScribblePreprocessor. Para garabatos sueltos; mas libertad a la IA.
|
||||
- "canny": CannyEdgePreprocessor. Para bocetos con bordes nitidos. Es el unico
|
||||
cuyo modelo CN dedicado esta instalado en el server 8GB actual (ver GOTCHA del
|
||||
modulo); funciona out-of-the-box.
|
||||
checkpoint: checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en
|
||||
8GB lowvram) por defecto. keyword-only.
|
||||
style: descriptor de estilo que mantiene coherentes los sprites de un set (ej.
|
||||
"game asset, clean, centered", "dark fantasy creature", "pixel art"). Mismo style
|
||||
+ checkpoint + (lora) para todos los sprites del mismo juego. keyword-only.
|
||||
strength: fuerza del ControlNet (cuanto respeta el boceto). 0.0 = ignora el dibujo
|
||||
(txt2img puro); 1.0 = se ciñe estrictamente a las lineas. 0.8 (recomendado) deja
|
||||
algo de margen a la IA para interpretar. Se clampa a [0.0, 2.0]. keyword-only.
|
||||
size: lado en px del sprite generado (EmptyLatentImage size x size). El preprocesador
|
||||
normaliza el boceto a esta resolucion. 512 por defecto (SD1.5). keyword-only.
|
||||
seed: semilla del KSampler. keyword-only.
|
||||
lora: LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_sd15.safetensors').
|
||||
None = sin LoRA. keyword-only.
|
||||
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only.
|
||||
preprocess: si True (default), inserta el preprocesador del control_type entre el
|
||||
boceto y el ControlNet (recomendado: el boceto crudo casi nunca es un mapa de
|
||||
control limpio). Si False, el boceto se pasa DIRECTO al ControlNet como mapa de
|
||||
lineas (util solo cuando sketch_image YA es un lineart blanco-sobre-negro limpio).
|
||||
keyword-only.
|
||||
controlnet_name: override explicito del modelo ControlNet en models/controlnet. None
|
||||
= usa el modelo emparejado al control_type segun _CONTROL_MAP. Pasa aqui
|
||||
'control_v11p_sd15_canny_fp16.safetensors' para degradar lineart/scribble al
|
||||
modelo canny disponible (ver GOTCHA del modulo). keyword-only.
|
||||
negative: prompt negativo. None usa el negativo por defecto pensado para sprites
|
||||
(una figura, fondo limpio, sin texto). keyword-only.
|
||||
steps, cfg, sampler_name, scheduler, filename_prefix: parametros de generacion.
|
||||
keyword-only.
|
||||
|
||||
Returns:
|
||||
dict en API format listo para comfyui_submit_workflow: txt2img base + rama ControlNet
|
||||
(LoadImage del boceto -> [Preprocessor si preprocess] -> ControlNetApply -> KSampler.
|
||||
positive) + LoRA de estilo opcional. Es UN sprite; varios objetos del mismo set ->
|
||||
llamar por `subject`/`sketch_image` con el mismo style/checkpoint/(lora).
|
||||
|
||||
Raises:
|
||||
ValueError: si sketch_image o subject estan vacios, o si control_type no es uno de
|
||||
'lineart' / 'scribble' / 'canny'.
|
||||
"""
|
||||
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||
from ml.comfyui_inject_controlnet import comfyui_inject_controlnet
|
||||
|
||||
if not sketch_image or not sketch_image.strip():
|
||||
raise ValueError(
|
||||
"comfyui_build_sprite_from_sketch_workflow: 'sketch_image' no puede estar vacio "
|
||||
"(el ControlNet necesita el boceto en input/)."
|
||||
)
|
||||
if not subject or not subject.strip():
|
||||
raise ValueError(
|
||||
"comfyui_build_sprite_from_sketch_workflow: 'subject' no puede estar vacio "
|
||||
"(la IA necesita saber QUE pintar sobre la forma del boceto)."
|
||||
)
|
||||
if control_type not in _CONTROL_MAP:
|
||||
raise ValueError(
|
||||
"comfyui_build_sprite_from_sketch_workflow: control_type "
|
||||
f"'{control_type}' invalido; usa uno de {sorted(_CONTROL_MAP)}."
|
||||
)
|
||||
|
||||
sketch_image = sketch_image.strip()
|
||||
subject = subject.strip()
|
||||
strength = max(0.0, min(2.0, float(strength)))
|
||||
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||
neg = _SKETCH_NEGATIVE if negative is None else negative
|
||||
cn_model = controlnet_name or _CONTROL_MAP[control_type][1]
|
||||
|
||||
# Prompt positivo: describe el sujeto y el estilo; la SILUETA la impone el ControlNet,
|
||||
# asi que aqui solo se empuja material/acabado y "single subject, full body".
|
||||
positive = (
|
||||
f"{subject}, {style}, single subject, full body, "
|
||||
"painted, polished, high detail, consistent design"
|
||||
)
|
||||
|
||||
wf = comfyui_build_txt2img_workflow(
|
||||
checkpoint,
|
||||
positive,
|
||||
neg,
|
||||
steps=steps,
|
||||
cfg=cfg,
|
||||
width=size,
|
||||
height=size,
|
||||
seed=seed,
|
||||
sampler_name=sampler_name,
|
||||
scheduler=scheduler,
|
||||
filename_prefix=filename_prefix,
|
||||
)
|
||||
|
||||
# Rama ControlNet: LoadImage(boceto) + ControlNetLoader + ControlNetApply, repuntando
|
||||
# KSampler.positive. Reutiliza el inyector encadenable del registry.
|
||||
wf = comfyui_inject_controlnet(wf, sketch_image, cn_model, strength=strength)
|
||||
|
||||
# Interpone el preprocesador del control_type entre el boceto y el ControlNet.
|
||||
if preprocess:
|
||||
wf = _inject_preprocessor(wf, control_type=control_type, resolution=size)
|
||||
|
||||
if lora:
|
||||
from ml.comfyui_inject_lora import comfyui_inject_lora
|
||||
|
||||
wf = comfyui_inject_lora(
|
||||
wf, lora, strength_model=lora_strength, strength_clip=lora_strength
|
||||
)
|
||||
|
||||
return wf
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
wf = comfyui_build_sprite_from_sketch_workflow(
|
||||
"knight_sketch.png",
|
||||
"armored knight, fantasy hero",
|
||||
control_type="lineart",
|
||||
style="game asset, clean, centered",
|
||||
strength=0.8,
|
||||
seed=7,
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"nodes": list(wf),
|
||||
"classes": sorted({n["class_type"] for n in wf.values()}),
|
||||
"control_type": "lineart",
|
||||
"cn_model": next(
|
||||
n["inputs"]["control_net_name"]
|
||||
for n in wf.values()
|
||||
if n["class_type"] == "ControlNetLoader"
|
||||
),
|
||||
"preprocessor": next(
|
||||
n["class_type"]
|
||||
for n in wf.values()
|
||||
if n["class_type"].endswith("Preprocessor")
|
||||
),
|
||||
"strength": next(
|
||||
n["inputs"]["strength"]
|
||||
for n in wf.values()
|
||||
if n["class_type"] == "ControlNetApply"
|
||||
),
|
||||
"positive": wf["6"]["inputs"]["text"],
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user