跳至主要內容
Gray Tsao

Orthoshot:一顆零件的十張標準視角圖,一次拖放

報價要圖的時候,需要圖的那個人手上通常沒有 CAD。這是一個在瀏覽器裡開 STEP、量尺寸、估重量、一鍵匯出十張標準視角的工具——檔案不上傳,因為零件幾何是別人的財產。

要圖的人,手上沒有 CAD

報價、工單、採購溝通,每一次都需要同一組東西:一顆零件的標準視角圖,加上尺寸跟重量。

出這組東西的流程長這樣——開 CAD、把模型擺到正視角、截圖、轉到後視角、截圖,左、右、上、下,再來四張等角,然後一張一張命名,最後壓成一個壓縮檔。一顆零件幾分鐘,一張報價單上有八顆零件就是半小時。

更麻煩的是需要這組東西的人:業務、採購、正在跟客戶對話的那一個。他們手上通常沒有 CAD 授權,也不該為了截十張圖去學怎麼操作 CAD。所以實際流程是「請工程師幫我出圖」,然後等。

Orthoshot 想砍掉的就是這段等待。

拖進去,按一顆按鈕

.step / .stp / .stl / .glb / .gltf 拖進視窗——任何位置都可以,不用瞄準某個框——然後按匯出。

拿回來的是 <模型名>_snapshots.zip,裡面十張 PNG:

  • 六張正交:frontbackleftrighttopbottom
  • 四張等角:iso_front_right_topiso_front_left_topiso_back_right_topiso_back_left_top

解析度 1024 或 2048,背景白底或透明,擷取途中可以中止。

同一個畫面上還有:

  • 尺寸:寬 (X) / 高 (Y) / 深 (Z)、實際體積、包圍盒體積、頂點數與面數
  • 重量:內建 63 筆工程材料密度,分成鋁、不鏽鋼、銅與黃銅、鋼、工具鋼/鈦/鎳合金、塑膠、硬脆材料/陶瓷七組,也可以自己輸入密度
  • 單位:頂端 mm / inch 切換並記在 localStorage;3D 畫面左下角兩種單位同時顯示
  • 剖面:選 X / Y / Z 切面軸、拖滑桿移動切面位置、翻轉保留側;切面會填色,不是變成一個空殼

檔案不上傳

沒有伺服器、沒有帳號、沒有上傳。STEP 用 occt-import-js(OpenCascade 的 WASM 版)在瀏覽器裡解析,算圖走 WebGL,壓縮檔也在瀏覽器裡打包。

這不是為了省主機費。零件幾何是客戶的財產,而一個「傳上去就好」的工具,在很多公司裡是直接過不了的——與其解釋伺服器上的檔案多久會刪、誰看得到,不如讓那段對話不存在。

代價要一起講:

  • occt 的 WASM 大約 7.6 MB,第一次開 STEP 得先下載它
  • 需要支援 WebGL2 的瀏覽器。偵測不到時會直接說明原因(最常見的是公司電腦把硬體加速關掉了),而不是給一片空白
  • 檔案大小的上限就是使用者那台機器的記憶體,沒有「加一台機器」這個選項

截圖不等於出圖

那十張圖不是抓畫面。

第一個理由是姿態。使用者在畫面上會一直轉模型——那是他在看零件,不是在構圖。如果快照跟著畫面走,同一顆零件在不同人手上會匯出十組角度不一樣的圖,那就不再是「標準視角」。所以快照一律以模型的原始姿態拍攝:相機是臨時建的正交相機,依包圍盒自動套框,near / far 用包圍盒八個角點在視線方向上的投影收緊。畫面上怎麼轉、怎麼縮放,都不影響輸出。

第二個理由踩得比較痛:顏色

離屏渲染出來的每一張 PNG,都明顯比畫面上看到的暗。

原因藏在 three 的渲染路徑裡:renderer.toneMappingrenderer.outputColorSpace 只有在畫到 canvas(或 XR target)時才會套用;畫到其他 render target 時,three 強制 NoToneMapping 加線性工作色彩空間。所以離屏那條路寫出來的是原始線性、沒有 tone mapping 的像素,而 PNG 檢視器會把那些位元組當成 sRGB 讀——就暗了。

修法不是自己寫一個 shader 把色彩空間轉換內聯進去。試過,編譯不過:three 已經對每一個非 raw 的 material 前置了 colorspace_pars_fragment,再內聯一次就是重複的函式定義。

正確做法是走 three 自己出的 OutputPass。它是一個 RawShaderMaterial,會去讀 renderer 當下的 toneMappingoutputColorSpacetoneMappingExposure,然後套用跟 canvas 完全相同的 chunk、完全相同的順序。畫面跟 PNG 一致不是調出來的,是同一段程式碼保證的。

還有兩個只有離屏才會遇到的細節:

  • render target 要自己開 stencilBuffer(three 的預設是 false),否則剖面的填色是靠 stencil test 畫的,在快照裡會整片消失
  • 場景要先畫到 half-float、4× multisample 的 target,才對得上開了 antialias 的 canvas;缺 EXT_color_buffer_float 這類擴充時退回 8-bit

那 83 行單位偵測,最後全刪了

一開始我以為 occt-import-js 只是把 STEP 的座標原樣吐出來,不做單位轉換。於是寫了一個 83 行的模組去讀 STEP header 裡宣告的長度單位,再把座標換算成公釐。

前提是錯的。occt 會把幾何轉換成它 linearUnit 參數指定的單位,而那個參數預設就是公釐。也就是說,我在已經是公釐的數字上又乘了一次換算係數。Onshape 預設以公尺匯出——那類檔案的尺寸直接放大一千倍。

拆掉這個假設花了三個 commit:先把換算拿掉、把偵測到的單位降級成「僅供顯示」的欄位,然後認清那個欄位根本沒人讀,連同 83 行跟它的測試一起刪掉。

留下來的是一行:

const STEP_PARAMS = { linearUnit: 'millimeter' }

明確傳,而不是靠 occt 的預設。差別在於「幾何一律是公釐」從一個繼承來的巧合,變成一句寫下來的契約——下一個讀這段程式碼的人(包括三個月後的我)不必翻函式庫的原始碼,才知道為什麼下游可以放心不做轉換。

那次刪掉的行數比加的多。這通常是好事。

現在還不行的事

  • 剖面需要 stencil buffer,也就是 WebGL2。偵測不到 WebGL 時會顯示原因而不是空白畫面,但剖面就是沒有。
  • 測試只涵蓋純函式:兩個檔案、19 個測試,範圍是尺寸格式化與體積/重量計算。DOM 與 WebGL 完全沒有自動測試——上面那些快照管線的坑,全部是靠肉眼比對抓出來的。
  • 沒有 CI。 repo 裡沒有 workflow,npm run linttsc -bvitest 都得自己記得跑。
  • CSP 目前是 Report-Only,而且是刻意的。 Draco decoder 從 www.gstatic.com 載入並以 blob worker 實體化,occt 的 WASM 也走類似路徑。強制模式只要有一條指令寫錯,Draco 壓縮的 glTF 就會無聲地載不起來——沒有錯誤訊息,只有一個永遠轉不完的載入狀態。要轉成強制模式,得先實際載一個 Draco .glb 跟一個 .step,確認 console 沒有違規回報。
  • 實際體積是 mesh 三角形的 signed tetrahedron 累加,只有網格封閉時才準。面板上有一行註記說明這件事,但工具不會針對某個檔案偵測並警告——開放網格(例如某些掃描來的 STL)會給出一個看起來很合理、但錯的重量。

最後一條是現在最想修的。一個安靜給錯數字的重量欄位,比沒有重量欄位糟。

試試看

https://orthoshot.grayswork.com(繁體中文) · https://orthoshot.grayswork.com/en/(English)

不用註冊。拖一顆零件進去,幾秒鐘就知道它對你有沒有用。

聯絡

說點具體的

寄信最快。我會在一個工作天內回覆,通常帶著比答案更多的問題。

或直接在這裡留言
所在地
新北,台灣 · UTC+8
狀態
2026年6月畢業 · 軟體工程職缺洽談中
Gray Tsao© 2026 · 最後更新 2026年8月