Joinery – 家具照片转可制作3D模型 是做什么的?
一张照片,一个页面,一个制作包。访客可以看到组装好的家具,拖动滑块让它分解开来,一步步了解组装顺序,点击部件即可查看其规格尺寸。驱动部件运动的同一个模型也生成了下料清单、工程图和说明书,因此纸面上的任何内容都与屏幕上的完全一致。
SKILL_DIR 是本文件所在的文件夹。构建结果和制作包将输出到运行命令所在文件夹下的 ./joinery-out 目录。
首次运行
cd "$SKILL_DIR" && npm install
需要 Node 20.10+。如需生成静态渲染图和说明书图片,还需要 Chromium:npx playwright install chromium,或者将 CHROMIUM 环境变量指向任意 Chrome/Chromium 可执行文件(CHROMIUM=/usr/bin/google-chrome)。
工作流程
照片 + 锚点 ─► spec.json ─► model.js ─► 验证 ─► 页面 ─► 静态图片 ─► 制作包
| 步骤 | 命令 | 输出结果 |
|---|---|---|
| 1. 解读 | 你,阅读照片 | spec.json:类型、锚点、材料、每个部件命名、每种接合方式选择、每条假设记录 |
| 2. 建模 | 你,编写 model.js | 作为参数函数描述的家具:盒子、接合、步骤 |
| 3. 验证 + 打包 | node "$SKILL_DIR/pack.mjs" ./my-piece | 如有任何碰撞或悬空则拒绝打包;生成下料清单、工程图、板材切割图、木材与五金清单、说明书(有 Chromium 时还会生成 manual.pdf) |
| 4. 构建页面 | node "$SKILL_DIR/build.mjs" ./my-piece | joinery-out/dist/my-piece.html,离线可用,约 570 KB |
| 5. 审查 | node "$SKILL_DIR/capture.mjs" my-piece --stills steps | 包含所有视图和步骤的联系表——阅读、修复、重复 |
| 6. 再次打包 | node "$SKILL_DIR/pack.mjs" ./my-piece | 说明书和 PDF 现在包含步骤图片 |
从最接近待制作家具的示例开始复制。每个示例都包含家具所需的所有文件:spec.json、model.js、main.js。家具文件通常放在用户自己的文件夹中,而不是 SKILL_DIR 中:复制后,将 model.js 和 main.js 中的两个 ../../kit/ 导入路径改为绝对路径 $SKILL_DIR/kit/。
| 示例 | 复制用于 | 展示内容 |
|---|---|---|
parsons-table | 简单的桌子、书桌、长凳 | 最小配置:桌腿、横撑、桌面、榫卯、纽扣件。64 行 |
walnut-racetrack-table | 圆形、椭圆形或锥形部件 | roundedRect / circle 轮廓、锥形圆腿、榫头足够短以避免在桌腿内互相干涉 |
walnut-display-cabinet | 带曲线或玻璃的箱体家具 | 弯曲层压角(quarterRing、stock.layers)、夹钳铰链玻璃门、层板销、嵌槽背板、底座 |
walnut-entry-unit | 嵌入式家具、衣柜、厨房风格单元 | 多个箱体、框架与嵌板门、滑轨抽屉、浮动层板、贴皮 MDF 与 veneer 搭配、LED 和挂钩五金 |
walnut-hallway-unit | 钢木混合家具 | 焊接钢框架内嵌木盒、藤编面板门、绕过电箱的开放式凹槽 |
步骤 1:忠实解读照片
一张照片只能提供 无尺寸和隐藏结构。本技能是设计解读,而非摄影测量。在编写任何几何之前:
- 获取锚点。 询问一至三个实际尺寸(整体长 × 宽 × 高,或一个已知尺寸如“桌面 180 cm”)。如果对方无法提供,则使用
kit/validate.js中的人体工学标准(餐桌 750 H,座椅 450 H)并注明。其他所有尺寸均为照片中读取的比例乘以锚点。 - 命名类型。
dining-table、chair、bookshelf……这将决定人体工学检查和典型构造。 - 列出可见和不可见部分。 桌腿、桌面、可见层板标记为
visible。厚桌面下的横撑、背板、拉档、抽屉滑轨、角块标记为inferred—— 在部件上设置inferred: true,以便在每个输出中标记。制作者可以覆盖它们;他们必须知道哪些是你推断的。 - 从库中选择接合方式(
templates/joinery-library.md),根据类型 × 材料 × 车间选择。如果车间类型不明显,询问制作者有什么车间(shop:hand、cabinet、cnc);这也会影响工具列表。默认为木工车间:桌腿/横撑用榫卯、层板用槽接、面板用木榫或 Dominos、仅隐藏处用 pocket screws。将选择及其理由写入spec.source.assumptions。 - 读取材料和涂装。 根据颜色和纹理判断树种,根据边缘比例判断厚度,根据高光判断边缘处理(圆角/斜角/直角)。厚度必须落在标准规格上(
kit/validate.js中的STOCK_T)—— 40,而不是 38.7。根据光泽度设置finish:开放式哑光表面为hardwax-oil(默认)或oil,均匀的半光薄膜为lacquer。涂漆和金属部件遵循各自的材料。
将所有内容写入 spec.json,使用 templates/spec.schema.json。规格是给人看的:制作者在阅读其他任何内容之前先阅读 source.assumptions。
步骤 2:构建模型
model.js 导出 piece = definePiece({name, type, shop, finish, params, source, materials, build})。build(P) 返回 {parts, joints, steps, hardware}。示例约为 70 行;一把椅子约为 120 行。
部件 是在组装姿态下的轴对齐边界框:box(id, name, [sx, sy, sz], [x, y, z], {grain, material, edge, explode, inferred, note})。x 为长度,y 为高度(地板在 0 处),z 为深度。grain 是木材纹理方向——决定下料清单中的木料长度和渲染中的年轮图案。相同下料的部件使用相同的 name,合并为下料清单的一行;id 保持唯一。
曲线和圆角部件 保留 size/at 的边界框,并添加 profile: {axis, path, note}:沿 axis 的真实轮廓,以盒子为中心(用于弯曲角的 quarterRing(R, t, [su, sv])、圆角玻璃或跑道形桌面的 roundedRect(w, h, radii)、圆腿的 circle(d);profile.taper: {to, end} 向一端渐缩)。它渲染为挤出形状,按轮廓验证,并按轮廓绘制。当盒子不是实际木料时(如弯曲层压板),添加 stock: {L, W, T, layers, layerT},下料清单和采购清单将使用该信息。在两个方向弯曲的部件(如雕刻椅背、复合形状的靠背板)尚无实体类型:告知用户,建模为最接近的直线挤出件,并在其 note 中说明。
五金件 是绘制在部件上的成品硬件,而非切割件:fittings: [{kind, size, at, material, axis, dir, holes: {n, d, cc, axis}}],其中 at 相对于部件中心。拉手、挂钩和 LED 轮廓会渲染;其螺丝孔会出现在部件的工程图上。同时也在 hardware 中列出它们。
每件颜色: materials 映射中的条目可以携带 base、dark、ring、streak、rough、clearcoat、env 来调整套件材料以匹配照片,而不影响其他部件。大平面上的深色油性木材需要 rough ≈ 0.9,否则主光的光泽会使其变灰;请对比照片和渲染的数值,而非凭感觉。
接合 连接两个部件 id:{a, b, type},其中 a 携带榫头/螺丝,b 接收。类型:mortise-tenon(榫长、榫厚、肩宽、斜接)、dowel(数量、直径、长度)、dado(深度)、rebate(面、深度:背板嵌在 b 边缘的槽中)、shelf-pin(数量、直径、长度、缩进:层板通过销子放在 b 上)、hinge(面、at:[世界 y…]、cup:35 在 a 上钻孔:+z 方向的门挂在 b 上)、groove(宽度、深度:面板或抽屉底板浮在 b 的边缘或面内)、runner(抽屉盒在 b 上的侧装滑轨)、weld(钢连接)、pocket-screw、button、figure-8、screw、glue。shelf-pin 和 hinge 允许硬件桥接所需的几毫米间隙;每个部件必须连接到其余部件,否则验证会拒绝。applyJoinery 将匹配的特征写入两个构件:榫头会延长下料清单中的木料长度,榫眼会出现在桌腿工程图上,并带有其面和位置,两者都会渲染。接合还告诉 validate 这两个实体允许接触。
步骤 是说明书:{title, parts: [ids], note, check, view}。每个部件必须恰好出现在一个步骤中。按制作者的方式排列:先子组件(端架、抽屉盒),然后连接它们的东西,最后是放在上面的东西,如果操作方式是倒置则家具倒置。view 设置该步骤在页面和说明书中的摄像机位置。
参数。 所有内容都是 P 中的数字。更改 L,横撑、榫头、纽扣间距和下料清单都会随之变化。这就是关键:同一个 model.js 可以生成 1600 版本和 2200 版本。
决定可建造性的规则
- 从照片出发,而非想象。 对照照片构建并并排比较渲染图:轮廓、腿与桌面比例、悬挑、横撑阴影线位置。从“一张漂亮的橡木桌子”想象出来的家具会显得通用;当有参照物时,同一段代码可以紧密重建真实家具。
- 真实木料。 厚度为标准规格。宽度超过 200 mm 的部件不能是一块整板——
pack.mjs会将其转化为拼板并注明。厚度超过 50 mm 的部件不能是一块整板——会变成层压板。纹理沿每根木料长度方向,否则验证会警告短纹理。 - 每个接合都是真实接合。 两个部件只能通过声明的接合共享体积。在桌腿内部相遇的榫头必须
mitred: true或缩短;验证会检查碰撞。宽实心桌面必须用纽扣件、figure-8 或长条孔固定——绝不能刚性胶合——验证会警告。 - 用数字修复碰撞。 当验证报告一个部件穿过另一个部件时,更改
P或build中的值。绝不能用摄像机隐藏它。 - 写下假设,而不仅仅是部件。 照片中未显示的横撑应标记为
inferred: true并添加note。说明书的第一个框中列出所有假设。 - 一个数字,一个来源。 下料清单、工程图、页面上的 HUD 和说明书都读取
stockOf(part)。如果某个数字看起来不对,修复模型,而不是输出。
实时页面规则
- 页面是一个离线 HTML 文件,小于 1 MB(内联 Three.js)。包含许多部件的大型家具也能保持快速,因为每个部件是一个圆角盒子,木材通过着色器实现,而非纹理。
- 灯光工作室:暖色主光、冷色补光、柔和地面阴影。木材着色器沿
grain自动绘制弦切纹理,并在端面显示端面纹理。保持roughness ≥ 0.45;浅色地面上的光泽木材看起来像塑料。 - 无操作 20 秒后,页面自动巡视:分解、分步、底部 X 光。任何输入都会交还控制权。
- URL 状态驱动静态捕捉:
?step=2、?explode=1、?iso=leg-fl、?view=under&xray=1、?panel=0。设置这些参数后,页面会直接跳转到状态而非动画,因此静态图片在 5 fps 和 60 fps 下相同。
审查循环
你无法查看页面,但可以查看静态图片。未经阅读当前构建的联系表,切勿报告家具已完成。
node "$SKILL_DIR/capture.mjs" <name> --stills steps→joinery-out/<name>-sheet.png:组装图、分解图、正视图、底部 X 光图、每个步骤一张图。- 对照检查清单阅读每张图。将参考照片放在
front和assembled图旁边。 - 对于单个部件,
--stills parts可隔离每个部件;对于单个状态,--still "step=3&view=under"。 - 修复、重建、返回步骤 1。当一遍检查未发现问题时停止。
- 同样打开
joinery-out/<name>/manual.html,像制作者那样阅读。
检查清单:
- 比例。 对照照片:腿粗与长度、顶部厚度与长度、悬挑、横撑线高度。误差在 5% 以内,否则修改参数。
- 接合。 榫眼出现在横撑接触的面上;榫头在分解视图和 X 光中可见;分解视图中没有部件悬空(无接合的部件没有合理的分解矢量)。
- 步骤。 每个步骤添加可见的内容;摄像机显示正在添加的部件;已放置的部件在下面可见。步骤 1 绝不能是桌面。
- 工程图。 工程图上的每个榫眼、槽孔和孔洞都命名了其面和距端部的距离。包含榫头的下料清单长度需注明。
- 木料。 木料清单中没有宽度超过 200 或厚度超过 50 的板材;拼板和层压板被列出。每个部件在切割图中位于一块板材或板料上。
- 工具和涂装。
tools.md列出制作者车间会用的工具,夹钳长度适用于最大的拼板。涂漆、贴皮和金属部件各有其涂装。 - 渲染。 距离边缘不超过 60 像素,面板从不遮挡家具,近白色木材(枫木、白蜡木)在浅色背景下有可见的边缘。
材料
kit/stage.js → MATERIALS:白橡木、红橡木、胡桃木、枫木、白蜡木、山毛榉、松木、樱桃木、桦木胶合板、MDF、钢、黄铜。通过基础/深色颜色加上年轮和纹理强度来添加树种。还包括:胡桃木-MDF、胡桃木-弯曲胶合板(贴皮板材)、MDF 黑色、玻璃(透明,由边缘绘制,在制作包中按尺寸订购而非从板材上切割)。definePiece 中的 materials 映射为下料清单命名(例如“白橡木,实木”)。