Communitygithub.com

model-by-coding

当需要将真实物体转化为代码构建的3D模型,并生成拆解视频和循环GIF时使用——通过照片测量物体,用Three.js代码建模,借助无头截图验证每个角度与照片的一致性,最后用HyperFrames 0.9.36渲染影片。

model-by-coding 是什么?

该技能是一个完整的3D建模与渲染管线,专为将实体硬表面产品(如风扇、台灯、音箱、相机、工具、厨房电器)转化为代码构建的3D模型并制作拆解动画而设计。它从照片测量物体尺寸,用Three.js以代码方式建模,通过无头Chrome截图逐角度与照片比对验证,最后使用HyperFrames渲染影片并生成循环GIF。支持平台包括任何可运行Node.js、Python和Chrome的环境(Windows/macOS/Linux)。该管线的核心原则是“验证优先”——每一步都强制将模型渲染与原始照片对比,确保最终展示的是真实被拍摄的物体,而非近似物。它特别适合需要可编辑、可重新渲染的几何模型而非扫描网格的场景,输出可直接嵌入README、社交媒体或作品集。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Mng12345/model-by-coding/tree/main

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

model-by-coding 是做什么的?

从照片出发,完全通过代码构建物理物体的3D模型,然后将其制作成拆解影片和GIF。该管线唯一坚持的核心理念是:每一步都通过照片验证模型,因此影片展示的是被拍摄的真实物体,而非一个看似合理的替代品。

使用场景

  • 需要在一段短视频中解释或展示某个硬表面产品(风扇、台灯、音箱、相机、工具、厨房电器)的内部结构;
  • 物体实际存在,可以从多个角度拍摄照片;
  • 输出物是一个用于README、帖子或作品集的循环拆解片段;
  • 已有的照片集需要转化为可编辑、可重新渲染的模型——而不是扫描网格。

不适合的场景

  • 需要照片级真实的资产:应使用摄影测量法或扫描仪;
  • 物体是有机、柔软或可变形的(织物、植物、面部);
  • 没有可用照片,或物体仅出现在视频中;
  • 单张静态图片就足够——则无需HyperFrames步骤;
  • 主题是文字/UI动画:该管线专注于几何体。

管线

步骤产出验收标准
1. 测量一个标尺尺寸、一个比例表、一个DIM对象比例在≥2张照片上交叉验证,每个假设标记为推断
2. 建模代码构建的Three.js场景、一个IIFE包、查看器页面组装状态无间隙;p = 1时每个零件分离
3. 验证六张相同姿态的照片/渲染对比图所有六张图上轮廓和特征比例在容差范围内
4. 拍摄一个HyperFrames合成、check报告、交付MP4ok: true,无错误,时长与data-duration匹配
5. 剪辑一个循环GIF起始和结束于静止姿态,小于约1 MB

一个物体的合理时间预算为几小时到两天,大部分时间花在步骤2-3上。跳过步骤3会把一周的建模变成按时交付的错误模型。

第一步 — 从照片测量

收集六个姿态(正面、真实侧面、背面、背面3/4、俯视、仰视),选择一个大尺寸作为标尺,用径向扫描测量,然后将所有其他尺寸表示为其比例。通过角周期计数离散特征(肋条、叶片、辐条),而非目测。将所有数字保存在一个DIM对象中,并附注释标明其来源照片以及是测量值还是推断值;绝对真实世界尺寸是一个决策,而非测量值。

深度参考:references/01-modeling.md §0–2。

第二步 — 用代码建模

一个场景模块,提供纯状态API:create、setView、setExplode、setExplodeMap、setSpin、setHeadTilt、setLift、render、getAnchors、resize、dispose。世界约定:物体朝向+Z,基座在y=0,单位为米。将外壳构建为车削轮廓,格栅构建为扁平带状(非圆管),叶片构建为参数化曲面,并通过一个函数注册每个零件,该函数存储其组装位置、爆炸偏移和锚点偏移。零件命名一次;影片、标签、锚点和声音都基于这些名称。

深度参考:references/01-modeling.md §3–6。

第三步 — 与照片验证

使用esbuild构建包,在无头Chrome中为每个姿态渲染一帧,然后将每帧渲染与其照片进行并排对比,并裁剪到物体的边界框。将比例和零件位置读作物体自身边界框的分数;不要在有复杂背景上对边缘拟合圆——拟合会吸附到背景弧线上。六个姿态,每个迭代几次。

深度参考:references/03-verify.md。

第四步 — 渲染影片

按照HyperFrames约定构建合成:一个根合成元素,带有data-duration,一个暂停的GSAP时间线位于window.__timelines["main"],仅通过hf-seek(而非rAF/定时器)重绘Three.js,具有自身时长的媒体标记为class="clip"并带有data-start/data-duration,一个音频混音作为音频片段,文本尺寸28 px或更大,大型包作为assets/vendor/*.min.js供应。在动画化之前编写镜头列表;从一个导演脚本驱动相机、爆炸和标签,以便音频提示可以共享其常量。运行check,阅读报告,然后运行render --quality delivery。

深度参考:references/02-render.md。

第五步 — 剪辑GIF

选择一个3-4秒的稳定片段(通常是爆炸后的保持状态),在gif-master中剪辑(宽度480,12 fps,循环),或使用ffmpeg双通道调色板命令,并在发布前验证大小、帧数和循环。深度参考:references/04-gif.md,scripts/make_gif.md。

硬约束

以下是违反时会中断管线的规则;它们不是风格偏好。

  1. 约定优先。 物体朝向+Z,基座在y=0,单位为米,相机:target + dist * (sin(yaw)cos(pitch), sin(pitch), cos(yaw)cos(pitch)),yaw/pitch以度为单位。每个脚本和影片共享此约定。
  2. 纯状态。 场景API必须将相同状态渲染到相同像素。无动画循环,无挂钟时间,无依赖于帧的代码中的Math.random()。
  3. 帧 = f(t)。 相机、爆炸、旋转、标签、卡片和声音都只是影片时间的函数;除此之外别无其他。
  4. 合成约定。 根元素上应有data-composition-id、data-start、data-duration、data-width、data-height;暂停的时间线注册为window.__timelines["<id>"];在hf-seek内使用e.detail.time进行重绘。
  5. 剪辑。 具有自身起始和时长的媒体(音频、视频、插入片段)携带class="clip"、data-start、data-duration;音频额外携带data-track-index。由时间线动画化的DOM无需剪辑属性。
  6. 可读性。 在1080p下,任何信息性内容不得低于28 px(检测器的类型下限为28 px = 短边的2.6%)。
  7. 供应商包权重。 three.js和GSAP以.min.js格式放在assets/vendor/下;使用本地字体;渲染期间无网络请求。
  8. 脚本通用。 scripts/中无物体名称、无绝对路径、无个人目录;每个脚本必须能从全新仓库运行。
  9. 验证命令。 写入文档、脚本或影片的每个命令和参数必须在信赖前针对固定版本执行一次。

参考路由

任务阅读
测量、比例、车削轮廓、格栅/叶片参数、零件、爆炸、锚点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/Xreferences/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检查报告;--fail-on-error用于CI
scripts/make_gif.md浏览器/ffmpeg GIF操作卡

最小可运行序列

命令从仓库根目录针对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
# 需要 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可选。
  • ffmpeg在PATH中(测试版本为2025-12-28的git构建)。
  • Chrome用于无头截图(测试版本157.0.8103.40);以上路径是Windows默认路径。
  • HyperFrames固定为0.9.36,通过npx --yes [email protected]使用。
  • check和render驱动无头Chrome并带有--browser-gpu自动模式:有硬件GPU时使用硬件,否则使用软件(SwiftShader);preview在有GPU时更流畅。

完成标准

  • 六张对比图可接受地读出,偏差已列出并解释;
  • hf_read_check.py --fail-on-error在最终检查报告上返回0;
  • 交付MP4存在,其时长等于合成的data-duration;
  • GIF循环干净且足够小以便发布;
  • 该技能中的每个命令已在全新仓库中运行一次。

相关技能