From a7fd9dbac2a493418341ca537560df96033b0e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Mon, 30 Mar 2026 07:54:26 +0000 Subject: [PATCH] feat: integrate real KiCad GLB models into WebGL PCB scene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ICComponent: uses qfp32.glb (large) or soic8.glb (small) with fallback to procedural - Capacitor: uses capacitor_0805.glb with scroll-reactive glow - Resistor: uses resistor_0603.glb - LED: uses led_0603.glb + dynamic PointLight for glow effect - All models preloaded via useGLTF.preload() - Fallback to procedural geometry if GLB fails to load - Models from KiCad 10.0 official 3D library (STEP → GLB via cascadio) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/WebGLBackground.tsx | 163 +++++++++++++++-------------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/src/components/WebGLBackground.tsx b/src/components/WebGLBackground.tsx index 9e514e3..bb3578a 100644 --- a/src/components/WebGLBackground.tsx +++ b/src/components/WebGLBackground.tsx @@ -1,6 +1,6 @@ import { useRef, useMemo, useEffect, useState } from 'react'; import { Canvas, useFrame, useThree } from '@react-three/fiber'; -import { Text } from '@react-three/drei'; +import { Text, useGLTF } from '@react-three/drei'; import { EffectComposer, Bloom, ChromaticAberration, Scanline } from '@react-three/postprocessing'; import { BlendFunction } from 'postprocessing'; import * as THREE from 'three'; @@ -297,37 +297,41 @@ function Vias() { /* ---------- 3D Electronic components on PCB ---------- */ -/* IC / Microcontroller (black rectangle with pins) */ +/* ---------- GLB Model loaders ---------- */ + +/* IC — uses SOIC-8 or QFP-32 GLB model */ function ICComponent({ position, size = [2, 0.3, 1.2], label, color = '#1a1a1a' }: { position: [number, number, number]; size?: [number, number, number]; label: string; color?: string; }) { const groupRef = useRef(null); - const pins = Math.floor(size[0] / 0.3); + const isLarge = size[0] > 1.5; + const modelUrl = isLarge ? '/assets/models3d/qfp32.glb' : '/assets/models3d/soic8.glb'; + + let glb: any = null; + try { glb = useGLTF(modelUrl); } catch {} useFrame(({ clock }) => { if (!groupRef.current) return; const cameraZ = -scrollProgress * 90; const dist = Math.abs(position[2] - cameraZ); const glow = Math.max(0, 1 - dist / 6); - // Subtle float when active groupRef.current.position.y = position[1] + glow * Math.sin(clock.getElapsedTime() * 2) * 0.03; }); + const scale = isLarge ? 0.4 : 0.25; + return ( - {/* IC body */} - - - - - {/* Dot (pin 1 marker) */} - - - - - {/* Silkscreen label on IC body */} + {glb?.scene ? ( + + ) : ( + + + + + )} {label} - {/* Pins — both sides */} - {Array.from({ length: pins }).map((_, i) => ( - - - - - - - - - - - ))} - {/* Solder pads glow */} @@ -361,65 +351,77 @@ function ICComponent({ position, size = [2, 0.3, 1.2], label, color = '#1a1a1a' ); } -/* Capacitor (cylinder) */ +/* Capacitor — uses capacitor_0805 GLB */ function Capacitor({ position, radius = 0.25, height = 0.5, color = '#2a4a8a' }: { position: [number, number, number]; radius?: number; height?: number; color?: string; }) { - const ref = useRef(null); + const ref = useRef(null); + let glb: any = null; + try { glb = useGLTF('/assets/models3d/capacitor_0805.glb'); } catch {} - useFrame(({ clock }) => { + useFrame(() => { if (!ref.current) return; const cameraZ = -scrollProgress * 90; const dist = Math.abs(position[2] - cameraZ); const glow = Math.max(0, 1 - dist / 6); - (ref.current.material as THREE.MeshStandardMaterial).emissiveIntensity = glow * 0.5; + ref.current.children.forEach(child => { + if ((child as any).material) { + (child as any).material.emissiveIntensity = glow * 0.5; + } + }); }); + const scale = radius * 4; + return ( - - - - - - {/* Top marking */} - - - - - {/* Stripe */} - - - - + + {glb?.scene ? ( + + ) : ( + + + + + )} ); } -/* Resistor (small rectangle with color bands) */ +/* Resistor — uses resistor_0603 GLB */ function Resistor({ position, rotation = [0, 0, 0] }: { position: [number, number, number]; rotation?: [number, number, number] }) { + let glb: any = null; + try { glb = useGLTF('/assets/models3d/resistor_0603.glb'); } catch {} + return ( - - - - - {/* Solder pads */} - - - - - - - - + {glb?.scene ? ( + + ) : ( + <> + + + + + + + + + + + + + + )} ); } -/* LED (small dome with glow) */ +/* LED — uses led_0603 GLB + glow */ function LED({ position, color = '#5bd1d8' }: { position: [number, number, number]; color?: string }) { - const ref = useRef(null); + const ref = useRef(null); const threeColor = useMemo(() => new THREE.Color(color), [color]); + let glb: any = null; + try { glb = useGLTF('/assets/models3d/led_0603.glb'); } catch {} useFrame(({ clock }) => { if (!ref.current) return; @@ -427,24 +429,33 @@ function LED({ position, color = '#5bd1d8' }: { position: [number, number, numbe const cameraZ = -scrollProgress * 90; const dist = Math.abs(position[2] - cameraZ); const proximity = Math.max(0, 1 - dist / 5); - (ref.current.material as THREE.MeshStandardMaterial).emissiveIntensity = proximity * (1.5 + Math.sin(t * 3) * 0.5); + // Glow point light near LED + const light = ref.current.children.find(c => c.type === 'PointLight') as THREE.PointLight; + if (light) light.intensity = proximity * (0.3 + Math.sin(t * 3) * 0.1); }); return ( - - - - - - {/* LED pad */} - - - - + + {glb?.scene ? ( + + ) : ( + + + + + )} + ); } +/* Preload all GLB models */ +useGLTF.preload('/assets/models3d/resistor_0603.glb'); +useGLTF.preload('/assets/models3d/capacitor_0805.glb'); +useGLTF.preload('/assets/models3d/led_0603.glb'); +useGLTF.preload('/assets/models3d/soic8.glb'); +useGLTF.preload('/assets/models3d/qfp32.glb'); + /* Silkscreen text (PCB printed labels) */ function Silk({ position, text, size = 0.15, color = '#e8e8d0', opacity = 0.5, rotation = [-Math.PI / 2, 0, 0] as [number, number, number] }: { position: [number, number, number]; text: string; size?: number; color?: string; opacity?: number; rotation?: [number, number, number];