model-by-coding は何をしますか?
物理的な物体の3Dモデルを写真からコードだけで構築し、分解動画とGIFに仕上げます。このパイプラインの唯一のこだわりは、モデルを毎ステップ写真と照合すること。そのため、動画には「もっともらしい物体」ではなく、実際に撮影された物体が映ります。
使用するケース
- ハードサーフェス製品(ファン、ランプ、スピーカー、カメラ、工具、キッチン家電)を短い動画で分解・解説したいとき
- 物体が実在し、複数のアングルから写真撮影できるとき
- 出力が README、投稿、デモリール用のループ分解クリップであるとき
- 既存の写真セットを、編集可能で再レンダリング可能なモデルにしたいとき(スキャンメッシュは不可)
使用しないケース
- フォトリアルなアセットが必要な場合 → フォトグラメトリまたはスキャナーを使用
- 有機的、柔らかい、変形する物体(布、植物、顔)
- 写真が入手できない、または物体が動画にしか写っていない場合
- 静止画一枚で十分な場合 → HyperFrames のステップは不要
- テキスト/UI アニメーションが主題の場合 → このパイプラインはジオメトリが対象
パイプライン
| ステップ | 成果物 | 受理条件 |
|---|---|---|
| 1. 計測 | 一つの基準寸法、比率テーブル、DIM オブジェクト | 比率が2枚以上の写真でクロスチェックされ、すべての推定に inferred の注釈あり |
| 2. モデリング | コードで構築された Three.js シーン、一つの IIFE バンドル、ビューワーページ | 組み立て状態で隙間なし;p = 1 ですべてのパーツが分離 |
| 3. 検証 | 6ポーズの写真/レンダリング比較シート | シルエットとフィーチャー比率が全6ポーズで許容範囲内 |
| 4. フィルム | HyperFrames コンポジション、check レポート、納品用 MP4 | ok: true、エラーなし、再生時間が data-duration と一致 |
| 5. カット | ループ GIF | 開始・終了が安定したポーズ、約 1 MB 以下 |
一つの物体にかかる現実的な予算は数時間〜2日ですが、その大半はステップ2〜3に費やされます。ステップ3を飛ばすと、「1週間かけたモデリング」が「期日通りにできた間違ったモデル」に変わります。
ステップ1 — 写真から計測する
6ポーズ(正面、真横、背面、背面3/4、上面、下面)を収集し、一つの大きな寸法を基準として選び、ラジアルスキャンで計測します。その他の寸法はすべて基準寸法に対する比率で表現します。個別のフィーチャー(リブ、ブレード、スポーク)は目視ではなく、角度周期から数えます。すべての数値を一つの DIM オブジェクトに保持し、ソース写真とそれが実測か推定かをコメントに記述します。絶対的な実世界サイズは決定事項であり、計測事項ではありません。
詳細:references/01-modeling.md §0–2。
ステップ2 — コードでモデリングする
純粋状態 API を持つ一つのシーンモジュール:create、setView、setExplode、setExplodeMap、setSpin、setHeadTilt、setLift、render、getAnchors、resize、dispose。世界座標の規約:物体は +Z 方向を向き、ベースは y = 0、単位はメートル。ハウジングは旋盤プロファイル、グリルは平らなリボン(丸パイプではない)、ブレードはパラメトリックシートとして構築します。すべてのパーツは、組み立て位置、爆発オフセット、アンカーオフセットを保存する一つの関数を通じて登録します。パーツ名は一度だけ命名し、フィルム、ラベル、アンカー、サウンドはすべてその名前をキーにします。
詳細:references/01-modeling.md §3–6。
ステップ3 — 写真と照合する
esbuild でバンドルを構築し、ヘッドレス Chrome でポーズごとに1フレームをレンダリング、各レンダリングを写真と並べてオブジェクトのバウンディングボックスにクロップした比較シートを作成します。比率とパーツ配置は、各オブジェクト自身のボックスに対する割合として読み取ります。背景が込み入っている場合にエッジに円をフィッティングしないでください — フィットが背景の円弧にラッチしてしまいます。6ポーズ、各数回の反復。
ステップ4 — フィルムをレンダリングする
HyperFrames 契約に従ってコンポジションを構築:data-duration を持つルートコンポジション要素、window.__timelines["main"] に登録された一時停止状態の GSAP タイムライン、hf-seek からのみ再描画される Three.js(rAF/タイマーからは不可)、独自のタイミングを持つメディアは class="clip" と data-start/data-duration を付与、一つのオーディオミックスをオーディオクリップとして、28 px 以上のテキスト、大きなバンドルは assets/vendor/*.min.js としてベンダー配置。アニメーション化する前にショットリストを書き、カメラ、爆発、ラベルを一つのディレクタースクリプトから制御し、オーディオキューがその定数を共有できるようにします。check を実行し、レポートを読み、その後 render --quality delivery を実行します。
ステップ5 — GIF をカットする
3〜4秒の安定したセグメント(通常は爆発後のホールド)を選び、gif-master(幅480、12 fps、ループ)または ffmpeg の2パスパレットコマンドでカットし、投稿前にサイズ、フレーム数、ループを確認します。詳細:references/04-gif.md、scripts/make_gif.md。
ハード制約
以下は、違反するとパイプラインが機能しなくなるルールであり、スタイルの好みではありません。
- 契約が最優先。 物体は +Z 方向、ベースは y = 0、単位はメートル、カメラは
target + dist * (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch))、yaw/pitch は度。すべてのスクリプトとフィルムがそれを共有します。 - 純粋状態。 シーン API は同じ状態から同じピクセルをレンダリングしなければなりません。アニメーションループ、壁時計、フレームが依存するコード内の
Math.random()は禁止。 - フレーム = f(t)。 カメラ、爆発、スピン、ラベル、カード、サウンドはすべてフィルム時間の関数であり、それ以外はありません。
- コンポジション契約。 ルート要素に
data-composition-id、data-start、data-duration、data-width、data-height;一時停止状態のタイムラインをwindow.__timelines["<id>"]として登録;hf-seek内でe.detail.timeを使用して再描画。 - クリップ。 独自の開始時間と再生時間を持つメディア(オーディオ、ビデオ、挿入)には
class="clip"、data-start、data-durationを付与;オーディオにはさらにdata-track-index。タイムラインでアニメーション化される DOM にはクリップ属性は不要。 - 可読性。 1080p で 28 px 未満の情報は表示しない(チェッカーのタイプ下限は 28 px = 短辺の 2.6%)。
- ベンダー配置の重み。 three.js と GSAP は
assets/vendor/配下に.min.jsとして;フォントはローカル;レンダリング中はネットワーク不可。 - スクリプトは汎用的に。
scripts/にはオブジェクト名、絶対パス、個人用ディレクトリを含めない;各スクリプトは新規チェックアウトから実行可能でなければなりません。 - コマンドを検証する。 ドキュメント、スクリプト、フィルムに記述されたすべてのコマンドとパラメータは、固定バージョンに対して一度実行し、信頼できることを確認してから使用します。
参考文献ルーティング
| タスク | 参照先 |
|---|---|
| 計測、比率、旋盤プロファイル、グリル/ブレードパラメータ、パーツ、爆発、アンカー | references/01-modeling.md |
| コンポジション契約、ディレクター/ストーリーボード、オーディオ同期、HyperFrames コマンドと出力 | references/02-render.md |
| スクリーンショットループ、比較シート、角度の読み取り、円フィットの失敗 | references/03-verify.md |
| gif-master 操作カード、クレジット、ffmpeg GIF レシピ | references/04-gif.md |
| リポジトリ、GitHub Release、コミュニティリスト PR、YouTube/X | references/05-publish.md |
| 症状→原因→修正カタログ(照明、影、リブ、タイプ、バンドル、Chrome) | references/06-gotchas.md |
このスキルに同梱されるツール:
| ファイル | 役割 |
|---|---|
scripts/view.html | シングルフレームビューワー;ポーズとバンドルはクエリパラメータから |
scripts/verify_sheet.py | 写真とレンダリングのサイドバイサイドシート(--photo --render --out --bbox) |
scripts/hf_read_check.py | HyperFrames の check レポートを要約;CI 用に --fail-on-error |
scripts/make_gif.md | GIF 用のブラウザ/ffmpeg 操作カード |
最小実行可能シーケンス
以下のコマンドはリポジトリルートから examples/fan/ の実例に対して実行します。新しいオブジェクトの場合は src/scene.js、photos/、video/ を適宜置き換えてください。作業ファイルは %TEMP% に作成されるため、チェックアウトはクリーンなままです。パラメータはそのまま使用してください — これらは検証済みのパラメータです。PowerShell で実行します。
$work = Join-Path $env:TEMP "mbc-verify"; New-Item -ItemType Directory -Force $work | Out-Null
# 1. シーンをバンドル(新しいオブジェクト用のテンプレート;`three` のインストールが必要)。
# サンプルは examples/fan/video/assets/vendor/fan.bundle.min.js にバンドルを同梱しているため、
# このステップは例題ではスキップ可能。
npx --yes [email protected] src/scene.js --bundle --format=iife --global-name=Scene `
--minify --target=chrome120 --outfile=dist/scene.bundle.min.js
# 2. サンプルの検証ポーズを1つスクリーンショット(1920x1080、実行ごとに新しいプロファイル)
$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. そのレンダリングを同じポーズの正面写真と比較
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. フィルムを HyperFrames 契約に照らして check し、レポートを読む
Set-Location examples/fan/video
# assets/audio/mix.wav が必要(Release アセット;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. 納品用マスターをレンダリング
npx --yes [email protected] render --quality delivery
Set-Location ../../..
# 6. ffmpeg で GIF をカット(ブラウザルート: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")
ステップ3では二つのクロップボックスが表示されます;検証済み実行では saved ...\front-sheet.png 1112x702 | photo (130, 245, 1195, 1455) | render (652, 264, 1252, 1080) と出力されます。
期待される出力:check は video/.hyperframes/artifacts/checks/<uuid>.json を書き込み、snapshot は video/.hyperframes/artifacts/snapshots/<uuid>/frame-00-at-29.6s.png を書き込み、render は video/renders/video_<YYYY-MM-DD>_<HH-MM-SS>.mp4(43.5秒、1920×1080/30 fps で約36 MB、ハードウェアGPUで約2.5分のキャプチャ時間)を書き込みます。
必要条件
- Node.js(テスト済み:22.19.0)と
npx;npx 経由の esbuild 0.24.2。 - Python 3.11(テスト済み:3.11.1)と Pillow(シート生成用);numpy は任意。
- PATH が通った ffmpeg(テスト済み:2025-12-28 git ビルド)。
- ヘッドレススクリーンショット用 Chrome(テスト済み:157.0.8103.40);上記のパスは Windows のデフォルト。
npx --yes [email protected]で固定された HyperFrames 0.9.36。checkとrenderは--browser-gpuを自動で使用してヘッドレス Chrome を起動:ハードウェアGPUがあればそれを使用、なければソフトウェア(SwiftShader)を使用。previewは GPU があるとスムーズ。
完了条件
- 6枚の比較シートが許容範囲内で読め、偏差が一覧表示・説明されていること。
- 最終 check レポートで
hf_read_check.py --fail-on-errorが終了コード 0 で終了すること。 - 納品用 MP4 が存在し、その再生時間がコンポジションの
data-durationと一致すること。 - GIF がきれいにループし、投稿可能なサイズであること。
- このスキルのすべてのコマンドが新規チェックアウトで一度実行されていること。