機器技能
你打造機器的方式,就像產品攝影師拍攝它,以及工程師解釋它一樣:真實物體、真實尺寸、真實材質、在乾淨的攝影棚場景中,並以真實方程式運作。輸入一句話,輸出一個 HTML 檔案,以及一支可透過單一指令產生的影片。
輸出總是:
<name>.html:一個單一自包含的頁面(使用來自 jsDelivr 的 three.js 0.170,工具包已內嵌)。它可以直接從磁碟、任何靜態主機或 iframe 中開啟。- 一支影片:
node scripts/record.mjs <name>.html out/<name>.mp4 --music freepd:<類別>/<名稱>。
請先打開 examples/。每個範例都是根據其頁面頂端的一句話製作的。examples/turbofan.html 是參考範例:在開始撰寫任何程式碼之前,請從頭到尾閱讀它。
標準
在開始之前,先想像觀眾在觀看三秒後會看到的畫面。它必須已經看起來像真實機器的棚拍產品照片:正確的輪廓和比例、看起來像金屬的質感、在紙質地板上的柔和陰影、沒有任何物件漂浮、裁切或佔位物。然後它開始運作,螢幕上的每個數字都是計算出來的。
只要以下任何一項成立,結果就不算完成:
- 缺少機械師會預期看到的零件(燃燒室沒有燃油噴嘴、手錶沒有擒縱機構、引擎沒有閥門)
- 有物件漂浮、互相貫穿,或在接合處出現明顯的接縫或縫隙
- 材質看起來像塑膠但應該是金屬,或全部都是同一種灰色
- 螢幕上的數字是手動輸入的,而非由解算器計算
- 剖面圖顯示的是空心外殼,而不是實心的剖面面
- 影片的第一幀畫面不美觀
工作流程
1. 研究真實機器(5分鐘,不寫程式碼)
在機器腳本頂端的註解區塊中,根據你所知道的(以及必要的網路搜尋),記錄以下資訊:
- 真實尺寸(公尺):5 到 10 個定義輪廓的零件尺寸(例如:渦輪風扇:風扇直徑 2.85 公尺、長度 4.9 公尺、發動機罩最大直徑 3.44 公尺)。
- 零件清單:按組裝順序排列,並標註每個零件的材質。目標是 15 到 40 個已命名的零件;重複的零件(葉片、齒輪齒、螺栓、線圈)需單獨計數。
- 物理原理:使其運作的原理,整理成 3 到 6 個你將實際計算的方程式,並包含真實常數和操作點(例如:渦輪風扇:布雷頓循環,起飛時 N1 100%、OPR 42、T4 1750 K,約 330 kN)。
- 一個關鍵數值:觀眾最關心的一個數值:主要指標(推力、扭矩、每小時脈搏數、流量、功率)。它會以巨大的襯線字體顯示,並搭配一個細長的進度條。
- 輸入控制:觀眾想要操控的輸入:一個滑桿(油門、風速、轉速、幫浦速度、研磨粗細)。
- 破壞模式:工程師會擔心的故障,以及解算器會發生的狀況(例如:風扇葉片脫落及其不平衡載荷、超過紅線轉速導致的氣門浮動、幫浦失速、過壓)。這必須是計算得出的結果,而非一段動畫。
2. 從模板建立頁面
將 kit/template.html 複製為 <name>.html。它包含了 import map(固定版本的 [email protected])、字型、一個用來放置工具包的標記區塊,以及你需要填寫的四個部分:
const { THREE, Lab, MAT, GEO, heatGlow, clamp, lerp, approach, fmt } = window.LabKit
const lab = new Lab({ prompt, no, title, sub, size, view, cut }) // 1. 實驗室
// 2. 零件 lab.mesh(GEO..., MAT..., { name, pos, explode, xray, count })
// 3. 解算器 lab.hero(...); lab.slider(...); lab.onTick((dt, lab) => { ... lab.setHero(...) })
// 4. 破壞、解釋、影片 lab.breakIt(...); lab.how(...); lab.autoFilm(...); lab.start()
複製完成後,立即執行 node <skill 目錄>/scripts/inline.mjs <name>.html:它會將工具包程式碼貼入標記區塊中,使頁面無論位於何處都是一個自包含的檔案(模板中的 src="../kit/lab-kit.js" 僅在此儲存庫中解析)。然後,在第二個腳本區塊中撰寫你的機器。
3. 幾何:真實的固體,真實的尺寸
在機器自身的座標系中以公尺為單位進行製作,Y 軸向上,地板在 y = 0 處,除非你設定了 ground。將 size 設定為最長的實際尺寸;工具包會調整顯示比例,使每台機器在場景中佔據相同空間,而比例尺會顯示真實尺寸。
- 圓形零件(軸、盤、外殼、噴嘴、錐體、整流罩、活塞、錶圈):使用
GEO.lathe([[r, y], ...])搭配一個封閉的輪廓。外殼:GEO.shell(profile, thickness),管子:GEO.tube(rOut, rIn, h),倒角圓盤:GEO.disc(r, h)。角落保持清晰,曲線部分自動平滑著色。 - 葉片和導葉:
GEO.rotor({ count, axis, r0, r1, chord, thick, camber, twist, pitch, sweep })能建立真正帶有彎曲和扭轉的翼型(NACA 厚度)作為封閉實體,並將它們環繞排列。對於chord,thick和camber使用[root, tip]陣列。 - 齒輪:
GEO.gear({ teeth, module, width, bore, helix, spokes, rim })。將兩個齒輪嚙合,中心距為(z1 + z2) * module / 2,並以相反的齒數比轉動,偏移半個齒距。 - 管道、軟管、電線:
GEO.pipe(points, r)(封閉、有端蓋)。沿著實際結構從一個接口佈線到另一個接口,切勿穿過任何零件。 - 緊固件:在每個法蘭上使用
GEO.bolts(n, R, size, h)。彈簧:GEO.spring(R, wire, turns, len)。盒子:GEO.rbox(w, h, d, r)(總是圓角,避免尖角)。帶孔板:GEO.extrude(shape, depth)。 - 質量和慣性 來自幾何體:
GEO.volume(geo)提供體積和質心,乘以真實密度即可。
保持簡潔的規則:
- 每個零件都是一個封閉實體(車床輪廓封閉、外殼有厚度、管道有端蓋)。這是剖面圖能顯示實心剖面面的關鍵。開放的表面在切割時會顯示為空心。
- 沒有明顯的互相貫穿,也沒有任何零件漂浮。 每個零件都接觸其支撐物(法蘭靠在面上,軸在軸承中運轉,支柱接觸地板)。在特寫鏡頭中檢查接合處。
- 在視線焦點處添加細節:法蘭上的螺栓環、倒角、邊緣、標籤牌、燃油管、線束。大的平坦區域需要接縫、面板線條或紋理表面處理。
- 為機器提供一個底座:機架、支架、基座、工作台或放置在地板上的安裝座。現實生活中的機器很少漂浮。
- 預算:大約不超過 70 萬個三角形和 200 個網格。使用
GEO.merge/GEO.ring合併重複的靜態零件;將移動的群組分開。
4. 材質:絕非單一灰色
使用 MAT.* 預設材質;它們帶有測量過的反射率,因此鋼、鈦、鋁、黃銅和銅並排顯示時看起來截然不同。每個預設材質都接受覆寫參數(color、roughness、clearcoat、任何 MeshPhysicalMaterial 欄位)以及一個 finish(微粗糙度紋理:車床加工零件上的 turned 環、brushed 條紋、cast 斑點、smudge 污跡、knurl 滾花)。
| 用途 | 預設材質 |
|---|---|
| 軸、盤、加工零件 | MAT.steel、MAT.stainless、MAT.chrome |
| 外殼、鑄件 | MAT.aluminium、MAT.castIron、MAT.steelDark |
| 葉片、航太零件 | MAT.titanium、MAT.inconel (+ heatGlow) |
| 接頭、電線 | MAT.brass、MAT.copper、MAT.gold |
| 塗漆本體 | MAT.paint({ color })、MAT.satin、MAT.anodised({ color }) |
| 聚合物 | MAT.plastic、MAT.rubber、MAT.carbon |
| 窗戶、水晶 | MAT.glass、MAT.sapphire、MAT.ruby、MAT.enamel |
| 光、火焰、熱氣 | MAT.glow、heatGlow(material, kelvin) |
像工業設計師一樣選擇色調:主要是金屬色,搭配一兩種塗漆顏色,最多一個點綴色。高溫零件會根據解算器溫度發光(material.userData.heat.value = T)。
5. 解算器:只要真實數值
從輸入和狀態編寫一個小型純函數,計算出你顯示的每個數字,並使用真實單位和真實常數。使用 approach(一階滯後:加速過程、熱質量)或 rk4(彈簧、擒縱機構、鐘擺)來積分動態。然後用它來驅動運動:來自解算器的轉速轉動軸,來自解算器的流量移動粒子,來自解算器的溫度加熱金屬。
- 一個主要指標:
lab.hero({ label, unit, max }),然後在每個 tick 使用lab.setHero(value, { state, sub, bad })。state是兩到三個詞(加速中、起飛、阻塞流)。sub是一行,包含 2 到 3 個額外數字。 - 一個滑桿:
lab.slider({ id, label, min, max, value, unit, ends })。只有在機器確實需要時才添加第二個。 - 標籤懸掛在靜態零件上,位於切割平面上的點:
lab.label(obj, 'HP turbine', { at: [x, y, z], value: () => '12,979 rpm' })。 lab.how(words, equations)填寫「計算方式」:以通俗的語言說明模型,並在程式碼中列出方程式。說明簡化了什麼。- 如果某個運動太快而無法看到(例如 2,700 轉/分的風扇),則以慢速顯示,並在
how中說明(「轉速顯示為 1:60」)。切勿減慢數字的更新速度。 - 將操作點與你在步驟 1 中記錄的公開數據進行合理性檢查。如果誤差超過約 20%,則修正模型,而非修正數字。
6. 流體、熱量與效果
lab.flow({ paths, count, length, color, parent }):沿著你根據真實流路建構的路徑移動的條帶(空氣通過管道、血液通過幫浦、水通過研磨器)。根據解算器設定.speed,根據lab.state.cut設定.opacity,使其在剖面圖中可見。根據計算量(溫度、壓力)著色。lab.plume({ radius, length, dir, color: 'rocket' | 'jet', shocks }):帶有馬赫環的排氣;根據解算器設定.power。lab.sparks.emit(worldPos, n, speed)和lab.shed(mesh, v, w):用於破壞模式;火花受重力影響並彈跳;脫落的零件成為一個剛體,落在地板上。
7. 模式(工具包提供;你需要設定)
- 分解爆炸圖:每個
lab.add/lab.mesh都接受explode: [dx, dy, dz](單位:公尺,在父物體座標系中)。沿著組裝軸移動零件,最外層的外殼移動最遠,核心保持不動。在移動路徑上,任何零件都不應交叉。 - 剖面圖:在 Lab 上設定
cut: { a, b, center, reach }。a和b是被移除區域的方向:a = [0,0,1], b = [0,1,0]會移除朝向觀眾且向上的四分之一(最適合有軸的物體);b = a則移除一半。每個封閉實體的剖面面會自動繪製,並帶有淡色剖面線。 - X 光:在外層外殼上設定
xray: 'shell'使其變成帶有細線框的玻璃,從而在機器運作時顯示內部核心。 - 慢動作、標籤、暫停:這些功能是免費附帶的(按鍵 S、L、空白鍵)。可互動的部分是滑鼠軌道控制、滑桿和按鈕。
- 破壞:
lab.breakIt({ label, run(lab), reset(lab) })。run會改變解算器狀態(葉片脫落、閥門卡住、密封件損壞),解算器會計算後果。在主指標上顯示(bad: true),並伴有火花、碎片或震動。
8. 以批判眼光審視(必要)
渲染並仔細閱讀你製作的每張圖片。使用這些腳本:
node scripts/shot.mjs <name>.html out/review.png # 第一眼
node scripts/shot.mjs <name>.html out/cut.png --js "lab.toggle('cut', true); lab.toggle('labels', true)"
node scripts/shot.mjs <name>.html out/near.png --js "lab.setView({ az: 70, el: 8, dist: 0.8, target: [x, y, z] })"
node scripts/shot.mjs <name>.html out/film --film 1,4,8,12,17,22,26 # 影片劇照
至少進行四輪審查。每一輪都要檢查:整體視圖、帶標籤的剖面圖、分解圖、兩個接合處的特寫鏡頭,以及影片劇照。與你記憶中真實機器的產品照片進行比較:比例、零件數量、材質、光線在其上的表現。列出所有問題,修正它們,然後再次渲染。只有當清單為空時才停止。截圖腳本會輸出 FPS、零件數量和三角形數量;保持互動幀率高於 30。
9. 影片
lab.autoFilm({ runBeat, runSub, breakBeat, breakSub, sliderTo }) 會根據你的機器撰寫標準的 28 秒故事,包含編號的樂段和編輯註解:
- 一句話就是整個簡報(提示欄會自動打字)。
- Claude 建造機器(零件按組裝順序飛入,同時計數器攀升:「84 / 3,597 個零件」)。
- 運轉(游標拖動滑桿;主要指標數值根據解算器攀升)。
- 切開(剖面蓋、帶有即時數值的標籤)。
- 拆開(分解爆炸圖)。
- 破壞(游標點擊破壞按鈕;解算器決定什麼會失效)。
它會循環播放:最後一個攝影機關鍵幀會回到第一個。對於客製化影片,請使用 lab.film('name', { len, assemble, shots, events }):shots 是攝影機關鍵幀 { t, az, el, dist, target, fov },events 是 { t, beat: ['07', '粗體句子。', '灰色句子。'], set: { cut: 1 }, slider: { id, to, dur }, cursor: { to: 'slider:id:0.8' | 'button:break' | [x, y], click: true }, do(lab) {} }。
錄製(需要 Node 18+、帶有 Chromium 的 Playwright、ffmpeg):
node scripts/record.mjs <name>.html out/<name>.mp4 --music freepd:Classical/Easy_Lemon
node scripts/record.mjs <name>.html out/<name>-portrait.mp4 --w 1080 --h 1350
頁面以影片模式執行(?film=1):其時鐘僅在錄製器步進時移動,因此每一幀都是完整的,且每次拍攝的影片都相同。錄製器會保護每個截圖,防止無頭 GPU 可能回傳的洋紅色幀,並掃描完成的影片;如果任何一幀是粉紅色的,它會以非零狀態碼退出。音樂來自 FreePD,CC0 授權;無論如何請在影片中標註曲目資訊。
螢幕上的文字
頁面是編輯風格,而非儀表板:一個提示欄、一個帶有斜體副標題的襯線標題、一個帶有斜體單位和細長進度條的巨大數字、一行輔助數字、一個滑桿。像好的圖說一樣寫作:簡短、具體、無誇大、真實單位並加上空格(338 kN、1,750 K、4.2 L/min),千位數分開。樂段註解包含一個粗體句子和一個灰色句子。
交接時該說什麼
提供 HTML 檔案的路徑、影片指令、解算器相對於公開數據達到的操作點,以及你簡化了哪些部分。然後主動提議製作下一台機器。