skill / 3d-component-creator
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/3d-component-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: 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:

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

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.

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

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

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:

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

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

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