model-by-coding은(는) 무엇을 하나요?
실제 물체의 사진으로부터 코드로만 3D 모델을 구축한 다음, 분해 필름과 GIF로 만듭니다. 파이프라인은 한 가지에 대해 엄격합니다: 모든 단계에서 사진과 비교하여 모델을 검증하므로, 필름은 '그럴듯한' 물체가 아니라 실제 촬영된 물체를 보여줍니다.
사용하는 경우
- 단단한 표면 제품(선풍기, 램프, 스피커, 카메라, 공구, 주방 기기)을 짧은 비디오로 설명하거나 분해하여 보여줘야 할 때
- 물체가 실제로 존재하며 여러 각도에서 사진을 찍을 수 있을 때
- README, 게시물 또는 데모 릴에 쓸 루핑 분해 클립이 필요할 때
- 기존 사진 세트를 편집 가능하고 재렌더링 가능한 모델로 만들어야 할 때 (스캔 메시가 아닌)
사용하지 않는 경우
- 실사 같은 에셋이 필요한 경우: 대신 광측량법이나 스캐너를 사용하세요.
- 물체가 유기적이거나 부드럽거나 변형되는 경우(천, 식물, 얼굴)
- 사진이 없거나 물체가 비디오로만 볼 수 있는 경우
- 단일 정지 이미지로 충분한 경우 — HyperFrames 단계가 불필요합니다.
- 주제가 텍스트/UI 애니메이션인 경우: 이 파이프라인은 기하학(geometry)에 관한 것입니다.
파이프라인
| 단계 | 산출물 | 수용 기준 |
|---|---|---|
| 1. 측정(Measure) | 하나의 기준 치수, 비율 표, DIM 객체 | 2장 이상의 사진에서 비율 교차 확인, 모든 가정은 '추정(inferred)'으로 표시 |
| 2. 모델링(Model) | 코드로 빌드된 Three.js 장면, 하나의 IIFE 번들, 뷰어 페이지 | 조립된 상태에 틈이 없음; p = 1이 모든 부품을 분리 |
| 3. 검증(Verify) | 동일 포즈 사진/렌더 6장의 비교 시트 | 6장 모두에서 실루엣 및 특징 비율이 허용 오차 이내 |
| 4. 필름(Film) | HyperFrames 합성물, check 보고서, 전달 MP4 | ok: true, 오류 없음, 재생 시간이 data-duration과 일치 |
| 5. 편집(Cut) | 루핑 GIF | 안정된 포즈에서 시작하고 끝나며, ~1 MB 미만 |
하나의 물체에 예상 소요 시간은 몇 시간에서 이틀 정도이며, 대부분 단계 2~3에 소요됩니다. 3단계를 건너뛰면 일주일 동안 모델링해도 잘못된 모델이 제때 납품되는 결과를 낳습니다.
1단계 — 사진에서 측정하기
여섯 가지 포즈(앞면, 진정한 측면, 뒷면, 뒷면 3/4, 위, 아래)를 수집하고, 하나의 큰 치수를 기준으로 선택한 후 방사형 스캔으로 측정하고, 다른 모든 것은 그것에 대한 비율로 표현합니다. 개별 특징(리브, 블레이드, 스포크)은 눈으로 세지 말고 각주기(angular period)로 셉니다. 모든 숫자를 하나의 DIM 객체에 담고, 출처 사진 이름과 측정값인지 추정값인지 주석으로 표시합니다. 절대적인 실제 크기는 측정이 아닌 결정(decision)입니다.
심화: references/01-modeling.md §0–2.
2단계 — 코드로 모델링하기
순수 상태 API를 가진 하나의 장면 모듈: create, setView, setExplode, setExplodeMap, setSpin, setHeadTilt, setLift, render, getAnchors, resize, dispose. 세계 좌표 규칙: 물체는 +Z 방향, 바닥은 y=0, 단위는 미터. 하우징은 선반 프로파일(lathe profiles)로, 그릴은 둥근 튜브가 아닌 평평한 리본(flat ribbons)으로, 블레이드는 매개변수 시트(parametric sheets)로 구축하고, 모든 부품을 하나의 함수를 통해 등록하여 조립된 위치, 분해 오프셋 및 앵커 오프셋을 저장합니다. 부품 이름은 한 번만 지정합니다. 필름, 레이블, 앵커 및 사운드는 모두 그 이름을 키로 사용합니다.
심화: references/01-modeling.md §3–6.
3단계 — 사진과 비교하여 검증하기
esbuild로 번들을 빌드하고, 헤드리스 Chrome에서 포즈당 한 프레임을 렌더링한 후, 각 렌더링을 해당 사진과 물체의 경계 상자로 자른 나란히 비교 시트(side-by-side sheet)로 비교합니다. 비율과 부품 배치는 각 물체 자체 상자의 분수로 읽습니다. 배경이 복잡한 경우 원을 가장자리에 맞추지 마십시오 — 피팅이 배경 호에 고정됩니다. 여섯 포즈, 각각 몇 회 반복.
4단계 — 필름 렌더링하기
HyperFrames 계약에 따라 합성물을 빌드합니다: data-duration이 있는 루트 합성 요소, window.__timelines["main"]에 등록된 일시 중지된 GSAP 타임라인, hf-seek에서만 다시 그려지는 Three.js(rAF/타이머는 절대 안 됨), class="clip"과 data-start/data-duration으로 표시된 자체 타이밍의 미디어, 하나의 오디오 믹스를 오디오 클립으로, 28px 이상의 텍스트, 그리고 assets/vendor/*.min.js에 벤더링된 큰 번들. 애니메이션 전에 샷 리스트를 작성하십시오. 카메라, 분해 및 레이블을 하나의 디렉터 스크립트에서 구동하여 오디오 큐가 그 상수를 공유할 수 있도록 합니다. check를 실행하고 보고서를 읽은 다음 render --quality delivery를 실행합니다.
5단계 — GIF 편집하기
3~4초의 안정된 구간(보통 분해된 상태 유지)을 선택하고, gif-master(너비 480, 12fps, 루프) 또는 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는 도(degree) 단위. 모든 스크립트와 필름이 이를 공유합니다. - 순수 상태. 장면 API는 동일한 상태를 동일한 픽셀로 렌더링해야 합니다. 애니메이션 루프, 벽시계(wall clock), 프레임이 의존하는 코드 내
Math.random()은 금지입니다. - 프레임 = f(t). 카메라, 분해, 회전, 레이블, 카드 및 사운드는 모두 필름 시간의 함수입니다. 그 외는 없습니다.
- 합성 계약. 루트 요소에
data-composition-id,data-start,data-duration,data-width,data-height;window.__timelines["<id>"]에 등록된 일시 중지된 타임라인;e.detail.time을 사용하여hf-seek내부에서 다시 그리기. - 클립. 자체 시작 시간과 재생 시간을 가진 미디어(오디오, 비디오, 삽입물)는
class="clip",data-start,data-duration을 가집니다. 오디오는 추가로data-track-index를 가집니다. 타임라인에 의해 애니메이션되는 DOM은 클립 속성이 필요 없습니다. - 가독성. 1080p에서 28px 미만의 정보성 콘텐츠는 금지(검사기의 최소 텍스트 크기는 28px = 짧은 변의 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 릴리스, 커뮤니티 목록 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 검사 보고서 요약; 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. 예제의 검증용 포즈 하나를 스크린샷 (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 계약에 대해 필름 검사 후 보고서 읽기
Set-Location examples/fan/video
# needs assets/audio/mix.wav (Release asset; see 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/30fps에서 약 36MB, 하드웨어 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가 있을 때 더 부드럽습니다.
완료 조건
- 여섯 장의 비교 시트가 허용 가능하게 읽히고, 편차가 나열되고 설명됨.
- 최종 검사 보고서에서
hf_read_check.py --fail-on-error가 0으로 종료됨. - 전달 MP4가 존재하고 재생 시간이 합성물의
data-duration과 일치함. - GIF가 깔끔하게 루프되고 게시 가능할 정도로 작음.
- 이 스킬의 모든 명령이 새 체크아웃에서 한 번 실행됨.