---
name: 3d-component-creator
description: Use when the user asks to "generate a 3D model", "create a 3D view", "show the 3D model", "visualize in 3D", "create a STEP file", "make a GLB", "add chip markings", "laser etch", "3D preview", or wants to see an interactive 3dView of a PCB component in the Gallia Viewer. Covers STEP/GLB generation, 3D model resolution from KiCad packages3D, laser etch chip markings, and interactive Babylon.js 3dView.
---

# 3D Component Creator

Generate interactive 3D models of PCB components with IC body, laser-etched chip markings, and pad geometry. Preview in 3dView (the Gallia Viewer's built-in Babylon.js 3D viewer), iterate with the user, and deliver STEP files for KiCad or Fusion 360.

**Input**: An existing `.kicad_mod` footprint + metadata JSON (from the **footprint-creator** skill)
**Output**: GLB for 3dView preview, STEP for CAD delivery, standalone HTML for sharing

## KiCad Service

All kicad-cli operations (STEP/GLB export, 3D model resolution) use 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`
Wrapper module: `/home/adom/gallia/viewer/generate-step.js` (delegates to API client)

### Library Lookup — Query Existing 3D Models and Footprints

The KiCad service has **14,000+ 3D models** (STEP/WRL) and **7,000+ footprints** installed. Before creating anything from scratch, query the library:

```javascript
import { models3dList, models3dDownload, fpList, fpDownload, fpExportGlbByName, fpExportStepByName } from '/home/adom/gallia/viewer/kicad-api-client.js';

// ── Fastest: Export GLB or STEP directly by library + name (single call) ──
// No file download needed — the service looks up the footprint, resolves the 3D model, and returns the export.
const glb = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23.glb');
// → { outputPath: '/tmp/SOT-23.glb', hasModel: true, modelSource: 'cache', modelName: 'SOT-23' }
// Export GLB without the FR4 board (component + pads only — ideal for web 3D viewers)
const glbNoBoard = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-noboard.glb', { noBoard: true });
// Export "enhanced" GLB — viewer-ready with organized scene graph and PBR materials
const glbEnhanced = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-enhanced.glb', { enhanced: true });
// Export enhanced + mm-scaled GLB — pre-scaled 1000x for Babylon.js shadows (check asset.extras.adom.mmScale to avoid double-scaling)
const glbMM = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-mm.glb', { enhanced: true, mmScale: true });
const step = await fpExportStepByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23.step');

// ── Search for models/footprints when you don't know the exact library ──
const models = await models3dList({ q: 'sot-23', format: 'step' });
// → { total: 12, models: [{ library: "Package_TO_SOT_SMD", name: "SOT-23-5", ... }] }
await models3dDownload('Package_TO_SOT_SMD.3dshapes/SOT-23-5.step', '/tmp/SOT-23-5.step');

const fps = await fpList({ q: 'sot-23-5' });
await fpDownload('Package_TO_SOT_SMD.pretty/SOT-23-5.kicad_mod', '/tmp/SOT-23-5.kicad_mod');
```

**When to use which approach:**
- **`fpExportGlbByName` / `fpExportStepByName`** — Best for web preview (GLB) or CAD delivery (STEP) of standard library components. Single call, no intermediate files. Use `fpList` first to find the library + name if you don't know them.
- **Direct download** (`models3dDownload`) — When you just need the raw STEP/WRL file itself (e.g., importing into Fusion 360 as a reference model). Faster, no footprint context.
- **Export pipeline** (`pcbExportGlb` / `pcbExportStep`) — When you have a custom `.kicad_mod` (not from the standard library) and need it rendered with pads + IC body on substrate.

## Workflow Overview

```
1. Locate footprint  →  2. Resolve 3D model  →  3. Generate GLB  →  4. Preview in Gallia Viewer  →  5. Iterate  →  6. Deliver STEP
```

## Step 1: Locate the Footprint

The 3D pipeline requires:
- A `.kicad_mod` file (from footprint-creator or the KiCad library via `fpList` / `fpDownload`)
- Metadata JSON with key fields: `bodySize`, `padCount`, `packageType`, `partName`, `manufacturer`

```
/home/adom/project/project-content/schematics/footprints/
  PART_NAME/
    PART_NAME.kicad_mod             # Input footprint
    PART_NAME-fp-metadata.json      # Metadata with bodySize, manufacturer, etc.
    PART_NAME.step                  # Output STEP file
    PART_NAME-3d-viewer.html        # 3dView — standalone 3D viewer HTML
```

## Step 2: 3D Model Resolution (3-Tier Fallback)

The pipeline automatically resolves IC body STEP models:

| Tier | Source | When |
|------|--------|------|
| 1 | **KiCad packages3D** (GitLab) | Standard packages (TSSOP, QFN, SOT, BGA, etc.) — auto-downloaded and cached |
| 2 | **Custom STEP** | Manufacturer-provided or user-supplied STEP file |
| 3 | **Pads only** | No model available — exports pad geometry on PCB substrate |

### Resolution Algorithm

1. Read `.kicad_mod`, extract `(model "${KICAD7_3DMODEL_DIR}/Category.3dshapes/Name.wrl")` reference
2. Convert `.wrl` to `.step` in the relative path
3. Check local cache — if hit, return immediately
4. HTTP GET from GitLab raw URL (15s timeout)
5. On success: save to cache, return path
6. On failure: return null (falls back to pads-only)

**GitLab URL format** (~7,200 STEP files):
```
https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/{category}.3dshapes/{name}.step
```

### Cache

Downloaded models are cached at `/home/adom/.cache/kicad-3d-models/` mirroring the KiCad repo structure:
```
/home/adom/.cache/kicad-3d-models/
  Package_SO.3dshapes/TSSOP-14_4.4x5mm_P0.65mm.step
  Package_LGA.3dshapes/Bosch_LGA-14_3x2.5mm_P0.5mm.step
```
Cache persists across sessions — models are only downloaded once.

### 3D Model Variable Resolution (handled by remote service)

KiCad footprints reference 3D models via `${KICAD7_3DMODEL_DIR}/...wrl`. The remote KiCad service automatically resolves these variable paths to cached `.step` files server-side. You do NOT need to handle this manually — just call `generateGLB()` or `generateSTEP()` from `generate-step.js` and the service handles model resolution, downloading, and caching.

**Code**: `/home/adom/gallia/viewer/kicad-3d-model-resolver.js`

Key exports:
- `resolveModel(kicadModPath)` — full resolution pipeline (cache check → download → fallback)
- `resolveCustomModel(stepPath)` — for manufacturer-provided STEP files
- `rewriteModelPaths(content)` — replaces `${KICAD7_3DMODEL_DIR}/...wrl` with absolute cache paths
- `injectModelReference(content, modelPath)` — adds `(model ...)` to footprints that lack one

## Step 3: Generate GLB / STEP

```javascript
import { generateGLB, generateSTEP } from '/home/adom/gallia/viewer/generate-step.js';

// GLB for 3D viewer (with IC body auto-resolved)
const glbResult = await generateGLB(kicadModPath, fpName, outputPath, options);

// STEP for CAD delivery
const stepResult = await generateSTEP(kicadModPath, fpName, outputPath, options);

// Result: { outputPath, hasModel, modelSource, modelName }
// modelSource: 'cache' | 'download' | 'custom' | 'none'
```

### Options

- Default: auto-resolves 3D model (downloads from GitLab if needed)
- `{ skipModelResolution: true }`: pads-only export
- `{ customStepPath: '/path/to/model.step' }`: use a specific STEP file
- `{ enhanced: true }`: viewer-ready GLB with organized scene graph and PBR materials (see Enhanced GLB section below)
- `{ mmScale: true }`: scale 1000x (meters → millimeters) for Babylon.js shadow/lighting compatibility
- `{ enhanced: true, mmScale: true }`: combined — best for Babylon.js 3D viewers

### Export Flags (used by remote service internally)

The remote service sets these flags automatically — listed here for reference only:

| Scenario | Flags |
|----------|-------|
| **With IC body model** | `--include-pads --subst-models -f -o output` |
| **Pads only (no model)** | `--include-pads --no-components -f -o output` |

You do NOT need to set these flags — `generateGLB()` and `generateSTEP()` handle them.

### Enhanced GLB Mode

Pass `{ enhanced: true }` to any GLB export function to get a **viewer-ready** GLB with:

1. **Organized scene graph** — named parent nodes: `component`, `board_pads`, `board_fr4`
2. **PBR materials** — IC body (dark mold), leads (metallic silver), copper pads (gold), FR4 (green)
3. **Body/lead classification** — dual-criteria algorithm (inner 70% center test + upper 50% height test) correctly classifies primitives across SOT, SOIC, QFP, and other IC packages

Optionally add `{ mmScale: true }` to pre-scale 1000x (meters → millimeters) for Babylon.js shadow/lighting compatibility.

```javascript
// Enhanced GLB for 3dView (recommended for web viewers)
const glb = await fpExportGlbByName('Package_SO', 'SOIC-8_3.9x4.9mm_P1.27mm', '/tmp/soic8.glb', { enhanced: true });
// Enhanced + mm-scaled for Babylon.js (shadows work correctly at this scale)
const glb = await fpExportGlbByName('Package_SO', 'SOIC-8_3.9x4.9mm_P1.27mm', '/tmp/soic8.glb', { enhanced: true, mmScale: true });
// Or with POST endpoint:
const glb = await pcbExportGlb(kicadModPath, fpName, outputPath, { enhanced: true, mmScale: true });
```

The enhanced GLB scene graph:
```
root
  └─ component        ← REF** node (IC body + leads with distinct materials)
  └─ board_pads       ← copper pad meshes
  └─ board_fr4        ← FR4 substrate mesh (omitted if noBoard: true)
```

### GLB Metadata (`asset.extras.adom`)

Every post-processed GLB embeds metadata in `gltf.asset.extras.adom`:
```json
{ "zUp": true, "enhanced": true, "mmScale": true, "boardStripped": false }
```

Babylon.js viewers should check `asset.extras.adom.mmScale` before applying their own 1000x scale to avoid double-scaling.

**When to use enhanced mode:**
- Displaying in 3dView / Babylon.js — enhanced GLBs render with correct materials out of the box
- Client-side material fixup is no longer needed — the GLB itself contains proper PBR materials

**When to add mmScale:**
- Babylon.js viewers that need correct shadow maps and lighting at component scale
- Only if the viewer doesn't already apply its own 1000x scale (check `asset.extras.adom.mmScale`)

**When NOT to use enhanced mode:**
- Exporting for CAD tools (use STEP instead)
- When you need the raw KiCad output without modifications

### GLB Coordinate System (critical)

**Standard GLB output is in meters** (glTF spec default), NOT millimeters. **mmScale GLB output is in millimeters** (1000x scaled).

```
Standard mode:          1 mm = 0.001 scene units (use const MM = 0.001)
mmScale mode:           1 mm = 1 scene unit (no conversion needed)
```

Use `const MM = 0.001` for standard mode. Enhanced mode uses millimeter-scale coordinates directly. Y-axis is up.

## Step 4: Preview in 3dView

### Option A: Built-in 3dView (preferred)

**3dView** is the Gallia Viewer's built-in Babylon.js 3D viewer. Use the `gv_3d_display` MCP tool:

```
gv_3d_display
  glb_path: "/path/to/PART_NAME-3d.glb"
  body_size: { x: 4.4, y: 5.0, z: 1.2 }
  part_name: "CDCEL913"
  manufacturer: "Texas Instruments"
  package_type: "TSSOP-14"
  pad_count: 14
  title: "CDCEL913 3D Model"
```

This copies the GLB to the Gallia Viewer server's serving directory and switches to 3dView with the model loaded.

### Option B: Standalone HTML (for export/sharing)

Generate self-contained HTML with embedded Babylon.js:

```javascript
import { generate3DViewerHTML } from '/home/adom/gallia/viewer/kicad-3d-viewer.js';
import { writeFileSync } from 'fs';

const html = await generate3DViewerHTML(glbPath, {
  bodySize: metadata.bodySize,
  padCount: metadata.padCount,
  packageType: metadata.packageType,
  partName: metadata.partName,
  manufacturer: metadata.manufacturer,
});
writeFileSync(`${outputDir}/PART_NAME-3d-viewer.html`, html);
```

Display via `gv_display_file` MCP tool.

### 3dView Features

**3dView** provides:
- Arc-rotate camera (orbit, pan, zoom, WASD movement)
- Skybox and studio lighting
- View cube for orientation
- Ground plane toggle
- Shadow casting
- IC body with laser-etched chip markings
- Info bar showing package details
- Bottom light ON by default (illuminates underside/pad surfaces)
- XYZ axes OFF by default
- Pad highlighting with cross-pane sync (LibView)

### WebSocket Messages

| Message | Direction | Purpose |
|---------|-----------|---------|
| `show_3d` | server → viewer | Switch to 3dView and load model |
| `clear_3d` | server → viewer | Clear the 3D scene |
| `model_3d_options` | server → viewer | Update laser etch / info bar |
| `stop_tour` | relay → viewer | Stop cinematic tour, freeze camera |
| `start_tour` | relay → viewer | Start/restart cinematic tour |
| `set_camera` | relay → viewer | Set camera alpha/beta/radius (beauty angle) |
| `set_fr4` | relay → viewer | Explicitly show/hide FR4 board (`{ visible: true }`) |
| `toggle_fr4` | relay → viewer | Toggle FR4 board visibility |
| `show_origin` | relay → viewer | Toggle XYZ axis lines at world origin (`{ visible: true/false }`) |
| `set_view` | relay → viewer | Set camera to named preset: `front`, `back`, `left`, `right`, `top`, `bottom`, `isometric` |

The `relay → viewer` messages are sent via the management relay's `broadcast` action:
```bash
curl -X POST http://127.0.0.1:8772/command -H 'Content-Type: application/json' \
  -d '{"action":"broadcast","message":{"type":"stop_tour"}}'
```

## Step 5: Laser Etch Chip Markings

3dView renders laser-etched text on the IC body top surface — manufacturer name, part number, date code, and pin 1 dot. This is automatic when `manufacturer` and `partName` are provided and the model has an IC body.

### How It Works

- Uses known body dimensions from metadata (NOT bounding box detection or raycasting)
- Multiplies mm dimensions by `0.001` to convert to scene units (meters)
- IC body top surface = highest point of centered model bounding box (Y-up)
- Plane at 85% of body dimensions (realistic edge margin)
- DynamicTexture with monospace font, silver/white text color
- Pin 1 dot in top-left corner

### Why This Approach Works

All IC packages have a flat top surface for pick-and-place during reflow soldering. The top surface position is deterministic from known body dimensions — no mesh analysis or raycasting needed.

### Babylon.js DynamicTexture Implementation

```javascript
const MM = 0.001;
const topY = boundingBox.max.y;  // IC body top (Y-up)
const bw = bodySize.x * MM;     // body width in scene units
const bd = bodySize.y * MM;     // body depth in scene units
const longSide = Math.max(bw, bd);
const shortSide = Math.min(bw, bd);

// Canvas texture
const dtex = new BABYLON.DynamicTexture('laserEtch', 1024, scene, true);
const ctx = dtex.getContext();
ctx.fillStyle = 'rgba(210, 212, 215, 0.8)';
ctx.textAlign = 'center';
ctx.font = fontSize + 'px monospace';
// ... draw text lines, pin 1 dot ...
dtex.update();

// Material
const mat = new BABYLON.StandardMaterial('etchMat', scene);
mat.diffuseTexture = dtex;
mat.diffuseTexture.hasAlpha = true;
mat.useAlphaFromDiffuseTexture = true;
mat.emissiveColor = new BABYLON.Color3(0.82, 0.83, 0.84);
mat.backFaceCulling = false;
mat.zOffset = -1;

// Plane on IC body top
const plane = BABYLON.MeshBuilder.CreatePlane('etch', {
  width: longSide * 0.85,
  height: shortSide * 0.85,
}, scene);
plane.rotation.x = Math.PI / 2;        // lay flat (Babylon planes face +Z)
plane.position.y = topY + 0.01 * MM;   // 0.01mm above surface
plane.material = mat;
```

### Marking Lines Logic

Default marking lines (when `manufacturer` and `partName` are provided):
1. `manufacturer.toUpperCase()` (e.g., "TEXAS INSTRUMENTS")
2. `partName.toUpperCase()` (e.g., "CDCEL913")
3. Date code (YYMM format)

Override with custom `markingLines` array for non-standard markings.

## Step 6: Deliver

### 6a: STEP file for KiCad Desktop

```javascript
const stepResult = await generateSTEP(kicadModPath, fpName, outputPath);
```

Then send via MCP tools:
```
1. send_files
     filePaths: ["/path/to/PART_NAME.step"]
     targetApp: "kicad"
     destinationFolder: "3d-models"
```

### 6b: STEP to Fusion 360

```
1. send_files
     filePaths: ["/path/to/PART_NAME.step"]
     targetApp: "fusion360"
     destinationFolder: "fusion"
2. fusion_import_step
     filePath: "<destinationPath from step 1>"
```

## Code Reference

| File | Purpose |
|------|---------|
| `/home/adom/gallia/viewer/generate-step.js` | STEP/GLB export via remote KiCad service + model resolution |
| `/home/adom/gallia/viewer/kicad-3d-model-resolver.js` | Model download, caching, path rewriting |
| `/home/adom/gallia/viewer/kicad-3d-viewer.js` | 3dView integration (`serve3DModel`) + standalone HTML (`generate3DViewerHTML`) |

## Common Pitfalls

- **GLB is in meters, not mm** — GLB output uses meters per glTF spec. `1mm = 0.001 scene units`. Passing raw mm values produces geometry 1000x too large
- **Model path resolution** — `${KICAD7_3DMODEL_DIR}` variables in `.kicad_mod` are resolved automatically by the remote KiCad service. You do NOT need to rewrite paths manually
- **`.wrl` → `.step` substitution** — handled automatically by the remote service (`--subst-models` flag applied server-side)
- **DynamicTexture size** — Use power-of-2 dimensions (1024, 512) for optimal GPU performance
- **Babylon.js plane orientation** — Planes face +Z by default; use `rotation.x = Math.PI/2` to lay flat on XZ plane facing Y-up
- **Gallia Viewer restart required for 3d.html** — After editing `viewer/3d.html`, restart the Gallia Viewer server (`3d.html` is cached with `readFileSync` at startup). `index.html` is read fresh on each request and does NOT require a restart
