Board Creator
UnreviewedCreate interactive board layout visualizations and display them in the Gallia Viewer viewer. The **3D view** generates a GLB model using real STEP geometry for machine pins/contacts (from `adom-tsci-l
name: board-creator
description: Create interactive board layout visualizations for Adom molecules and PCB designs. 3D view uses a Python-built GLB with real STEP machine pin/contact geometry displayed in the Gallia Viewer Babylon.js 3D viewer. 2D view renders as interactive SVG HTML. Use when the user asks to "show the board", "create a board layout", "visualize the board", "board viewer", "3D board view", "2D board view", "PCB visualization", or wants to see an interactive PCB board visualization in the Gallia Viewer viewer.
Board Layout Creator
Create interactive board layout visualizations and display them in the Gallia Viewer viewer. The 3D view generates a GLB model using real STEP geometry for machine pins/contacts (from adom-tsci-library) with standardized pipeline PBR materials, displayed in the built-in Gallia Viewer Babylon.js 3D viewer. The 2D view renders an interactive SVG HTML file. These are visual board viewers — not KiCad .kicad_pcb files.
KiCad Service
DRC validation uses the remote KiCad CLI service — KiCad is NOT installed locally in user containers. The service URL is configured via the KICAD_API environment variable (default: http://127.0.0.1:8780).
API client: /home/adom/gallia/viewer/kicad-api-client.js
When to Use
- User asks to "show the board" or "visualize the board layout"
- User wants to see a 3D or 2D view of a molecule/PCB design
- After creating a molecule footprint, user wants to see the physical layout
- User asks for a "board viewer" or "PCB visualization"
- User wants an interactive view with hover tooltips, orbit controls, or pan/zoom
Output
Files saved to the project boards directory:
/home/adom/project/project-content/schematics/boards/BOARD_NAME/
BOARD_NAME.glb # 3D GLB model (real STEP pins, pipeline PBR materials)
build_3d_model.py # Python script to rebuild the GLB
BOARD_NAME-board-2d.html # Interactive 2D SVG top-down viewer
BOARD_NAME-footprint.json # Footprint data (copy or reference)
- 3D view: Display via
gv_3d_display(Gallia Viewer Babylon.js viewer) - 2D view: Display via
gv_display_file(HTML in Gallia Viewer iframe)
Input Data
The board viewer is driven by a footprint JSON object. If the user has a _footprint.json file, read it. Otherwise, construct the data from the user's design specs.
Footprint JSON Schema
{
"name": "Board_Name_v1",
"version": "v1",
"board": {
"width_mm": 32.0,
"height_mm": 32.0,
"size_param": "30x30",
"pin_type": "MachinePinMediumStandard",
"contact_type": "MachineContactMedium",
"mol_type": "4pin",
"border_radius": 1.2
},
"machine_pins": [
{ "name": "MP1", "x": -15.0, "y": -15.0, "drill": 1.1, "pad": 1.6 }
],
"contacts": [
{ "name": "GPIO0", "margin": "leftMargin", "x": -15.0, "y": -9.0, "drill": 0.78, "pad": 1.3, "pitch": 2.0 }
],
"margins": [
{ "name": "leftMargin", "pcbX": -15.0, "pcbY": 0, "width": 2.0, "height": 28.0 }
]
}
Components Array (for 3D/2D rendering)
In addition to the footprint JSON, define a components array for on-board ICs, passives, and connectors:
const COMPONENTS = [
{ name: "RP2350B", ref: "U1", pkg: "QFN-80", x: 0, y: 0, w: 7, h: 7, d: 1.0, color: "#1a237e" },
{ name: "W25Q128", ref: "U2", pkg: "SOIC-8", x: 6.5, y: -3, w: 5, h: 4, d: 0.8, color: "#1b5e20" },
// ... more components
];
Hydrogen Color Scheme
All board visualizations MUST match the Hydrogen dark theme.
Backgrounds & Borders
| Element | Color |
|---|---|
| Body background | #0f1218 |
| Info panel background | rgba(13, 17, 23, 0.95) |
| Panel border | #21262d |
| Board fill (PCB green) | #0d5016 (2D) or pipeline FR4 rgb(0.034, 0.105, 0.033) (3D) |
| Board stroke | #1b8a2a |
| Solder mask top | Pipeline FR4 material (3D) — see Standardized PBR Materials below |
Text Colors
| Element | Color |
|---|---|
| Primary text | #e6edf3 |
| Secondary text | #7d8590 |
| Teal accent | #00b8b1 |
| Info panel labels | #7d8590 |
Signal Colors (contacts)
| Signal Type | 2D Color | 3D Hex | Use For |
|---|---|---|---|
| Power (+3V3, +5V, VIN) | #f47067 |
0xe04040 |
Supply rails |
| Ground (GND) | #57ab5a |
0x40c040 |
Ground connections |
| GPIO | #6cb6ff |
0x4080e0 |
General purpose I/O |
| Debug (SWDIO, SWCLK) | #daaa3f |
0xe0c830 |
SWD debug pins |
| Special (RUN, ADC_VREF) | #d2a8ff |
0x9070c0 |
Misc signals |
Standardized PBR Materials (3D)
These are the canonical pipeline materials from molecule-converter/scripts/compress_glb.py :: create_standard_materials(). All board 3D views MUST use these exact values for consistency with the production Blender pipeline.
PCB Layer Materials
| Material | Name | Base Color (linear RGB) | Metallic | Roughness | IOR | Notes |
|---|---|---|---|---|---|---|
| FR4 PCB | Standard_FR4_PCB |
(0.034, 0.105, 0.033) |
0.200 | 0.350 | 1.000 | Solder mask green; HSV(0.333, 0.682, 0.105); sheenWeight=0.2 |
| Tan Substrate | Standard_Tan_Substrate |
(0.471, 0.431, 0.275) |
0.000 | 0.200 | 1.329 | Internal PCB layer edges; HSV(0.133, 0.417, 0.471) |
| Gold Machine Pin | Standard_Gold_Machine_Pin |
(0.900, 0.599, 0.200) |
1.000 | 0.077 | 50.000 | Machine pins and contacts; HSV(0.095, 0.778, 0.900) |
| HASL Copper | Standard_HASL_Copper |
(0.950, 0.950, 0.950) |
1.000 | 0.150 | 1.450 | Exposed copper pads (silver finish) |
Three.js Equivalent
// Canonical pipeline materials — use these exact values
const MATERIALS = {
fr4: {
color: new THREE.Color(0.034, 0.105, 0.033), // Dark solder mask green
metalness: 0.200,
roughness: 0.350,
},
tanSubstrate: {
color: new THREE.Color(0.471, 0.431, 0.275), // PCB edge/internal tan
metalness: 0.000,
roughness: 0.200,
},
goldPin: {
color: new THREE.Color(0.900, 0.599, 0.200), // Gold machine pin
metalness: 1.000,
roughness: 0.077,
},
haslCopper: {
color: new THREE.Color(0.950, 0.950, 0.950), // Silver HASL finish
metalness: 1.000,
roughness: 0.150,
},
};
Component Materials (non-pipeline, board-creator conventions)
| Element | Hex | Properties |
|---|---|---|
| IC body (dark) | 0x1e1e28 |
roughness: 0.15 |
| USB-C shell | 0x50505a |
metalness: 0.6, roughness: 0.25 |
| Button body | 0x463223 |
roughness: 0.5 |
| Capacitor body | 0x503719 |
roughness: 0.5 |
Fonts
- UI text:
-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif - Technical text:
'SF Mono', 'Fira Code', 'Consolas', monospace
Machine Pin & Contact Specs
Reference dimensions from adom-tsci-library/globals.ts:
| Type | Hole ID | Hole OD | Bounding Box |
|---|---|---|---|
| MachinePinMediumShort | 1.1mm | 1.6mm | 2mm |
| MachinePinMediumStandard | 1.1mm | 1.6mm | 2mm |
| MachinePinLargeShort | 3.45mm | 5.2mm | 6mm |
| MachinePinLargeStandard | 3.45mm | 5.2mm | 6mm |
| MachineContactMedium | 0.78mm | 1.3mm | 2mm |
| MachineContactLarge | 2.62mm | 4.4mm | 6mm |
3D Board Viewer (GLB + Gallia Viewer Babylon.js)
IMPORTANT: The 3D board view MUST use the built-in Gallia Viewer Babylon.js 3D viewer (gv_3d_display), NOT a self-contained Three.js HTML file. This ensures real STEP geometry for machine pins/contacts, proper PBR materials, and a consistent viewer experience.
Pipeline: Python GLB Builder
Generate a composite GLB model using trimesh in Python, loading actual STEP models from adom-tsci-library/lib/3D_Models/ for machine pins and contacts. Reference implementation: /home/adom/Molecule_RP2350B_Core_USB_v1/build_3d_model.py.
import trimesh
from trimesh.visual.material import PBRMaterial
import colorsys, json
STEP_DIR = "/home/adom/adom-tsci-library/lib/3D_Models"
# Load real STEP geometry (converted m → mm)
def load_step_mm(name):
scene = trimesh.load(f"{STEP_DIR}/{name}.step")
meshes = []
for gname, geom in scene.geometry.items():
geom.vertices *= 1000 # meters → mm
meshes.append(geom)
return meshes
pin_meshes = load_step_mm("MachinePinMediumStandard")
contact_meshes = load_step_mm("MachineContactMedium")
Pipeline PBR Materials in Python
def pipeline_pbr(h, s, v, metallic, roughness, name="mat"):
r, g, b = colorsys.hsv_to_rgb(h, s, v)
return PBRMaterial(baseColorFactor=[r, g, b, 1.0],
metallicFactor=metallic, roughnessFactor=roughness, name=name)
MAT_FR4 = pipeline_pbr(0.333, 0.682, 0.105, metallic=0.200, roughness=0.350, name="Standard_FR4_PCB")
MAT_GOLD_PIN = pipeline_pbr(0.095, 0.778, 0.900, metallic=1.000, roughness=0.077, name="Standard_Gold_Machine_Pin")
MAT_HASL = PBRMaterial(baseColorFactor=[0.95, 0.95, 0.95, 1.0],
metallicFactor=1.000, roughnessFactor=0.150, name="Standard_HASL_Copper")
MAT_SUBSTRATE = pipeline_pbr(0.133, 0.417, 0.471, metallic=0.000, roughness=0.200, name="Standard_Tan_Substrate")
Building the Scene
scene = trimesh.Scene()
# PCB board (box with FR4 material)
board = trimesh.creation.box(extents=[BOARD_W, BOARD_H, PCB_THICKNESS])
board.visual = trimesh.visual.TextureVisuals(material=MAT_FR4)
board.apply_translation([0, 0, -PCB_THICKNESS/2])
scene.add_geometry(board, node_name="PCB_Board")
# Real STEP machine pins at each corner position
for pin in footprint["machine_pins"]:
for i, mesh in enumerate(pin_meshes):
m = mesh.copy()
m.visual = trimesh.visual.TextureVisuals(material=MAT_GOLD_PIN)
m.apply_translation([pin["x"], pin["y"], 0])
scene.add_geometry(m, node_name=f"Pin_{pin['name']}_{i}")
# Real STEP contacts at each edge position (color-coded by signal)
for idx, contact in enumerate(footprint["contacts"]):
mat = contact_material(contact["name"]) # signal-based color
for i, mesh in enumerate(contact_meshes):
m = mesh.copy()
m.visual = trimesh.visual.TextureVisuals(material=mat)
m.apply_translation([contact["x"], contact["y"], 0])
scene.add_geometry(m, node_name=f"Contact_{contact['name']}_{idx}_{i}")
# Components as boxes with appropriate materials
# ... (IC body, flash, LDO, USB-C, crystal, buttons, caps, LEDs)
# Export
scene.export("BOARD_NAME.glb", file_type='glb')
Displaying in Gallia Viewer (required for 3D)
Always use gv_3d_display for the 3D view:
gv_3d_display(
glb_path="/path/to/BOARD_NAME.glb",
part_name="RP2350B Core+USB",
manufacturer="Adom",
package_type="Molecule 32x32mm",
pad_count=60,
body_size={ x: 32, y: 32, z: 1.6 },
title="Board Name 3D"
)
This renders in the built-in Gallia Viewer Babylon.js viewer with orbit controls, shadows, PBR lighting, and proper material rendering.
Fallback: Three.js HTML Template
A Three.js HTML template (board-3d-template.html) is included in this skill directory as a fallback only — use it when the user specifically requests a self-contained HTML file for sharing outside Gallia Viewer. It uses primitive cylinder geometry for pins (not real STEP models) and should not be the default 3D output.
2D Board Viewer (SVG)
Generate a self-contained HTML file with dynamic SVG rendering. Reference template: board-2d-template.html in this skill directory.
Rendering Approach
Use JavaScript to build SVG string and inject into a container div. Re-render on pan/zoom/toggle.
function render() {
const W = window.innerWidth, H = window.innerHeight;
const s = SCALE * vpScale;
const ox = W/2 + vpX, oy = H/2 + vpY;
function tx(mmx) { return ox + mmx * s; }
function ty(mmy) { return oy + mmy * s; }
function ts(mm) { return mm * s; }
let svg = `<svg xmlns="..." width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`;
// ... build SVG elements
svg += '</svg>';
container.innerHTML = svg;
// Attach hover events to data-type elements
}
Required Elements
- Grid — 2mm grid pattern using SVG
<pattern> - Board outline — rounded rectangle with PCB green fill
- Silkscreen border — dashed white outline inset from board edge
- Machine pins — gray circles at corners with drill hole centers
- Contacts — colored circles along edges, color-coded by signal
- Components — colored rectangles with ref designator and name labels
- Labels — rotated text for top/bottom contacts, horizontal for left/right
- Dimension lines — board width/height with arrows
- Margin visualization — optional dashed overlay showing edge and center margins
Interactive Controls
- Pan: mousedown + drag
- Zoom: mouse wheel (1.12x in, 0.89x out, clamped 0.2–10)
- Toolbar buttons: Contacts toggle, Margins toggle, Labels toggle, Zoom Fit
- Hover tooltips: show signal name, side, position, pad/drill specs
Signal Color Function (2D)
function sigColor(sig) {
if (sig.startsWith('+3V3') || sig === '+5V' || sig === 'VIN') return '#f47067';
if (sig === 'GND') return '#57ab5a';
if (sig.startsWith('GPIO')) return '#6cb6ff';
if (sig === 'SWDIO' || sig === 'SWCLK') return '#daaa3f';
return '#d2a8ff';
}
QFN / IC Package Rendering
When rendering QFN or other IC packages in 2D board layout viewers, model them as proper rotatable geometric objects.
QFN Pin Position Model
Standard QFN pin numbering is counter-clockwise from pin 1 (viewed from top). Pin 1 is at the top-left corner of the left side.
// QFN parameters
const QFN = { body: 10, pitch: 0.4, pinsPerSide: 20, padW: 0.2, padL: 0.6 };
const QFN_ROTATION = 0; // 0°=pin1 top-left, rotates CW
const pinPos = {};
(() => {
const SIDES = ['left','bottom','right','top'];
const rad = QFN_ROTATION * Math.PI / 180;
const cosR = Math.cos(rad), sinR = Math.sin(rad);
const span = (QFN.pinsPerSide - 1) * QFN.pitch;
const start = -span / 2;
const edge = QFN.body / 2 + QFN.padL / 2;
for (let i = 0; i < totalPins; i++) {
const pin = i + 1;
const side = Math.floor(i / QFN.pinsPerSide);
const idx = i % QFN.pinsPerSide;
let bx, by;
if (side === 0) { bx = -edge; by = start + idx * QFN.pitch; } // left: T→B
else if (side === 1) { bx = start + idx * QFN.pitch; by = edge; } // bottom: L→R
else if (side === 2) { bx = edge; by = -(start + idx * QFN.pitch); } // right: B→T
else { bx = -(start + idx * QFN.pitch); by = -edge; } // top: R→L
const x = bx * cosR - by * sinR;
const y = bx * sinR + by * cosR;
const sideIdx = (side + Math.round(QFN_ROTATION / 90)) % 4;
pinPos[pin] = { x, y, side: SIDES[sideIdx] };
}
})();
Pin 1 Marker
Offset perpendicular inward from the edge, NOT toward body center:
const p1 = pinPos[1];
const inw = p1.side==='left'?[1,0]:p1.side==='right'?[-1,0]:p1.side==='top'?[0,1]:[0,-1];
const markerX = p1.x + inw[0] * 1.0;
const markerY = p1.y + inw[1] * 1.0;
Pin Tooltips
Render transparent hit-area rects after all component groups (for z-order). Use data-* attributes for pin name, net, and notes. Show tooltips on mouseenter with component name, pin number, function, net assignment, and contextual notes.
Overlap Detection
Always run AABB overlap detection with 0.3mm clearance padding after any component repositioning. Display a visible warning banner when overlaps exist.
KiCad PCB Export
Generate .kicad_pcb files from layout data for DRC validation and interop with KiCad.
Generator Pattern
Write a Python script (gen_kicad_pcb.py) that:
- Defines component positions and footprint types matching the layout viewer
- Generates inline footprints with proper pad geometry (no external library dependencies)
- Assigns nets to pads for DRC connectivity checking
- Outputs a valid KiCad 9
.kicad_pcbfile
Key footprint pad specs (SMD roundrect, F.Cu/F.Paste/F.Mask layers):
- 0402: 2 pads at ±0.48mm, size 0.56×0.62mm
- 0805: 2 pads at ±0.9375mm, size 1.025×1.4mm
- SOT-223: 3 pads left at 2.3mm pitch + 1 tab pad right
- SOIC-8: 8 pads at 1.27mm pitch, 2 rows at ±2.7mm
- QFN-80: 80 perimeter pads (0.2×0.6mm at 0.4mm pitch) + exposed thermal pad
DRC Validation
import { pcbDrc } from '/home/adom/gallia/viewer/kicad-api-client.js';
const report = await pcbDrc('/path/to/board.kicad_pcb');
// report is the parsed JSON DRC report
Expected benign violations when using inline footprints:
lib_footprint_issues— footprint library names not found (expected for inline footprints)text_height— silkscreen text below 0.8mm minimum (cosmetic)
Rendering from KiCad
IMPORTANT: The KiCad service SVG export renders pads as thin outlines only, not filled shapes. The result is nearly invisible. Instead, use a custom Python renderer (render_pcb.py) with Pillow that draws filled copper pads, IC bodies, and silkscreen on a PCB-green board. Display the PNG via gv_display_file.
Critical Rules
SVG Sizing
The SVG MUST use dynamic width and height based on window.innerWidth / window.innerHeight. Do NOT use fixed pixel dimensions — they break in the Gallia Viewer iframe.
Self-Contained HTML
All CSS, JavaScript, and data MUST be inline in the single HTML file. The only external resources allowed are CDN imports (Three.js via importmap for 3D views). No local file references.
Coordinate System
- 2D SVG: Standard screen coordinates (Y down). Board center at screen center.
- 3D Three.js: Y-up coordinate system. Board surface at Y=0, board extends below (negative Y). Components placed above (positive Y).
- Footprint JSON: Standard PCB coordinates (origin at center, X-right, Y-up). Contact X/Y positions are in mm from board center.
Data-Driven Rendering
All board-specific data (pins, contacts, components, dimensions) MUST be defined in a BOARD_DATA config object at the top of the script. The rendering code below should be generic and work with any valid BOARD_DATA.
const BOARD_DATA = {
name: "Board Name",
board: { width: 32, height: 32, thickness: 1.6, borderRadius: 1.2 },
machinePins: [ /* from footprint JSON */ ],
contacts: [ /* from footprint JSON */ ],
components: [ /* user-defined component array */ ],
margins: [ /* from footprint JSON */ ],
};
Display in Gallia Viewer
3D View (default) — ALWAYS use gv_3d_display:
gv_3d_display(
glb_path="/path/to/BOARD_NAME.glb",
part_name="Board Name",
manufacturer="Adom",
package_type="Molecule WxHmm",
pad_count=N,
body_size={ x: W, y: H, z: 1.6 },
title="Board Name 3D"
)
2D View — use gv_display_file:
gv_display_file(file_path="/path/to/BOARD_NAME-board-2d.html", title="Board Name 2D")
Workflow
- Gather data — Read footprint JSON if available, or construct from user specs
- Create output folder —
mkdir -p /home/adom/project/project-content/schematics/boards/BOARD_NAME/ - Generate 3D GLB — Write a
build_3d_model.pythat loads real STEP pin/contact models fromadom-tsci-library/lib/3D_Models/, applies pipeline PBR materials, adds component geometry, and exports as GLB - Run the builder —
python3 build_3d_model.py - Display 3D in Gallia Viewer — Use
gv_3d_displaywith the generated GLB (default view) - Generate 2D viewer — Create
BOARD_NAME-board-2d.htmlfollowing the SVG template - Iterate — User may request changes (different view, colors, component positions)