O que model-by-coding faz?
Construa um modelo 3D de um objeto físico a partir de fotografias, inteiramente em código, e depois transforme-o em um filme de desmontagem e um GIF. O pipeline é opinativo em uma questão: o modelo é verificado em relação às fotos em cada etapa, para que o filme mostre o objeto que foi fotografado, e não algo plausível.
Quando usar
- um produto de superfície dura (ventilador, luminária, alto-falante, câmera, ferramenta, eletrodoméstico) deve ser explicado ou mostrado em partes em um vídeo curto;
- o objeto existe fisicamente e pode ser fotografado de vários ângulos;
- a saída é um clipe de desmontagem em loop para um README, uma postagem ou um demo reel;
- um conjunto de fotos existente deve se tornar um modelo editável e re-renderizável — não uma malha escaneada.
Quando não usar
- são necessários assets fotorrealistas: use fotogrametria ou um scanner;
- o objeto é orgânico, macio ou deformável (tecido, plantas, rostos);
- não há fotografias disponíveis, ou o objeto só é visível em um vídeo;
- uma única imagem estática é suficiente — as etapas do HyperFrames são desnecessárias;
- o assunto é uma animação de texto/UI: este pipeline é sobre geometria.
Pipeline
| etapa | produzir | critério de aceitação |
|---|---|---|
| 1. Medir | uma dimensão de régua, uma tabela de proporções, um objeto DIM | proporções verificadas em ≥2 fotos, toda suposição marcada como inferida |
| 2. Modelar | uma cena Three.js construída em código, um bundle IIFE, a página do visualizador | estado montado não tem lacunas; p = 1 separa cada parte |
| 3. Verificar | seis folhas de comparação foto/renderização na mesma pose | proporções de silhueta e características dentro da tolerância em todas as seis |
| 4. Filmar | uma composição HyperFrames, relatório check, MP4 de entrega | ok: true, sem erros, duração corresponde a data-duration |
| 5. Cortar | um GIF em loop | começa e termina em poses estabilizadas, abaixo de ~1 MB |
Um orçamento realista para um objeto é de algumas horas a dois dias, a maior parte nas etapas 2–3. Pular a etapa 3 é o que transforma uma semana de modelagem em um modelo errado entregue no prazo.
Etapa 1 — Medir a partir das fotos
Colete seis poses (frente, lado verdadeiro, costas, costas 3/4, topo, base), escolha uma
dimensão grande como régua, meça-a com uma varredura radial e expresse
tudo mais como uma proporção dela. Conte características discretas (nervuras, pás, raios)
a partir de seu período angular, e não visualmente. Mantenha cada número em um objeto
DIM com um comentário nomeando sua foto de origem e se é medido ou
inferido; o tamanho absoluto do mundo real é uma decisão, não uma medição.
Profundidade: references/01-modeling.md §0–2.
Etapa 2 — Modelar em código
Um módulo de cena com uma API de estado puro: create, setView, setExplode,
setExplodeMap, setSpin, setHeadTilt, setLift, render, getAnchors,
resize, dispose. Convenção mundial: objeto voltado para +Z, base em y = 0, metros.
Construa carcaças como perfis de torno, a grade como fitas planas (não tubos redondos),
pás como folhas paramétricas e registre cada parte por meio de uma função que
armazena sua posição montada, deslocamento de explosão e deslocamento de âncora. Nomeie as
partes uma vez; o filme, rótulos, âncoras e som dependem desses nomes.
Profundidade: references/01-modeling.md §3–6.
Etapa 3 — Verificar em relação às fotos
Compile o bundle com esbuild, renderize um quadro por pose no Chrome headless e compare cada renderização com sua foto como uma folha lado a lado cortada para a caixa delimitadora do objeto. Leia proporções e posicionamento de partes como frações da própria caixa do objeto; não ajuste círculos a bordas em um fundo complexo — o ajuste se prende a arcos de fundo. Seis poses, algumas iterações cada.
Profundidade: references/03-verify.md.
Etapa 4 — Renderizar o filme
Construa a composição conforme o contrato do HyperFrames: um elemento raiz de composição
com data-duration, uma timeline GSAP pausada em window.__timelines["main"],
Three.js redesenhado apenas a partir de hf-seek (nunca de rAF/timers), mídia com seu
tempo próprio marcada como class="clip" com data-start/data-duration, um mix de áudio
como um clipe de áudio, texto com 28 px ou maior, e o bundle grande fornecido como
assets/vendor/*.min.js. Escreva a lista de cenas antes de animar; direcione câmera,
explosão e rótulos a partir de um único script do diretor para que as dicas de áudio possam
compartilhar suas constantes. Execute check, leia o relatório, depois render --quality delivery.
Profundidade: references/02-render.md.
Etapa 5 — Cortar o GIF
Escolha um segmento estável de 3–4 s (geralmente a parada explodida), corte-o com gif-master
(largura 480, 12 fps, loop) ou com o comando de paleta de dois passos do ffmpeg, e verifique
tamanho, contagem de quadros e loop antes de publicar. Profundidade:
references/04-gif.md,
scripts/make_gif.md.
Restrições rígidas
Estas são as regras que quebram o pipeline quando violadas; não são preferências de estilo.
- Contrato primeiro. Objeto voltado para +Z, base em y = 0, metros, câmera
target + dist * (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch)), yaw/pitch em graus. Todo script e o filme compartilham isso. - Estado puro. A API da cena deve renderizar o mesmo estado nos mesmos pixels.
Nenhum loop de animação, nenhum relógio de parede, nenhum
Math.random()em código do qual o quadro dependa. - Quadro = f(t). Câmera, explosão, rotação, rótulos, cards e som são funções do tempo do filme; nada mais.
- Contrato de composição.
data-composition-id,data-start,data-duration,data-width,data-heightno elemento raiz; timeline pausada registrada comowindow.__timelines["<id>"]; redesenho dentro dehf-seekusandoe.detail.time. - Clipes. Mídia com seu próprio início e duração (áudio, vídeo, inserções)
carrega
class="clip",data-start,data-duration; áudio adicionalmentedata-track-index. DOM animado pela timeline não precisa de atributos de clipe. - Legibilidade. Nada informacional abaixo de 28 px em 1080p (piso de tipo do verificador é 28 px = 2,6% do lado menor).
- Pesos fornecidos. three.js e GSAP como
.min.jsemassets/vendor/; fontes locais; nenhuma rede durante a renderização. - Scripts são genéricos. Nenhum nome de objeto, nenhum caminho absoluto, nenhum diretório pessoal
em
scripts/; cada script deve ser executado a partir de um checkout limpo. - Verificar comandos. Todo comando e parâmetro escritos em docs, scripts ou no filme deve ser executado uma vez contra as versões fixadas antes de ser confiável.
Roteamento de referência
| tarefa | leia |
|---|---|
| medição, proporções, perfis de torno, parâmetros de grade/lâmina, partes, explosão, âncoras | references/01-modeling.md |
| contrato de composição, diretor/storyboard, sincronia de áudio, comandos e saídas do HyperFrames | references/02-render.md |
| loop de captura de tela, folhas de comparação, leitura de ângulos, falha do ajuste de círculo | references/03-verify.md |
| cartão de operação do gif-master, créditos, receita de GIF do ffmpeg | references/04-gif.md |
| repositório, GitHub Release, PR da lista da comunidade, YouTube/X | references/05-publish.md |
| catálogo sintoma → causa → correção (iluminação, sombras, nervuras, tipo, bundles, Chrome) | references/06-gotchas.md |
Ferramentas fornecidas com este skill:
| arquivo | função |
|---|---|
scripts/view.html | visualizador de quadro único; pose e bundle a partir de parâmetros de consulta |
scripts/verify_sheet.py | folha lado a lado foto-vs-renderização (--photo --render --out --bbox) |
scripts/hf_read_check.py | resumir um relatório de verificação do HyperFrames; --fail-on-error para CI |
scripts/make_gif.md | cartão de operação para navegador/ffmpeg do GIF |
Sequência mínima executável
Os comandos são executados a partir da raiz do repositório contra o exemplo trabalhado em
examples/fan/; para um novo objeto, substitua src/scene.js, photos/ e
video/. Os arquivos de trabalho vão para %TEMP% para que o checkout permaneça limpo. Mantenha os
parâmetros — eles são os verificados. Execute no PowerShell.
$work = Join-Path $env:TEMP "mbc-verify"; New-Item -ItemType Directory -Force $work | Out-Null
# 1. compilar o bundle (modelo para um novo objeto; precisa do `three` instalado).
# O exemplo já fornece o bundle em examples/fan/video/assets/vendor/fan.bundle.min.js,
# então esta etapa pode ser pulada para o exemplo.
npx --yes [email protected] src/scene.js --bundle --format=iife --global-name=Scene `
--minify --target=chrome120 --outfile=dist/scene.bundle.min.js
# 2. capturar uma pose de verificação do exemplo (1920x1080, perfil novo por execução)
$chrome = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$png = Join-Path $work "front.png"
$udd = Join-Path $env:TEMP ("view-" + [guid]::NewGuid().ToString("N"))
& $chrome --headless=new --use-angle=d3d11 --window-size=1920,1080 `
--hide-scrollbars --virtual-time-budget=12000 `
--screenshot=$png --user-data-dir=$udd `
"file:///$(($PWD.Path).Replace('\','/'))/scripts/view.html?bundle=../examples/fan/video/assets/vendor/fan.bundle.min.js&yaw=-10&pitch=12&dist=0.85&ty=0.185&fov=30&noshadow=1"
# 3. comparar essa renderização com a fotografia frontal na mesma pose
python scripts/verify_sheet.py --photo examples/fan/reference/fan.jpg --render $png `
--out (Join-Path $work "front-sheet.png") --bbox "130,245,1195,1455"
# 4. verificar o filme contra o contrato do HyperFrames, depois ler o relatório
Set-Location examples/fan/video
# precisa de assets/audio/mix.wav (Release asset; veja examples/fan/README.md)
npx --yes [email protected] check --at 21.15,27.6,35.18
$report = Get-ChildItem .hyperframes/artifacts/checks/*.json |
Sort-Object LastWriteTime | Select-Object -Last 1
python ../../../scripts/hf_read_check.py $report.FullName --fail-on-error
npx --yes [email protected] snapshot --at 29.6
# 5. renderizar o master de entrega
npx --yes [email protected] render --quality delivery
Set-Location ../../..
# 6. cortar o GIF com ffmpeg (rota do navegador: scripts/make_gif.md)
$mp4 = (Get-ChildItem examples/fan/video/renders/*.mp4 | Sort-Object LastWriteTime | Select-Object -Last 1).FullName
ffmpeg -hide_banner -loglevel error -y `
-ss 29.6 -t 3.8 -i "$mp4" `
-vf "fps=12,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=sierra2_4a" `
-loop 0 (Join-Path $work "teardown.gif")
A etapa 3 imprime as duas caixas de corte; a execução verificada imprime
saved ...\front-sheet.png 1112x702 | photo (130, 245, 1195, 1455) | render (652, 264, 1252, 1080).
Saídas esperadas: check escreve
video/.hyperframes/artifacts/checks/<uuid>.json; snapshot escreve
video/.hyperframes/artifacts/snapshots/<uuid>/frame-00-at-29.6s.png; render
escreve video/renders/video_<YYYY-MM-DD>_<HH-MM-SS>.mp4 (cerca de 36 MB para 43,5 s
em 1920×1080/30 fps, aproximadamente 2,5 minutos de captura com uma GPU de hardware).
Requisitos
- Node.js (testado 22.19.0) com
npx; esbuild 0.24.2 via npx. - Python 3.11 (testado 3.11.1) com Pillow para as folhas; numpy opcional.
- ffmpeg no PATH (testado com uma build git de 2025-12-28).
- Chrome para capturas de tela headless (testado 157.0.8103.40); o caminho acima é o padrão do Windows.
- HyperFrames fixado em 0.9.36 via
npx --yes [email protected]. checkerenderusam Chrome headless com--browser-gpuno automático: GPU de hardware quando presente, software (SwiftShader) caso contrário;previewé mais suave com uma GPU.
Definição de pronto
- seis folhas de comparação são lidas de forma aceitável, com os desvios listados e explicados;
hf_read_check.py --fail-on-errorsai com 0 no relatório de verificação final;- o MP4 de entrega existe e sua duração é igual ao
data-durationda composição; - o GIF faz loop limpo e é pequeno o suficiente para publicar;
- todo comando neste skill foi executado uma vez em um checkout novo.