Install this skill

Paste this into Claude Code (VS Code panel, Adom editor, or terminal) to install:

Search the Adom Wiki for the skill "Board Creator" (slug: board-creator) at https://wiki-ufypy5dpx93o.adom.cloud/wiki/skills/board-creator and install it into my local ~/.claude/skills/board-creator/ directory. Fetch the skill_source content from the wiki page and save it as SKILL.md. Then confirm it's installed by showing the first 5 lines.
?
What is a skill? Skills are instructions that teach AI assistants like Claude Code how to perform specific tasks. The description below is loaded into the AI as context when you invoke this skill. Well-written skills make the AI significantly more effective. Like Wikipedia, anyone can improve a skill by clicking Edit AI Skill — or have your AI submit an edit on your behalf.

Description

Edit AI Skill

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

ElementColor
Body background#0f1218
Info panel backgroundrgba(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 topPipeline FR4 material (3D) — see Standardized PBR Materials below

Text Colors

ElementColor
Primary text#e6edf3
Secondary text#7d8590
Teal accent#00b8b1
Info panel labels#7d8590

Signal Colors (contacts)

Signal Type2D Color3D HexUse For
Power (+3V3, +5V, VIN)#f470670xe04040Supply rails
Ground (GND)#57ab5a0x40c040Ground connections
GPIO#6cb6ff0x4080e0General purpose I/O
Debug (SWDIO, SWCLK)#daaa3f0xe0c830SWD debug pins
Special (RUN, ADC_VREF)#d2a8ff0x9070c0Misc 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

MaterialNameBase Color (linear RGB)MetallicRoughnessIORNotes
FR4 PCBStandard_FR4_PCB(0.034, 0.105, 0.033)0.2000.3501.000Solder mask green; HSV(0.333, 0.682, 0.105); sheenWeight=0.2
Tan SubstrateStandard_Tan_Substrate(0.471, 0.431, 0.275)0.0000.2001.329Internal PCB layer edges; HSV(0.133, 0.417, 0.471)
Gold Machine PinStandard_Gold_Machine_Pin(0.900, 0.599, 0.200)1.0000.07750.000Machine pins and contacts; HSV(0.095, 0.778, 0.900)
HASL CopperStandard_HASL_Copper(0.950, 0.950, 0.950)1.0000.1501.450Exposed 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)

ElementHexProperties
IC body (dark)0x1e1e28roughness: 0.15
USB-C shell0x50505ametalness: 0.6, roughness: 0.25
Button body0x463223roughness: 0.5
Capacitor body0x503719roughness: 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:

TypeHole IDHole ODBounding Box
MachinePinMediumShort1.1mm1.6mm2mm
MachinePinMediumStandard1.1mm1.6mm2mm
MachinePinLargeShort3.45mm5.2mm6mm
MachinePinLargeStandard3.45mm5.2mm6mm
MachineContactMedium0.78mm1.3mm2mm
MachineContactLarge2.62mm4.4mm6mm

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

  1. Grid — 2mm grid pattern using SVG <pattern>
  2. Board outline — rounded rectangle with PCB green fill
  3. Silkscreen border — dashed white outline inset from board edge
  4. Machine pins — gray circles at corners with drill hole centers
  5. Contacts — colored circles along edges, color-coded by signal
  6. Components — colored rectangles with ref designator and name labels
  7. Labels — rotated text for top/bottom contacts, horizontal for left/right
  8. Dimension lines — board width/height with arrows
  9. 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:

  1. Defines component positions and footprint types matching the layout viewer
  2. Generates inline footprints with proper pad geometry (no external library dependencies)
  3. Assigns nets to pads for DRC connectivity checking
  4. Outputs a valid KiCad 9 .kicad_pcb file

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

  1. Gather data — Read footprint JSON if available, or construct from user specs
  2. Create output foldermkdir -p /home/adom/project/project-content/schematics/boards/BOARD_NAME/
  3. Generate 3D GLB — Write a build_3d_model.py that loads real STEP pin/contact models from adom-tsci-library/lib/3D_Models/, applies pipeline PBR materials, adds component geometry, and exports as GLB
  4. Run the builderpython3 build_3d_model.py
  5. Display 3D in Gallia Viewer — Use gv_3d_display with the generated GLB (default view)
  6. Generate 2D viewer — Create BOARD_NAME-board-2d.html following the SVG template
  7. Iterate — User may request changes (different view, colors, component positions)

Skill Source

Edit AI Skill
---
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

```json
{
  "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:

```javascript
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

```javascript
// 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`.

```python
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

```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

```python
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.

```javascript
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
1. **Grid** — 2mm grid pattern using SVG `<pattern>`
2. **Board outline** — rounded rectangle with PCB green fill
3. **Silkscreen border** — dashed white outline inset from board edge
4. **Machine pins** — gray circles at corners with drill hole centers
5. **Contacts** — colored circles along edges, color-coded by signal
6. **Components** — colored rectangles with ref designator and name labels
7. **Labels** — rotated text for top/bottom contacts, horizontal for left/right
8. **Dimension lines** — board width/height with arrows
9. **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)

```javascript
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.

```javascript
// 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:

```javascript
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:
1. Defines component positions and footprint types matching the layout viewer
2. Generates inline footprints with proper pad geometry (no external library dependencies)
3. Assigns nets to pads for DRC connectivity checking
4. Outputs a valid KiCad 9 `.kicad_pcb` file

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

```javascript
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`.

```javascript
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

1. **Gather data** — Read footprint JSON if available, or construct from user specs
2. **Create output folder** — `mkdir -p /home/adom/project/project-content/schematics/boards/BOARD_NAME/`
3. **Generate 3D GLB** — Write a `build_3d_model.py` that loads real STEP pin/contact models from `adom-tsci-library/lib/3D_Models/`, applies pipeline PBR materials, adds component geometry, and exports as GLB
4. **Run the builder** — `python3 build_3d_model.py`
5. **Display 3D in Gallia Viewer** — Use `gv_3d_display` with the generated GLB (default view)
6. **Generate 2D viewer** — Create `BOARD_NAME-board-2d.html` following the SVG template
7. **Iterate** — User may request changes (different view, colors, component positions)

Sub-Skills
?
What are Sub-Skills?

Sub-skills are community-contributed AI skill extensions for this component. They teach AI assistants about specific tools, configurators, or workflows.

Examples:

  • A manufacturer’s configuration tool for a motor controller
  • A community-written design guide for an amplifier circuit
  • An automated test/validation script for a sensor module

How to add one: Click Add Sub-Skill, provide the URL to your skill and a brief description. Submissions are reviewed by the Adom team before going live.

No sub-skills yet. Be the first to contribute one!

0 revisions · Updated 2026-04-05 20:08:10