feat(jobs): in-browser STL / PLY / OBJ scan viewer

Clinics upload intraoral scans as raw STL/PLY/OBJ and labs need to see
what was captured before accepting the case. Built a tooth-level 3D
preview that runs entirely in the browser — no server-side rendering,
no extra service, just three.js + react-three-fiber driven off the
existing download proxy.

Component (src/components/stl-viewer.tsx)
  - Detects format from the filename (.stl / .ply / .obj).
  - fetch() → ArrayBuffer → STLLoader / PLYLoader / OBJLoader.parse.
  - OBJ comes back as a Group; merge the child meshes into a single
    BufferGeometry (positions concat) so all three formats render
    through the same mesh path.
  - Scene: dark background, ambient + two directional lights, a
    light-grey standard material, drei <Bounds> for auto-fit framing,
    OrbitControls with damping. A 'Sığdır' button in the corner re-fits
    the camera by remounting Bounds.
  - Cleanup: geometry.dispose() on unmount, fetch cancellation guard,
    inline error/loading states.

Wiring (job-files-panel.tsx)
  - VIEWABLE_RE = /\.(stl|ply|obj)$/i. Only those rows get the new
    eye-icon button.
  - STLViewer is loaded via next/dynamic with ssr:false so three.js
    (~500KB minified) only enters the bundle when the dialog actually
    opens. Mounting is also gated on viewerOpen, so closing the dialog
    frees the WebGL context.
  - Dialog is a tall (85vh) wide (max-w-5xl) shell with the filename
    + size in the header and the canvas filling the rest.

Deps added: three, @react-three/fiber, @react-three/drei (+ types).
This commit is contained in:
kovakmedya
2026-05-22 01:51:05 +03:00
parent 0e4033aa3f
commit 9e78d506ae
4 changed files with 701 additions and 2 deletions
+8 -1
View File
@@ -37,6 +37,8 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1",
"@tailwindcss/postcss": "^4.1.18",
"@tanstack/react-table": "^8.21.3",
"appwrite": "^24.2.0",
@@ -57,12 +59,16 @@
"recharts": "3.6.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"three": "^0.184.0",
"vaul": "^1.1.2",
"zod": "^4.3.2",
"zustand": "^5.0.9"
},
"pnpm": {
"onlyBuiltDependencies": ["sharp", "unrs-resolver"],
"onlyBuiltDependencies": [
"sharp",
"unrs-resolver"
],
"patchedDependencies": {
"node-fetch-native-with-agent@1.7.2": "patches/node-fetch-native-with-agent@1.7.2.patch"
}
@@ -72,6 +78,7 @@
"@types/node": "^25.0.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.184.1",
"eslint": "^9.39.2",
"eslint-config-next": "16.1.1",
"tailwindcss": "^4.1.18",