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
Publish 0.1.5
8 files changed
+169−3
README.md+9@@ -29,6 +29,15 @@ The two halves mate coplanar at the blade dock, with the project's molecules, ju  +## 3D Models++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) |+|---|---|+| [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) |+ ## Input power Four independent bench-supply input to the workcell control panel. Every rail is
diagrams/viewer-scaffold.pngadded⋯ 1 unchanged line ⋯
diagrams/viewer-workcell.pngadded⋯ 1 unchanged line ⋯
models/Control_Panel_Scaffold_v2.0.1.glbadded⋯ 1 unchanged line ⋯
models/Control_Panel_Workcell_v2.0.1.glbadded⋯ 1 unchanged line ⋯
package.json+2−2@@ -2,7 +2,7 @@ "slug": "base-scaffold-control-panel", "title": "Base Scaffold Control Panel", "type": "app",- "version": "0.1.4",+ "version": "0.1.5", "description": "The Base Scaffold System's control panel: electrical specifications and the connect/disconnect interface between a base scaffold and the workcell ecosystem.", "tags": [ "hardware",@@ -29,7 +29,7 @@ "uninstall": "./uninstall.sh" }, "hero": {- "headline": "BSS Control Panel",+ "headline": "Base Scaffold Control Panel", "subhead": "The electrical front door of the Base Scaffold System.", "screenshot": "docs/hero.png" }
page.json+1−1@@ -2,7 +2,7 @@ "slug": "base-scaffold-control-panel", "title": "Base Scaffold Control Panel", "type": "app",- "version": "0.1.1",+ "version": "0.1.5", "description": "The Base Scaffold System's control panel: electrical specifications and the connect/disconnect interface between a base scaffold and the workcell ecosystem.", "tags": [ "hardware",
readme.htmladded+157@@ -0,0 +1,157 @@+<!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]) {+ try { cache[name] = await loader.loadAsync(MODELS[name]); }+ catch (e) { document.getElementById('load').textContent = 'load failed: ' + e.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>