¿Qué hace handdrawn-3d?
Modelas el sujeto en 3D con unos pocos sólidos simples, y el kit lo dibuja a mano. Las caras reciben tres tonos planos de marcador
desde una luz en la esquina superior izquierda. Las caras vecinas del mismo tono se fusionan en un solo lavado. El contorno se
entinta con grosor y los pliegues más finos, con líneas temblorosas y que se estrechan, sobre papel crema. La escena se dibuja una vez por fotograma
con t dando una vuelta completa. La salida es un SVG animado en bucle: los fotogramas se cambian con CSS, sin script,
por lo que funciona en navegadores y etiquetas <img>. El texto usa una fuente dibujada a mano incorporada con soporte completo para vietnamita.
Kit: assets/kit.js · renderizador: assets/render.mjs · todas las funciones y opciones: references/kit.md (léelo
antes de escribir una escena) · ejemplos resueltos: examples/ (taza, tetera, tostadora, molino: lee el más cercano).
Configuración (una vez)
La exportación PNG/GIF necesita @resvg/resvg-js. Si render.mjs dice que falta, ejecuta npm install en la carpeta de esta habilidad.
El SVG no necesita nada.
Antes de dibujar
- ¿Se puede dibujar? Si el tema es abstracto ("esperanza", "productividad"), no dibujes algo vago. Sugiere una o dos escenas concretas que lo representen, luego dibuja la que el usuario elija.
- Diagramas, gráficos, UI, botones, cosas interactivas: no es para esta habilidad.
- ¿Qué se mueve? Cada imagen anima el objeto en sí mismo: la cámara nunca se mueve. Elige uno o dos movimientos que pertenezcan al tema. Una tapa de tetera tintinea y echa vapor. La tostada sale disparada. Las aspas de un molino giran. Un ventilador da vueltas. La cola de un gato se menea. Una lámpara cabecea. Una planta se mece. Si el usuario nombra un movimiento, usa ese. Si nada puede moverse de forma plausible, usa vapor, un suave cabeceo o un balanceo, y la propia ebullición de las líneas.
Flujo de trabajo
- Partes. Enumera las partes reales del sujeto y cómo se construyen a partir de sólidos. Escríbelo como una tabla en un comentario al principio de la escena: parte · llamada · tamaño/posición · color. Modela a partir de las partes reales (bordes, tapas, perillas, asas, patas, ranuras, bisagras), no una masa con un nombre. Consulta las proporciones que no te queden claras.
- Modelarlo en
scene.js(mundo: x derecha, y atrás, z arriba, suelo z = 0; unidades tipo mm; el encuadre es automático):- cosas redondas que se podrían tornear →
k.lathe(los recipientes llevanwall:),k.cylinder,k.sphere - cosas con formas de caja →
k.box(round:para esquinas suaves de electrodomésticos), contornos extraños →k.extrude - asas, picos, tuberías, patas, cables, tallos →
k.tube, comenzando dentro del cuerpo (se corta en la superficie) - contenido de un recipiente →
{ in: container }; marcas planas en una cara →k.panel/k.linecon{ on: solid } - gira todo el sujeto con
k.group({ rotate: { z: 45 } }, …)cuando su "vista lateral" deba mirar al espectador (el pico de una tetera a la izquierda y el asa a la derecha)
- cosas redondas que se podrían tornear →
- Animar con
t∈ [0, 1): las partes móviles van en unk.group({ about, rotate, at }, …)impulsado pork.wave(),k.pulse()o360 * t. Cada movimiento debe volver a donde empezó ent = 1. - Renderizar y mirar:
Abrenode <skill>/assets/render.mjs scene.js -o out/scene.svg --png out/scene.png --strip --thumbout/scene.png(un fijo) yout/scene-frames.png(6 fotogramas a lo largo del bucle) con la herramienta Leer y míralos. Añade--gridpara leer las coordenadas de la hoja. Revisaout/scene-128.png: debe seguir reconociéndose como el sujeto a 128 px. - Corregir y volver a renderizar. Revisa:
- ¿Se reconoce el sujeto de un vistazo? El contorno primero.
- Nada dibujado en el orden incorrecto: una parte que se ve a través de un cuerpo, una parte lejana sobre una cercana, contenido
oculto o que sobresale. Corrígelo con las reglas de Cómo se ordenan los sólidos en
references/kit.md: sólidos separados,in:,on:, modela solo lo que sobresale de una abertura, ylayercomo último recurso. - Las partes tocan lo que las sostiene. Nada flota a menos que deba hacerlo (una tapa que salta).
- El movimiento se entiende en la tira de fotogramas y el bucle no tiene saltos.
- Las etiquetas están en los márgenes y no cruzan el dibujo más de lo necesario.
Repite los pasos 4 y 5 hasta que esté bien (normalmente de 2 a 4 rondas). Renderiza la versión final sin
--grid.
Nunca entregues sin haber mirado el fijo y la tira de fotogramas. Entrega la ruta del SVG (la imagen animada),
más el PNG, y muestra el PNG. Añade --gif out/scene.gif cuando el usuario quiera un GIF o el SVG tenga que
ir a algún sitio que no reproduzca animación SVG (aplicaciones de chat, algunos clientes de correo).
Reglas de estilo
- Un solo aspecto: tinta + tonos planos de marcador sobre papel. Sin degradados, brillos, desenfoques, trucos de opacidad, SVG
<text>o SVG sin formato. Usa solo el kit. - Color: nombres de paleta (consulta
references/kit.md), de 3 a 5 por imagen más la tinta. Los nombres de paleta quedan mejor; cualquier hexadecimal se suaviza. Los tonos claro, medio y oscuro se obtienen automáticamente de cada relleno. No finjas sombreado con sólidos adicionales. - Cámara: isométrica por defecto. Cambia
sheet.camerasolo por una razón. Una inclinación más baja (20–25) y una guiñada de 25–35 crean una "toma de producto" más amigable para cosas altas (torres, botellas, lámparas, personas). - Simple y contundente: unos pocos sólidos limpios se leen mejor que muchos pequeños y complicados. Las piezas pequeñas (perillas, patas, botones) son lo que lo hace parecer real: agrégalas.
- Ponlo sobre el suelo: las sombras son automáticas. Algo sobre una tabla, mesa o platillo proyecta su sombra sobre él. Una tabla, bandeja, montículo o mesa debajo de un objeto pequeño ayuda a que se sitúe en el espacio.
- Texto:
k.title(...)opcional en la esquina superior izquierda, más notas cortas en los márgenes en minúscula (k.label) que apunten a puntos 3D. Escríbelas en el idioma del usuario. Sin cuadros ni bocadillos sobre el dibujo. - Hoja: 1000×1000 por defecto. Para escenas anchas usa
export const sheet = { width: 1200, height: 900 }.--transparentelimina el papel. - Tamaño de animación: 16 fotogramas a 12 fps (un bucle de 1.3 s) por defecto; un SVG típico pesa entre 0.5 y 1 MB. Para un movimiento
lento usa
sheet.fps = 8. Manténframes≤ 24.
Plantilla de escena
// Una tetera sobre un salvamanteles; la tapa tintinea.
// parte llamada tamaño / ubicación color
// salvamanteles cilindro r 120, h 10 madera
// cuerpo torno { smooth } r 55→94→50, h 118, sobre el salvamanteles turquesa
// pico tubo [14, 7] desde dentro del cuerpo, hacia la izquierda turquesa
// asa tubo 8 ambos extremos dentro del cuerpo, lado derecho turquesa
// tapa+perilla torno + esfera en un grupo articulado en su borde trasero turquesa, ocre
export const sheet = { seed: 3 };
export function draw(k, t = 0) {
k.cylinder([0, 0, 0], 120, 10, { fill: 'wood' });
k.group({ rotate: { z: 45 } }, () => { // pico a la izquierda de la pantalla, asa a la derecha
k.lathe([0, 0, 10], [[55, 0], [78, 10], [94, 45], [90, 85], [68, 112], [50, 118]], { fill: 'teal', smooth: true });
k.tube([[-78, 0, 50], [-112, 0, 66], [-128, 0, 100], [-146, 0, 118]], [14, 7], { fill: 'teal' });
k.tube([[70, 0, 112], [112, 0, 108], [122, 0, 72], [84, 0, 40]], 8, { fill: 'teal' });
const hop = Math.max(0, k.wave(0, 2 * t)); // 0..1, dos saltos por bucle
k.group({ about: [44, 0, 128], rotate: { y: -18 * hop }, at: [0, 0, 16 * hop] }, () => {
k.lathe([0, 0, 127], [[52, 0], [50, 5], [30, 13], [8, 18], [0, 19]], { fill: 'teal', smooth: true });
k.sphere([0, 0, 152], 10, { fill: 'ochre' });
});
k.steam([-150, 0, 125], { h: 90, n: 2, spread: 18 });
});
k.label('tapa tintineante', { to: [0, 0, 140], side: 'right' });
}