solidworks_mcp.ui.routers.viewer¶
solidworks_mcp.ui.routers.viewer ¶
Three.js 3D model viewer route for the Prefab CAD dashboard.
TODO: move _VIEWER_HTML to a Jinja2 template file at templates/viewer.html¶
Attributes¶
_VIEWER_HTML
module-attribute
¶
_VIEWER_HTML = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">\n<title>3D Model Viewer</title>\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { background: #0f172a; color: #94a3b8; font-family: system-ui, sans-serif; overflow: hidden; }\n#wrap { width: 100vw; height: 100vh; }\n#overlay { position: fixed; inset: 0; display: flex; flex-direction: column;\n align-items: center; justify-content: center; gap: 10px; pointer-events: none; }\n#status { font-size: 13px; text-align: center; max-width: 300px; line-height: 1.6; }\n#hint { position: fixed; bottom: 10px; left: 50%; transform: translateX(-50%);\n font-size: 11px; opacity: 0.35; user-select: none; }\n#fmt-badge { position: fixed; top: 8px; right: 10px; font-size: 10px;\n opacity: 0.4; letter-spacing: 0.05em; user-select: none; }\n</style></head><body>\n<div id="wrap"></div>\n<div id="overlay">\n <div id="icon" style="font-size:28px">⏳</div>\n <div id="status">Loading 3D model…</div>\n</div>\n<div id="hint">Drag to rotate · Scroll to zoom · Right-drag to pan</div>\n<div id="fmt-badge"></div>\n<script type="importmap">{"imports": {\n "three": "https://cdn.jsdelivr.net/npm/three@0.165.0/build/three.module.js",\n "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.165.0/examples/jsm/"\n}}</script>\n<script type="module">\nimport * as THREE from \'three\';\nimport { OrbitControls } from \'three/addons/controls/OrbitControls.js\';\nimport { GLTFLoader } from \'three/addons/loaders/GLTFLoader.js\';\nimport { STLLoader } from \'three/addons/loaders/STLLoader.js\';\n\nconst params = new URLSearchParams(location.search);\nconst pathParts = location.pathname.split(\'/\').filter(Boolean);\nconst pathSessionId = pathParts[pathParts.length - 1] || \'prefab-dashboard\';\nconst sessionId = params.get(\'session_id\') || pathSessionId;\nconst ts = params.get(\'t\') || \'0\';\nconst fmt = params.get(\'fmt\') || \'stl\';\n\nconst renderer = new THREE.WebGLRenderer({ antialias: true });\nrenderer.setPixelRatio(window.devicePixelRatio);\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\nrenderer.setSize(window.innerWidth, window.innerHeight);\ndocument.getElementById(\'wrap\').appendChild(renderer.domElement);\n\nconst scene = new THREE.Scene();\nscene.background = new THREE.Color(0x0f172a);\n\nconst camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 100000);\ncamera.position.set(0, 100, 250);\n\nconst controls = new OrbitControls(camera, renderer.domElement);\ncontrols.enableDamping = true;\ncontrols.dampingFactor = 0.06;\n\nscene.add(new THREE.AmbientLight(0xffffff, 0.8));\nconst dirLight = new THREE.DirectionalLight(0xffffff, 1.2);\ndirLight.position.set(1, 2, 1.5);\nscene.add(dirLight);\nconst fillLight = new THREE.DirectionalLight(0x8ab4f8, 0.4);\nfillLight.position.set(-1, -1, -1);\nscene.add(fillLight);\n\nconst stlMaterial = new THREE.MeshPhongMaterial({\n color: 0x3b82f6, specular: 0x1e3a5f, shininess: 60, side: THREE.DoubleSide\n});\n\nfunction fitCamera(object) {\n const box = new THREE.Box3().setFromObject(object);\n const size = box.getSize(new THREE.Vector3());\n const center = box.getCenter(new THREE.Vector3());\n const maxDim = Math.max(size.x, size.y, size.z) || 100;\n camera.position.set(center.x, center.y + maxDim * 0.6, center.z + maxDim * 2);\n camera.near = maxDim * 0.001;\n camera.far = maxDim * 200;\n camera.updateProjectionMatrix();\n controls.target.copy(center);\n controls.update();\n}\n\nfunction hideOverlay() { document.getElementById(\'overlay\').style.display = \'none\'; }\nfunction showError(msg) {\n document.getElementById(\'icon\').textContent = \'( )\';\n document.getElementById(\'status\').textContent = msg;\n}\nfunction onProgress(p) {\n const pct = p.total ? Math.round(p.loaded / p.total * 100) : 0;\n document.getElementById(\'status\').textContent = \'Loading\\u2026 \' + pct + \'%\';\n}\n\nif (fmt === \'glb\') {\n document.getElementById(\'fmt-badge\').textContent = \'GLB\';\n const glbUrl = location.origin + \'/previews/\' + sessionId + \'.glb?_t=\' + ts;\n new GLTFLoader().load(glbUrl, (gltf) => {\n hideOverlay();\n const model = gltf.scene;\n model.traverse((node) => {\n if (node.isMesh && node.material) {\n const mats = Array.isArray(node.material) ? node.material : [node.material];\n mats.forEach((m) => { m.side = THREE.DoubleSide; });\n }\n });\n scene.add(model);\n fitCamera(model);\n }, onProgress, () => showError(\'No 3D model file yet. Attach a SolidWorks model, then click Refresh 3D View.\'));\n} else if (fmt === \'stl\') {\n document.getElementById(\'fmt-badge\').textContent = \'STL\';\n const stlUrl = location.origin + \'/previews/\' + sessionId + \'.stl?_t=\' + ts;\n new STLLoader().load(stlUrl, (geometry) => {\n hideOverlay();\n geometry.computeBoundingBox();\n geometry.center();\n geometry.computeVertexNormals();\n const mesh = new THREE.Mesh(geometry, stlMaterial);\n scene.add(mesh);\n fitCamera(mesh);\n }, onProgress, () => showError(\'No 3D model file yet. Attach a SolidWorks model, then click Refresh 3D View.\'));\n} else {\n showError(\'No 3D model file yet. Attach a SolidWorks model, then click Refresh 3D View.\');\n}\n\nwindow.addEventListener(\'resize\', () => {\n camera.aspect = window.innerWidth / window.innerHeight;\n camera.updateProjectionMatrix();\n renderer.setSize(window.innerWidth, window.innerHeight);\n});\n(function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); })();\n</script></body></html>\n'
Functions¶
get_viewer
async
¶
get_viewer(session_id: str = Path(description='Session identifier for 3D model file routing')) -> HTMLResponse
Serve the embedded Three.js 3D model viewer page.