app
Base Scaffold Control Panel
Public Made by Adomby adom
The Base Scaffold System's control panel: electrical specifications and the connect/disconnect interface between a base scaffold and the workcell ecosystem.
← Commit history
3D viewer moved to docs/3d-viewer.html (readme.html hijacked the page body)
2 files changed
+165−2
README.md+2−2@@ -34,9 +34,9 @@ The two halves mate coplanar at the blade dock, with the project's molecules, ju The exact board models from the component pages, in an interactive viewer (orbit, zoom, animations, bottom light) — click either preview to open it: -| [](/adom/base-scaffold-control-panel/render/readme.html) | [](/adom/base-scaffold-control-panel/render/readme.html) |+| [](/adom/base-scaffold-control-panel/render/docs/3d-viewer.html) | [](/adom/base-scaffold-control-panel/render/docs/3d-viewer.html) | |---|---|-| [Control Panel: Workcell — open 3D viewer](/adom/base-scaffold-control-panel/render/readme.html) | [Control Panel: Scaffold — open 3D viewer](/adom/base-scaffold-control-panel/render/readme.html) |+| [Control Panel: Workcell — open 3D viewer](/adom/base-scaffold-control-panel/render/docs/3d-viewer.html) | [Control Panel: Scaffold — open 3D viewer](/adom/base-scaffold-control-panel/render/docs/3d-viewer.html) | ## Input power
docs/3d-viewer.htmladded+163@@ -0,0 +1,163 @@+<!doctype html>+<html lang="en">+<head>+<meta charset="utf-8">+<meta name="viewport" content="width=device-width, initial-scale=1">+<title>Base Scaffold Control Panel — 3D</title>+<style>+* { margin: 0; padding: 0; box-sizing: border-box; }+html, body { height: 100%; }+body {+ background: #0d1117; color: #e6edf3; overflow: hidden;+ font-family: 'Segoe UI', system-ui, sans-serif; font-size: 14px;+}+#bar {+ position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 10;+ display: flex; align-items: center; gap: 10px; padding: 0 14px;+ background: rgba(22,27,34,.92); border-bottom: 1px solid #30363d;+}+#bar .t { font-weight: 700; font-size: 14px; }+#bar .t span { color: #00b8b0; }+.seg { display: flex; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }+.seg button {+ padding: 6px 14px; background: none; border: none; color: #8b949e;+ font: inherit; font-size: 12.5px; cursor: pointer;+}+.seg button + button { border-left: 1px solid #30363d; }+.seg button.on { background: rgba(0,184,176,.2); color: #00e6dc; font-weight: 700; }+.pill {+ margin-left: auto; padding: 5px 12px; border-radius: 999px; border: 1px solid #30363d;+ background: #1c2128; color: #8b949e; font-size: 12px; cursor: pointer;+}+.pill.on { color: #00e6dc; border-color: rgba(0,184,176,.4); }+#hint { position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%); z-index: 10;+ font-size: 11.5px; color: #8b949e; background: rgba(22,27,34,.8); padding: 5px 12px; border-radius: 999px; }+#load { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;+ color: #8b949e; z-index: 5; pointer-events: none; }+canvas { display: block; }+</style>+</head>+<body>+<div id="bar">+ <span class="t">Control Panel <span>3D</span></span>+ <div class="seg" id="boards">+ <button data-b="workcell" class="on">Workcell</button>+ <button data-b="scaffold">Scaffold</button>+ </div>+ <button class="pill" id="anim">Animation on</button>+ <button class="pill" id="bottom">Bottom light</button>+</div>+<div id="load">loading model…</div>+<div id="hint">drag to orbit · wheel to zoom · right-drag to pan</div>+<script type="importmap">{"imports":{"three":"https://unpkg.com/[email protected]/build/three.module.js","three/addons/":"https://unpkg.com/[email protected]/examples/jsm/"}}</script>+<script type="module">+import * as THREE from 'three';+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';+import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';++const MODELS = {+ workcell: 'models/Control_Panel_Workcell_v2.0.1.glb',+ scaffold: 'models/Control_Panel_Scaffold_v2.0.1.glb',+};++const renderer = new THREE.WebGLRenderer({ antialias: true });+renderer.outputColorSpace = THREE.SRGBColorSpace;+renderer.setPixelRatio(Math.min(devicePixelRatio, 2));+document.body.appendChild(renderer.domElement);++const scene = new THREE.Scene();+// component-viewer style vertical gradient background+const bgCanvas = document.createElement('canvas');+bgCanvas.width = 2; bgCanvas.height = 256;+const bctx = bgCanvas.getContext('2d');+const grad = bctx.createLinearGradient(0, 0, 0, 256);+grad.addColorStop(0, '#1e2a3d'); grad.addColorStop(0.2, '#1e2a3d'); grad.addColorStop(1, '#0a121f');+bctx.fillStyle = grad; bctx.fillRect(0, 0, 2, 256);+scene.background = new THREE.CanvasTexture(bgCanvas);++scene.add(new THREE.HemisphereLight(0xddeeff, 0x222428, 1.1));+scene.add(new THREE.AmbientLight(0xffffff, 0.65));+const key = new THREE.DirectionalLight(0xfff0d8, 0.95);+key.position.set(0.5, 1, 0.8); scene.add(key);+const bottomLight = new THREE.DirectionalLight(0xbfd7ff, 0.0);+bottomLight.position.set(0, -1, 0.2); scene.add(bottomLight);++const camera = new THREE.PerspectiveCamera(35, 1, 0.001, 100);+const controls = new OrbitControls(camera, renderer.domElement);+controls.enableDamping = true;+controls.zoomToCursor = true;++const loader = new GLTFLoader();+const draco = new DRACOLoader();+draco.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');+loader.setDRACOLoader(draco);++const cache = {};+let current = null, mixer = null, animOn = true;+const clock = new THREE.Clock();++async function show(name) {+ document.getElementById('load').style.display = 'flex';+ if (current) { scene.remove(current.root); current = null; mixer = null; }+ if (!cache[name]) {+ // relative resolves under /render/; fall back to the blob route if that 404s+ const candidates = ['../' + MODELS[name], '/blob/app/base-scaffold-control-panel/' + MODELS[name]];+ let lastErr = null;+ for (const url of candidates) {+ try { cache[name] = await loader.loadAsync(url); lastErr = null; break; }+ catch (e) { lastErr = e; }+ }+ if (lastErr) { document.getElementById('load').textContent = 'load failed: ' + lastErr.message; return; }+ }+ const g = cache[name];+ const root = g.scene;+ scene.add(root);+ current = { root, gltf: g };+ if (g.animations && g.animations.length) {+ mixer = new THREE.AnimationMixer(root);+ mixer.clipAction(g.animations[0]).play();+ }+ const box = new THREE.Box3().setFromObject(root);+ const c = box.getCenter(new THREE.Vector3());+ const r = box.getSize(new THREE.Vector3()).length() / 2;+ controls.target.copy(c);+ camera.position.set(c.x + r * 0.9, c.y - r * 1.1, c.z + r * 1.4);+ camera.near = r / 100; camera.far = r * 100;+ camera.up.set(0, 0, 1);+ camera.updateProjectionMatrix();+ controls.update();+ document.getElementById('load').style.display = 'none';+}++document.querySelectorAll('#boards button').forEach(b => b.onclick = () => {+ document.querySelectorAll('#boards button').forEach(o => o.classList.toggle('on', o === b));+ show(b.dataset.b);+});+const animBtn = document.getElementById('anim');+animBtn.classList.add('on');+animBtn.onclick = () => { animOn = !animOn; animBtn.classList.toggle('on', animOn); animBtn.textContent = animOn ? 'Animation on' : 'Animation off'; };+const bottomBtn = document.getElementById('bottom');+bottomBtn.onclick = () => {+ const on = bottomLight.intensity === 0;+ bottomLight.intensity = on ? 0.9 : 0;+ bottomBtn.classList.toggle('on', on);+};++function frame() {+ requestAnimationFrame(frame);+ const w = innerWidth, h = innerHeight;+ if (renderer.domElement.width !== w * renderer.getPixelRatio()) {+ renderer.setSize(w, h); camera.aspect = w / h; camera.updateProjectionMatrix();+ }+ const dt = clock.getDelta();+ if (mixer && animOn) mixer.update(dt);+ controls.update();+ renderer.render(scene, camera);+}+frame();+show('workcell');+addEventListener('resize', () => renderer.setSize(innerWidth, innerHeight));+</script>+</body>+</html>