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:
+8
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user