Que fait model-by-coding ?
Construisez un modèle 3D d'un objet physique à partir de photographies, entièrement en code, puis transformez-le en film de démontage et en GIF. Le pipeline est exigeant sur un point : le modèle est vérifié par rapport aux photos à chaque étape, de sorte que le film montre l'objet qui a été photographié plutôt que quelque chose de plausible.
Quand l'utiliser
- un produit à surface dure (ventilateur, lampe, enceinte, appareil photo, outil, appareil de cuisine) doit être expliqué ou présenté en pièces dans une courte vidéo ;
- l'objet existe physiquement et peut être photographié sous plusieurs angles ;
- le résultat est un clip de démontage en boucle pour un README, un post ou une démo ;
- un ensemble de photos existant doit devenir un modèle modifiable et re-rendu — pas un maillage scanné.
Quand ne pas l'utiliser
- des ressources photo-réalistes sont nécessaires : utilisez la photogrammétrie ou un scanner ;
- l'objet est organique, mou ou se déforme (tissu, plantes, visages) ;
- aucune photographie n'est disponible ou l'objet n'est visible que dans une vidéo ;
- une seule image fixe suffit — les étapes HyperFrames sont alors inutiles ;
- le sujet est une animation de texte/UI : ce pipeline concerne la géométrie.
Pipeline
| étape | produire | porte de vérification |
|---|---|---|
| 1. Mesurer | une dimension de règle, un tableau de ratios, un objet DIM | ratios croisés sur ≥2 photos, chaque hypothèse marquée déduite |
| 2. Modéliser | une scène Three.js codée, un bundle IIFE, la page de visualisation | l'état assemblé n'a pas de trous ; p = 1 sépare chaque pièce |
| 3. Vérifier | six planches photo/rendu en même pose | silhouettes et ratios de caractéristiques dans la tolérance sur les six |
| 4. Filmer | une composition HyperFrames, rapport check, MP4 de livraison | ok: true, aucune erreur, durée correspond à data-duration |
| 5. Couper | un GIF en boucle | commence et se termine sur des poses stables, sous ~1 Mo |
Un budget réaliste pour un objet est de quelques heures à deux jours, la majeure partie du temps dans les étapes 2–3. Sauter l'étape 3 transforme une semaine de modélisation en un mauvais modèle livré à l'heure.
Étape 1 — Mesurer à partir des photos
Collectez six poses (face, profil vrai, dos, dos 3/4, dessus, dessous), choisissez une
grande dimension comme règle, mesurez-la avec un balayage radial, et exprimez
tout le reste comme un ratio de celle-ci. Comptez les caractéristiques distinctes (nervures, pales, branches)
à partir de leur période angulaire plutôt qu'à l'œil. Gardez chaque nombre dans un objet DIM
avec un commentaire indiquant sa photo source et s'il est mesuré ou déduit ; la taille absolue
dans le monde réel est une décision, pas une mesure.
Approfondissement : references/01-modeling.md §0–2.
Étape 2 — Modéliser en code
Un module de scène avec une API à état pur : create, setView, setExplode,
setExplodeMap, setSpin, setHeadTilt, setLift, render, getAnchors,
resize, dispose. Convention monde : objet face à +Z, base à y = 0, mètres.
Construisez les boîtiers comme des profils de tour, la grille comme des rubans plats (pas de tubes ronds),
les pales comme des feuilles paramétriques, et enregistrez chaque pièce via une fonction qui
stocke sa position assemblée, son décalage d'explosion et son décalage d'ancrage. Nommez les
pièces une fois ; le film, les étiquettes, les ancres et le son utilisent tous ces noms.
Approfondissement : references/01-modeling.md §3–6.
Étape 3 — Vérifier par rapport aux photos
Compilez le bundle avec esbuild, rendez une image par pose dans Chrome sans tête, et comparez chaque rendu avec sa photo sous forme de planche côte à côte recadrée à la boîte englobante de l'objet. Lisez les proportions et le placement des pièces comme des fractions de la propre boîte de l'objet ; n'ajustez pas de cercles aux bords sur un fond chargé — l'ajustement s'accroche aux arcs de fond. Six poses, quelques itérations chacune.
Approfondissement : references/03-verify.md.
Étape 4 — Rendre le film
Construisez la composition selon le contrat HyperFrames : un élément de composition racine
avec data-duration, une timeline GSAP en pause à window.__timelines["main"],
Three.js redessiné uniquement depuis hf-seek (jamais depuis rAF/timers), des médias avec leur
propre timing marqués comme class="clip" avec data-start/data-duration, un mix audio
comme clip audio, du texte à 28 px ou plus, et le gros bundle vendu sous
assets/vendor/*.min.js. Écrivez la liste des plans avant d'animer ; pilotez la caméra, l'explosion
et les étiquettes à partir d'un script de réalisateur afin que les repères audio puissent partager ses
constantes. Exécutez check, lisez le rapport, puis render --quality delivery.
Approfondissement : references/02-render.md.
Étape 5 — Couper le GIF
Choisissez un segment stable de 3–4 s (généralement la tenue éclatée), coupez-le dans gif-master
(largeur 480, 12 ips, boucle) ou avec la commande de palette en deux passes de ffmpeg, et vérifiez
la taille, le nombre d'images et la boucle avant de publier. Approfondissement :
references/04-gif.md,
scripts/make_gif.md.
Contraintes strictes
Voici les règles qui brisent le pipeline lorsqu'elles sont violées ; ce ne sont pas des préférences de style.
- Contrat d'abord. Objet face à +Z, base à y = 0, mètres, caméra
target + dist * (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch)), yaw/pitch en degrés. Chaque script et le film le partagent. - État pur. L'API de la scène doit rendre le même état aux mêmes pixels.
Pas de boucle d'animation, pas d'horloge système, pas de
Math.random()dans le code dont dépend l'image. - Trame = f(t). Caméra, explosion, rotation, étiquettes, cartes et son sont tous des fonctions du temps du film ; rien d'autre.
- Contrat de composition.
data-composition-id,data-start,data-duration,data-width,data-heightsur l'élément racine ; timeline en pause enregistrée commewindow.__timelines["<id>"]; redessin danshf-seeken utilisante.detail.time. - Clips. Les médias avec leur propre début et durée (audio, vidéo, insertions)
portent
class="clip",data-start,data-duration; l'audio en plusdata-track-index. Le DOM animé par la timeline n'a pas besoin d'attributs de clip. - Lisibilité. Rien d'informatif en dessous de 28 px à 1080p (le seuil de type du vérificateur est 28 px = 2,6 % du petit côté).
- Poids vendus. three.js et GSAP comme
.min.jssousassets/vendor/; polices locales ; pas de réseau pendant le rendu. - Les scripts sont génériques. Pas de nom d'objet, pas de chemin absolu, pas de répertoire
personnel dans
scripts/; chaque script doit fonctionner à partir d'un checkout frais. - Commandes de vérification. Chaque commande et paramètre écrit dans les docs, les scripts ou le film doit être exécuté une fois contre les versions épinglées avant d'être fiable.
Routage des références
| tâche | lire |
|---|---|
| mesure, ratios, profils de tour, paramètres grille/pale, pièces, explosion, ancres | references/01-modeling.md |
| contrat de composition, storyboard/réalisateur, synchronisation audio, commandes et sorties HyperFrames | references/02-render.md |
| boucle de capture d'écran, planches de comparaison, lecture des angles, l'échec d'ajustement de cercle | references/03-verify.md |
| fiche d'opération gif-master, crédits, recette GIF ffmpeg | references/04-gif.md |
| dépôt, publication GitHub, PR de liste communautaire, YouTube/X | references/05-publish.md |
| catalogue symptôme → cause → correctif (éclairage, ombres, nervures, type, bundles, Chrome) | references/06-gotchas.md |
Outils fournis avec ce skill :
| fichier | rôle |
|---|---|
scripts/view.html | visualiseur d'image unique ; pose et bundle depuis les paramètres de requête |
scripts/verify_sheet.py | planche côte à côte photo-vs-rendu (--photo --render --out --bbox) |
scripts/hf_read_check.py | résumer un rapport de vérification HyperFrames ; --fail-on-error pour CI |
scripts/make_gif.md | fiche d'opération navigateur/ffmpeg pour le GIF |
Séquence exécutable minimale
Les commandes s'exécutent depuis la racine du dépôt par rapport à l'exemple travaillé dans
examples/fan/ ; pour un nouvel objet, remplacez src/scene.js, photos/ et
video/. Les fichiers de travail vont dans %TEMP% pour que le checkout reste propre. Gardez les
paramètres — ce sont ceux vérifiés. Exécutez dans PowerShell.
$work = Join-Path $env:TEMP "mbc-verify"; New-Item -ItemType Directory -Force $work | Out-Null
# 1. bundle de la scène (modèle pour un nouvel objet ; nécessite `three` installé).
# L'exemple fournit son bundle à examples/fan/video/assets/vendor/fan.bundle.min.js,
# donc cette étape peut être ignorée pour l'exemple.
npx --yes [email protected] src/scene.js --bundle --format=iife --global-name=Scene `
--minify --target=chrome120 --outfile=dist/scene.bundle.min.js
# 2. capture d'écran d'une pose de vérification de l'exemple (1920x1080, profil frais par exécution)
$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. comparer ce rendu avec la photographie de face dans la même 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. vérifier le film par rapport au contrat HyperFrames, puis lire le rapport
Set-Location examples/fan/video
# nécessite assets/audio/mix.wav (asset de version ; voir 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. rendre le master de livraison
npx --yes [email protected] render --quality delivery
Set-Location ../../..
# 6. couper le GIF avec ffmpeg (route navigateur : 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")
L'étape 3 imprime les deux boîtes de recadrage ; l'exécution vérifiée imprime
saved ...\front-sheet.png 1112x702 | photo (130, 245, 1195, 1455) | render (652, 264, 1252, 1080).
Sorties attendues : check écrit
video/.hyperframes/artifacts/checks/<uuid>.json ; snapshot écrit
video/.hyperframes/artifacts/snapshots/<uuid>/frame-00-at-29.6s.png ; render
écrit video/renders/video_<YYYY-MM-DD>_<HH-MM-SS>.mp4 (environ 36 Mo pour 43,5 s
à 1920×1080/30 ips, environ 2,5 minutes de capture avec un GPU matériel).
Prérequis
- Node.js (testé 22.19.0) avec
npx; esbuild 0.24.2 via npx. - Python 3.11 (testé 3.11.1) avec Pillow pour la planche ; numpy optionnel.
- ffmpeg sur PATH (testé avec une build git du 2025-12-28).
- Chrome pour les captures d'écran sans tête (testé 157.0.8103.40) ; le chemin ci-dessus est le chemin par défaut Windows.
- HyperFrames épinglé à 0.9.36 via
npx --yes [email protected]. checketrenderpilotent Chrome sans tête avec--browser-gpuen automatique : GPU matériel quand présent, logiciel (SwiftShader) sinon ;previewest plus fluide avec un GPU.
Définition de terminé
- six planches de comparaison lisibles de manière acceptable, avec les écarts listés et expliqués ;
hf_read_check.py --fail-on-errorsort 0 sur le rapport de vérification final ;- le MP4 de livraison existe et sa durée est égale au
data-durationde la composition ; - le GIF boucle proprement et est assez petit pour être publié ;
- chaque commande de ce skill a été exécutée une fois dans un checkout frais.