skill / board-creator
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/board-creator will not work until a maintainer publishes a tarball with install.sh and uninstall.sh.

See the publishing docs for the package.json schema and tarball layout required to ship this 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

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

  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)