docs: module catalog, 3D model, touchscreen concept, and PCB preview

This commit is contained in:
2026-06-09 00:28:16 -04:00
parent c8d7541065
commit dbb3b9d84f
4 changed files with 1327 additions and 0 deletions
+510
View File
@@ -0,0 +1,510 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pi Multi-FX Pedal — 3D Model</title>
<style>
* { margin: 0; padding: 0; }
body {
background: #0d0d0d;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #e0e0e0;
}
#info {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
text-align: center;
pointer-events: none;
z-index: 10;
background: rgba(13,13,13,0.7);
padding: 8px 20px;
border-radius: 20px;
border: 1px solid #333;
font-size: 0.8rem;
backdrop-filter: blur(4px);
}
#info span { color: #666; }
#key {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
background: rgba(13,13,13,0.8);
border: 1px solid #333;
border-radius: 8px;
padding: 12px;
font-size: 0.7rem;
backdrop-filter: blur(4px);
min-width: 120px;
}
#key div { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.swatch { width: 12px; height: 12px; border-radius: 2px; display: inline-block; }
.swatch.lcd { background: #2a3a3a; }
.swatch.glass { background: rgba(79,195,247,0.3); }
.swatch.disp { background: #1a5a0a; }
.swatch.rpi { background: #66bb6a; }
.swatch.hat { background: #a78bfa; }
.swatch.box { background: #888; }
.swatch.jack { background: #fbbf24; }
</style>
</head>
<body>
<div id="key">
<div><span class="swatch glass"></span> Touch Glass</div>
<div><span class="swatch disp"></span> Display PCB</div>
<div><span class="swatch rpi"></span> Raspberry Pi 4B</div>
<div><span class="swatch hat"></span> I2S HAT</div>
<div><span class="swatch box"></span> 1590BB Enclosure</div>
<div><span class="swatch jack"></span> Audio Jacks</div>
<div style="margin-top:6px; color:#888;">🖱 Drag to orbit</div>
<div style="color:#888;">🔄 Scroll to zoom</div>
</div>
<div id="info">🎸 Pi Multi-FX Pedal · <span>1590BB + 5" DSI Touch + I2S HAT</span></div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
// ── Scene Setup ──────────────────────────────────────────────────
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0d0d0d);
const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 500);
camera.position.set(150, 100, 200);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.appendChild(renderer.domElement);
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.left = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.autoRotate = true;
controls.autoRotateSpeed = 1.0;
controls.target.set(0, 5, 0);
// ── Lighting ─────────────────────────────────────────────────────
const ambient = new THREE.AmbientLight(0x404060, 0.6);
scene.add(ambient);
const keyLight = new THREE.DirectionalLight(0xffffff, 2.5);
keyLight.position.set(100, 150, 80);
keyLight.castShadow = true;
keyLight.shadow.mapSize.width = 1024;
keyLight.shadow.mapSize.height = 1024;
scene.add(keyLight);
const fillLight = new THREE.DirectionalLight(0x8888ff, 0.6);
fillLight.position.set(-80, 40, -60);
scene.add(fillLight);
const rimLight = new THREE.DirectionalLight(0xffffff, 0.8);
rimLight.position.set(-50, 100, -100);
scene.add(rimLight);
const underLight = new THREE.DirectionalLight(0x4488ff, 0.25);
underLight.position.set(0, -100, 0);
scene.add(underLight);
// Ground grid
const gridHelper = new THREE.GridHelper(300, 40, 0x333, 0x222);
gridHelper.position.y = -15;
scene.add(gridHelper);
// ── Helper: make a box with rounded edges ────────────────────────
function roundedBox(w, h, d, r, color, emissive, roughness, metalness, opacity) {
const shape = new THREE.Shape();
const w2 = w/2 - r;
const h2 = h/2 - r;
shape.moveTo(-w2, h2);
shape.quadraticCurveTo(w/2, h2, w/2, 0);
shape.quadraticCurveTo(w/2, -h2, w2, -h2);
shape.lineTo(-w2, -h2);
shape.quadraticCurveTo(-w/2, -h2, -w/2, 0);
shape.quadraticCurveTo(-w/2, h2, -w2, h2);
const extrudeSettings = { depth: d, bevelEnabled: true, bevelThickness: r*0.5, bevelSize: r*0.3, bevelSegments: 6 };
const geo = new THREE.ExtrudeGeometry(shape, extrudeSettings);
geo.translate(0, 0, -d/2);
const mat = new THREE.MeshStandardMaterial({
color, emissive, roughness, metalness,
transparent: opacity !== undefined,
opacity: opacity || 1.0,
emissiveIntensity: 0.1,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
function makeLabel(text, color = '#4fc3f7', size = '14px') {
const div = document.createElement('div');
div.textContent = text;
div.style.color = color;
div.style.fontFamily = 'Courier New, monospace';
div.style.fontSize = size;
div.style.fontWeight = '600';
div.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
div.style.background = 'rgba(0,0,0,0.5)';
div.style.padding = '2px 8px';
div.style.borderRadius = '4px';
div.style.border = '1px solid ' + color;
return new CSS2DObject(div);
}
// ── Colors ───────────────────────────────────────────────────────
const C = {
glass: 0x88ccdd,
glassEdge: 0x446688,
pcb: 0x1a5a0a,
rpi: 0x66bb6a,
rpiDark: 0x338833,
hat: 0x8855cc,
hatDark: 0x553388,
box: 0x888888,
boxDark: 0x555555,
jack: 0xfbbf24,
jackDark: 0xcc9900,
accent: 0x4fc3f7,
gold: 0xccaaff,
};
// ── Build the pedal ──────────────────────────────────────────────
const pedal = new THREE.Group();
// 1. ENCLOSURE — 1590BB (140×78×48mm external)
// Bottom box
const boxGeo = new THREE.BoxGeometry(140, 48, 78);
const boxMat = new THREE.MeshStandardMaterial({
color: 0x666666,
roughness: 0.7,
metalness: 0.3,
emissive: 0x222222,
emissiveIntensity: 0.05,
});
const boxMesh = new THREE.Mesh(boxGeo, boxMat);
boxMesh.position.y = -24;
boxMesh.castShadow = true;
boxMesh.receiveShadow = true;
pedal.add(boxMesh);
// Top face (panel) — slightly recessed
const panelMat = new THREE.MeshStandardMaterial({
color: 0x555555,
roughness: 0.6,
metalness: 0.4,
emissive: 0x111111,
emissiveIntensity: 0.05,
});
const panelGeo = new THREE.BoxGeometry(136, 2, 74);
const panelMesh = new THREE.Mesh(panelGeo, panelMat);
panelMesh.position.y = 0;
panelMesh.castShadow = true;
panelMesh.receiveShadow = true;
pedal.add(panelMesh);
// Enclosure edge bevels (visual)
const edgeMat = new THREE.MeshStandardMaterial({
color: 0x444444,
roughness: 0.8,
metalness: 0.2,
});
// 2. I2S HAT (bottom layer) — 65×56mm, sits inside enclosure
const hatMat = new THREE.MeshStandardMaterial({
color: C.hat,
roughness: 0.4,
metalness: 0.3,
emissive: C.hat,
emissiveIntensity: 0.05,
});
const hatGeo = new THREE.BoxGeometry(65, 1.6, 56);
const hatMesh = new THREE.Mesh(hatGeo, hatMat);
hatMesh.position.set(0, 2.5, 0);
hatMesh.castShadow = true;
hatMesh.receiveShadow = true;
pedal.add(hatMesh);
// HAT silkscreen marks
const hatchf = new THREE.Mesh(
new THREE.BoxGeometry(0.5, 0.15, 0.5),
new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0xffaa00, emissiveIntensity: 0.3 })
);
[-20, -10, 0, 10, 20].forEach(x => {
[-15, 0, 15].forEach(z => {
const dot = hatchf.clone();
dot.position.set(x, 3.3, z);
pedal.add(dot);
});
});
// HAT components (IC blocks)
function addIC(x, z, w, d, h, color, label) {
const mat = new THREE.MeshStandardMaterial({
color, roughness: 0.3, metalness: 0.5,
emissive: color, emissiveIntensity: 0.08,
});
const body = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), mat);
body.position.set(x, 3.3 + h/2, z);
body.castShadow = true;
pedal.add(body);
const lbl = makeLabel(label, '#a78bfa', '10px');
lbl.position.set(x, 6, z);
pedal.add(lbl);
}
addIC(-15, -8, 8, 6, 1.5, 0xaa66ee, 'U3 ADC');
addIC(15, -8, 8, 6, 1.5, 0x66aaff, 'U4 DAC');
addIC(-15, 8, 10, 10, 2.0, 0x66bb66, 'U2 Pre');
addIC(10, 8, 7, 6, 1.8, 0xee5555, 'U1 LDO');
// Jacks on I2S HAT — side mounted cylinders
function addJack(x, z, angle, label) {
const mat = new THREE.MeshStandardMaterial({
color: C.jack,
roughness: 0.3,
metalness: 0.6,
emissive: C.jack,
emissiveIntensity: 0.05,
});
const cylinder = new THREE.Mesh(new THREE.CylinderGeometry(4, 4, 12, 16), mat);
cylinder.rotation.z = Math.PI/2;
cylinder.position.set(x, 5, z);
cylinder.castShadow = true;
pedal.add(cylinder);
// Nut
const nutMat = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.4, metalness: 0.7 });
const nut = new THREE.Mesh(new THREE.CylinderGeometry(5, 5, 2, 6), nutMat);
nut.rotation.z = Math.PI/2;
nut.position.set(x, 5, z);
pedal.add(nut);
const lbl = makeLabel(label, '#fbbf24', '10px');
lbl.position.set(x + (x < 0 ? -10 : 10), 8, z);
pedal.add(lbl);
}
addJack(-68, -10, 0, 'J IN');
addJack(-68, 10, 0, 'J SEND');
addJack(68, -10, 0, 'J RETURN');
addJack(68, 10, 0, 'J OUT');
// 3. RPi 4B (middle layer) — 85×56mm
const rpiMat = new THREE.MeshStandardMaterial({
color: C.rpi,
roughness: 0.5,
metalness: 0.2,
emissive: C.rpi,
emissiveIntensity: 0.03,
});
const rpiGeo = new THREE.BoxGeometry(85, 1.6, 56);
const rpiMesh = new THREE.Mesh(rpiGeo, rpiMat);
rpiMesh.position.set(0, 7, 0);
rpiMesh.castShadow = true;
rpiMesh.receiveShadow = true;
pedal.add(rpiMesh);
// RPi standoffs (4x)
const standoffMat = new THREE.MeshStandardMaterial({
color: 0x888888, roughness: 0.3, metalness: 0.6,
});
for (let x of [-28, 28]) {
for (let z of [-20, 20]) {
const standoff = new THREE.Mesh(new THREE.CylinderGeometry(2.5, 2.5, 5, 8), standoffMat);
standoff.position.set(x, 5.5, z);
pedal.add(standoff);
}
}
// GPIO stacking header
const hdrMat = new THREE.MeshStandardMaterial({
color: 0x222222, roughness: 0.3, metalness: 0.2,
});
const hdr = new THREE.Mesh(new THREE.BoxGeometry(4, 6, 50), hdrMat);
hdr.position.set(22, 10, 0);
pedal.add(hdr);
const hdr2 = new THREE.Mesh(new THREE.BoxGeometry(4, 6, 50), hdrMat);
hdr2.position.set(-22, 10, 0);
pedal.add(hdr2);
// DSI port indicator
const dsiMat = new THREE.MeshStandardMaterial({
color: 0x4488ff, roughness: 0.3, metalness: 0.1,
});
const dsiPort = new THREE.Mesh(new THREE.BoxGeometry(4, 1, 10), dsiMat);
dsiPort.position.set(-38, 8, 5);
pedal.add(dsiPort);
const rpiLbl = makeLabel('RPi 4B', '#66bb6a', '12px');
rpiLbl.position.set(0, 12, 0);
pedal.add(rpiLbl);
// 4. Display PCB (Waveshare 5" — 102×76mm)
const dispMat = new THREE.MeshStandardMaterial({
color: C.pcb,
roughness: 0.4,
metalness: 0.2,
emissive: C.pcb,
emissiveIntensity: 0.03,
});
const dispGeo = new THREE.BoxGeometry(102, 1.6, 76);
const dispMesh = new THREE.Mesh(dispGeo, dispMat);
dispMesh.position.set(0, 13, 0);
dispMesh.castShadow = true;
dispMesh.receiveShadow = true;
pedal.add(dispMesh);
// Display standoffs
for (let x of [-40, 40]) {
for (let z of [-30, 30]) {
const standoff = new THREE.Mesh(new THREE.CylinderGeometry(2, 2, 5, 8), standoffMat);
standoff.position.set(x, 11, z);
pedal.add(standoff);
}
}
// DSI ribbon cable (visual)
const ribbonMat = new THREE.MeshStandardMaterial({
color: 0x8855cc, roughness: 0.5, metalness: 0.1,
transparent: true, opacity: 0.7,
});
const ribbon = new THREE.Mesh(new THREE.BoxGeometry(3, 1, 40), ribbonMat);
ribbon.position.set(-40, 12.5, -20);
ribbon.rotation.x = 0.3;
pedal.add(ribbon);
const ribbon2 = new THREE.Mesh(new THREE.BoxGeometry(3, 1, 30), ribbonMat);
ribbon2.position.set(-40, 10, -35);
ribbon2.rotation.x = 0.6;
pedal.add(ribbon2);
const dispLbl = makeLabel('5" DSI Display', '#33aa33', '12px');
dispLbl.position.set(0, 16, 0);
pedal.add(dispLbl);
// 5. Touch Glass (front face — fills enclosure top)
const glassMat = new THREE.MeshStandardMaterial({
color: C.glass,
roughness: 0.1,
metalness: 0.0,
transparent: true,
opacity: 0.15,
emissive: C.glass,
emissiveIntensity: 0.05,
});
const glassGeo = new THREE.BoxGeometry(120, 1.5, 65);
const glassMesh = new THREE.Mesh(glassGeo, glassMat);
glassMesh.position.set(0, 15.5, 0);
glassMesh.castShadow = true;
glassMesh.receiveShadow = true;
pedal.add(glassMesh);
// Glass edge bezel
const bezelMat = new THREE.MeshStandardMaterial({
color: 0x222233, roughness: 0.4, metalness: 0.3,
});
const bezel = new THREE.Mesh(new THREE.BoxGeometry(122, 0.5, 67), bezelMat);
bezel.position.set(0, 15, 0);
pedal.add(bezel);
// Simulated UI on glass — floating colored rectangles
function addUIPixel(x, z, w, h, color) {
const m = new THREE.MeshStandardMaterial({
color, roughness: 0.3, metalness: 0.0,
emissive: color, emissiveIntensity: 0.3,
transparent: true, opacity: 0.6,
});
const r = new THREE.Mesh(new THREE.BoxGeometry(w, 0.1, h), m);
r.position.set(x, 16.3, z);
pedal.add(r);
}
// Simulated preset name bar
addUIPixel(-30, 18, 60, 8, 0x4fc3f7);
// Bypass button glow
addUIPixel(-45, -5, 20, 10, 0x66bb6a);
// Volume slider
addUIPixel(5, -5, 40, 4, 0x4fc3f7);
// Volume knob
addUIPixel(30, -5, 6, 6, 0x4fc3f7);
// FX chain pills
addUIPixel(-30, -18, 25, 6, 0xa78bfa);
addUIPixel(-5, -18, 18, 6, 0x66bb6a);
addUIPixel(15, -18, 22, 6, 0x4fc3f7);
// Bottom bar
addUIPixel(-30, -28, 70, 6, 0x4488cc);
const glassLbl = makeLabel('Touch Glass', '#88ccdd', '12px');
glassLbl.position.set(60, 20, -30);
pedal.add(glassLbl);
// ── Position the whole pedal ───────────────────────────────────
pedal.position.set(0, 10, 0);
scene.add(pedal);
// ── Label: overall product name ────────────────────────────────
const titleLbl = makeLabel('Pi Multi-FX Pedal · 1590BB', '#4fc3f7', '18px');
titleLbl.position.set(0, 45, 80);
scene.add(titleLbl);
const subtitleLbl = makeLabel('5" DSI Touch · PCM1808+PCM5102 I2S HAT · 32 FX', '#888', '12px');
subtitleLbl.position.set(0, 40, 80);
scene.add(subtitleLbl);
// ── Responsive ──────────────────────────────────────────────────
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
});
// Toggle auto-rotate on click
renderer.domElement.addEventListener('click', () => {
controls.autoRotate = !controls.autoRotate;
});
// ── Animate ──────────────────────────────────────────────────────
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
animate();
</script>
</body>
</html>