{
  "schema_version": 1,
  "type": "skill",
  "slug": "3d-component-creator",
  "title": "3D Component Creator",
  "brief": "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 use",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "---\nname: 3d-component-creator\ndescription: 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.\n---\n\n# 3D Component Creator\n\nGenerate 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.\n\n**Input**: An existing `.kicad_mod` footprint + metadata JSON (from the **footprint-creator** skill)\n**Output**: GLB for 3dView preview, STEP for CAD delivery, standalone HTML for sharing\n\n## KiCad Service\n\nAll 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`).\n\nAPI client: `/home/adom/gallia/viewer/kicad-api-client.js`\nWrapper module: `/home/adom/gallia/viewer/generate-step.js` (delegates to API client)\n\n### Library Lookup — Query Existing 3D Models and Footprints\n\nThe KiCad service has **14,000+ 3D models** (STEP/WRL) and **7,000+ footprints** installed. Before creating anything from scratch, query the library:\n\n```javascript\nimport { models3dList, models3dDownload, fpList, fpDownload, fpExportGlbByName, fpExportStepByName } from '/home/adom/gallia/viewer/kicad-api-client.js';\n\n// ── Fastest: Export GLB or STEP directly by library + name (single call) ──\n// No file download needed — the service looks up the footprint, resolves the 3D model, and returns the export.\nconst glb = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23.glb');\n// → { outputPath: '/tmp/SOT-23.glb', hasModel: true, modelSource: 'cache', modelName: 'SOT-23' }\n// Export GLB without the FR4 board (component + pads only — ideal for web 3D viewers)\nconst glbNoBoard = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-noboard.glb', { noBoard: true });\n// Export \"enhanced\" GLB — viewer-ready with organized scene graph and PBR materials\nconst glbEnhanced = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-enhanced.glb', { enhanced: true });\n// Export enhanced + mm-scaled GLB — pre-scaled 1000x for Babylon.js shadows (check asset.extras.adom.mmScale to avoid double-scaling)\nconst glbMM = await fpExportGlbByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23-mm.glb', { enhanced: true, mmScale: true });\nconst step = await fpExportStepByName('Package_TO_SOT_SMD', 'SOT-23', '/tmp/SOT-23.step');\n\n// ── Search for models/footprints when you don't know the exact library ──\nconst models = await models3dList({ q: 'sot-23', format: 'step' });\n// → { total: 12, models: [{ library: \"Package_TO_SOT_SMD\", name: \"SOT-23-5\", ... }] }\nawait models3dDownload('Package_TO_SOT_SMD.3dshapes/SOT-23-5.step', '/tmp/SOT-23-5.step');\n\nconst fps = await fpList({ q: 'sot-23-5' });\nawait fpDownload('Package_TO_SOT_SMD.pretty/SOT-23-5.kicad_mod', '/tmp/SOT-23-5.kicad_mod');\n```\n\n**When to use which approach:**\n- **`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.\n- **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.\n- **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.\n\n## Workflow Overview\n\n```\n1. Locate footprint  →  2. Resolve 3D model  →  3. Generate GLB  →  4. Preview in Gallia Viewer  →  5. Iterate  →  6. Deliver STEP\n```\n\n## Step 1: Locate the Footprint\n\nThe 3D pipeline requires:\n- A `.kicad_mod` file (from footprint-creator or the KiCad library via `fpList` / `fpDownload`)\n- Metadata JSON with key fields: `bodySize`, `padCount`, `packageType`, `partName`, `manufacturer`\n\n```\n/home/adom/project/project-content/schematics/footprints/\n  PART_NAME/\n    PART_NAME.kicad_mod             # Input footprint\n    PART_NAME-fp-metadata.json      # Metadata with bodySize, manufacturer, etc.\n    PART_NAME.step                  # Output STEP file\n    PART_NAME-3d-viewer.html        # 3dView — standalone 3D viewer HTML\n```\n\n## Step 2: 3D Model Resolution (3-Tier Fallback)\n\nThe pipeline automatically resolves IC body STEP models:\n\n| Tier | Source | When |\n|------|--------|------|\n| 1 | **KiCad packages3D** (GitLab) | Standard packages (TSSOP, QFN, SOT, BGA, etc.) — auto-downloaded and cached |\n| 2 | **Custom STEP** | Manufacturer-provided or user-supplied STEP file |\n| 3 | **Pads only** | No model available — exports pad geometry on PCB substrate |\n\n### Resolution Algorithm\n\n1. Read `.kicad_mod`, extract `(model \"${KICAD7_3DMODEL_DIR}/Category.3dshapes/Name.wrl\")` reference\n2. Convert `.wrl` to `.step` in the relative path\n3. Check local cache — if hit, return immediately\n4. HTTP GET from GitLab raw URL (15s timeout)\n5. On success: save to cache, return path\n6. On failure: return null (falls back to pads-only)\n\n**GitLab URL format** (~7,200 STEP files):\n```\nhttps://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/{category}.3dshapes/{name}.step\n```\n\n### Cache\n\nDownloaded models are cached at `/home/adom/.cache/kicad-3d-models/` mirroring the KiCad repo structure:\n```\n/home/adom/.cache/kicad-3d-models/\n  Package_SO.3dshapes/TSSOP-14_4.4x5mm_P0.65mm.step\n  Package_LGA.3dshapes/Bosch_LGA-14_3x2.5mm_P0.5mm.step\n```\nCache persists across sessions — models are only downloaded once.\n\n### 3D Model Variable Resolution (handled by remote service)\n\nKiCad 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.\n\n**Code**: `/home/adom/gallia/viewer/kicad-3d-model-resolver.js`\n\nKey exports:\n- `resolveModel(kicadModPath)` — full resolution pipeline (cache check → download → fallback)\n- `resolveCustomModel(stepPath)` — for manufacturer-provided STEP files\n- `rewriteModelPaths(content)` — replaces `${KICAD7_3DMODEL_DIR}/...wrl` with absolute cache paths\n- `injectModelReference(content, modelPath)` — adds `(model ...)` to footprints that lack one\n\n## Step 3: Generate GLB / STEP\n\n```javascript\nimport { generateGLB, generateSTEP } from '/home/adom/gallia/viewer/generate-step.js';\n\n// GLB for 3D viewer (with IC body auto-resolved)\nconst glbResult = await generateGLB(kicadModPath, fpName, outputPath, options);\n\n// STEP for CAD delivery\nconst stepResult = await generateSTEP(kicadModPath, fpName, outputPath, options);\n\n// Result: { outputPath, hasModel, modelSource, modelName }\n// modelSource: 'cache' | 'download' | 'custom' | 'none'\n```\n\n### Options\n\n- Default: auto-resolves 3D model (downloads from GitLab if needed)\n- `{ skipModelResolution: true }`: pads-only export\n- `{ customStepPath: '/path/to/model.step' }`: use a specific STEP file\n- `{ enhanced: true }`: viewer-ready GLB with organized scene graph and PBR materials (see Enhanced GLB section below)\n- `{ mmScale: true }`: scale 1000x (meters → millimeters) for Babylon.js shadow/lighting compatibility\n- `{ enhanced: true, mmScale: true }`: combined — best for Babylon.js 3D viewers\n\n### Export Flags (used by remote service internally)\n\nThe remote service sets these flags automatically — listed here for reference only:\n\n| Scenario | Flags |\n|----------|-------|\n| **With IC body model** | `--include-pads --subst-models -f -o output` |\n| **Pads only (no model)** | `--include-pads --no-components -f -o output` |\n\nYou do NOT need to set these flags — `generateGLB()` and `generateSTEP()` handle them.\n\n### Enhanced GLB Mode\n\nPass `{ enhanced: true }` to any GLB export function to get a **viewer-ready** GLB with:\n\n1. **Organized scene graph** — named parent nodes: `component`, `board_pads`, `board_fr4`\n2. **PBR materials** — IC body (dark mold), leads (metallic silver), copper pads (gold), FR4 (green)\n3. **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\n\nOptionally add `{ mmScale: true }` to pre-scale 1000x (meters → millimeters) for Babylon.js shadow/lighting compatibility.\n\n```javascript\n// Enhanced GLB for 3dView (recommended for web viewers)\nconst glb = await fpExportGlbByName('Package_SO', 'SOIC-8_3.9x4.9mm_P1.27mm', '/tmp/soic8.glb', { enhanced: true });\n// Enhanced + mm-scaled for Babylon.js (shadows work correctly at this scale)\nconst glb = await fpExportGlbByName('Package_SO', 'SOIC-8_3.9x4.9mm_P1.27mm', '/tmp/soic8.glb', { enhanced: true, mmScale: true });\n// Or with POST endpoint:\nconst glb = await pcbExportGlb(kicadModPath, fpName, outputPath, { enhanced: true, mmScale: true });\n```\n\nThe enhanced GLB scene graph:\n```\nroot\n  └─ component        ← REF** node (IC body + leads with distinct materials)\n  └─ board_pads       ← copper pad meshes\n  └─ board_fr4        ← FR4 substrate mesh (omitted if noBoard: true)\n```\n\n### GLB Metadata (`asset.extras.adom`)\n\nEvery post-processed GLB embeds metadata in `gltf.asset.extras.adom`:\n```json\n{ \"zUp\": true, \"enhanced\": true, \"mmScale\": true, \"boardStripped\": false }\n```\n\nBabylon.js viewers should check `asset.extras.adom.mmScale` before applying their own 1000x scale to avoid double-scaling.\n\n**When to use enhanced mode:**\n- Displaying in 3dView / Babylon.js — enhanced GLBs render with correct materials out of the box\n- Client-side material fixup is no longer needed — the GLB itself contains proper PBR materials\n\n**When to add mmScale:**\n- Babylon.js viewers that need correct shadow maps and lighting at component scale\n- Only if the viewer doesn't already apply its own 1000x scale (check `asset.extras.adom.mmScale`)\n\n**When NOT to use enhanced mode:**\n- Exporting for CAD tools (use STEP instead)\n- When you need the raw KiCad output without modifications\n\n### GLB Coordinate System (critical)\n\n**Standard GLB output is in meters** (glTF spec default), NOT millimeters. **mmScale GLB output is in millimeters** (1000x scaled).\n\n```\nStandard mode:          1 mm = 0.001 scene units (use const MM = 0.001)\nmmScale mode:           1 mm = 1 scene unit (no conversion needed)\n```\n\nUse `const MM = 0.001` for standard mode. Enhanced mode uses millimeter-scale coordinates directly. Y-axis is up.\n\n## Step 4: Preview in 3dView\n\n### Option A: Built-in 3dView (preferred)\n\n**3dView** is the Gallia Viewer's built-in Babylon.js 3D viewer. Use the `gv_3d_display` MCP tool:\n\n```\ngv_3d_display\n  glb_path: \"/path/to/PART_NAME-3d.glb\"\n  body_size: { x: 4.4, y: 5.0, z: 1.2 }\n  part_name: \"CDCEL913\"\n  manufacturer: \"Texas Instruments\"\n  package_type: \"TSSOP-14\"\n  pad_count: 14\n  title: \"CDCEL913 3D Model\"\n```\n\nThis copies the GLB to the Gallia Viewer server's serving directory and switches to 3dView with the model loaded.\n\n### Option B: Standalone HTML (for export/sharing)\n\nGenerate self-contained HTML with embedded Babylon.js:\n\n```javascript\nimport { generate3DViewerHTML } from '/home/adom/gallia/viewer/kicad-3d-viewer.js';\nimport { writeFileSync } from 'fs';\n\nconst html = await generate3DViewerHTML(glbPath, {\n  bodySize: metadata.bodySize,\n  padCount: metadata.padCount,\n  packageType: metadata.packageType,\n  partName: metadata.partName,\n  manufacturer: metadata.manufacturer,\n});\nwriteFileSync(`${outputDir}/PART_NAME-3d-viewer.html`, html);\n```\n\nDisplay via `gv_display_file` MCP tool.\n\n### 3dView Features\n\n**3dView** provides:\n- Arc-rotate camera (orbit, pan, zoom, WASD movement)\n- Skybox and studio lighting\n- View cube for orientation\n- Ground plane toggle\n- Shadow casting\n- IC body with laser-etched chip markings\n- Info bar showing package details\n- Bottom light ON by default (illuminates underside/pad surfaces)\n- XYZ axes OFF by default\n- Pad highlighting with cross-pane sync (LibView)\n\n### WebSocket Messages\n\n| Message | Direction | Purpose |\n|---------|-----------|---------|\n| `show_3d` | server → viewer | Switch to 3dView and load model |\n| `clear_3d` | server → viewer | Clear the 3D scene |\n| `model_3d_options` | server → viewer | Update laser etch / info bar |\n| `stop_tour` | relay → viewer | Stop cinematic tour, freeze camera |\n| `start_tour` | relay → viewer | Start/restart cinematic tour |\n| `set_camera` | relay → viewer | Set camera alpha/beta/radius (beauty angle) |\n| `set_fr4` | relay → viewer | Explicitly show/hide FR4 board (`{ visible: true }`) |\n| `toggle_fr4` | relay → viewer | Toggle FR4 board visibility |\n| `show_origin` | relay → viewer | Toggle XYZ axis lines at world origin (`{ visible: true/false }`) |\n| `set_view` | relay → viewer | Set camera to named preset: `front`, `back`, `left`, `right`, `top`, `bottom`, `isometric` |\n\nThe `relay → viewer` messages are sent via the management relay's `broadcast` action:\n```bash\ncurl -X POST http://127.0.0.1:8772/command -H 'Content-Type: application/json' \\\n  -d '{\"action\":\"broadcast\",\"message\":{\"type\":\"stop_tour\"}}'\n```\n\n## Step 5: Laser Etch Chip Markings\n\n3dView 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.\n\n### How It Works\n\n- Uses known body dimensions from metadata (NOT bounding box detection or raycasting)\n- Multiplies mm dimensions by `0.001` to convert to scene units (meters)\n- IC body top surface = highest point of centered model bounding box (Y-up)\n- Plane at 85% of body dimensions (realistic edge margin)\n- DynamicTexture with monospace font, silver/white text color\n- Pin 1 dot in top-left corner\n\n### Why This Approach Works\n\nAll 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.\n\n### Babylon.js DynamicTexture Implementation\n\n```javascript\nconst MM = 0.001;\nconst topY = boundingBox.max.y;  // IC body top (Y-up)\nconst bw = bodySize.x * MM;     // body width in scene units\nconst bd = bodySize.y * MM;     // body depth in scene units\nconst longSide = Math.max(bw, bd);\nconst shortSide = Math.min(bw, bd);\n\n// Canvas texture\nconst dtex = new BABYLON.DynamicTexture('laserEtch', 1024, scene, true);\nconst ctx = dtex.getContext();\nctx.fillStyle = 'rgba(210, 212, 215, 0.8)';\nctx.textAlign = 'center';\nctx.font = fontSize + 'px monospace';\n// ... draw text lines, pin 1 dot ...\ndtex.update();\n\n// Material\nconst mat = new BABYLON.StandardMaterial('etchMat', scene);\nmat.diffuseTexture = dtex;\nmat.diffuseTexture.hasAlpha = true;\nmat.useAlphaFromDiffuseTexture = true;\nmat.emissiveColor = new BABYLON.Color3(0.82, 0.83, 0.84);\nmat.backFaceCulling = false;\nmat.zOffset = -1;\n\n// Plane on IC body top\nconst plane = BABYLON.MeshBuilder.CreatePlane('etch', {\n  width: longSide * 0.85,\n  height: shortSide * 0.85,\n}, scene);\nplane.rotation.x = Math.PI / 2;        // lay flat (Babylon planes face +Z)\nplane.position.y = topY + 0.01 * MM;   // 0.01mm above surface\nplane.material = mat;\n```\n\n### Marking Lines Logic\n\nDefault marking lines (when `manufacturer` and `partName` are provided):\n1. `manufacturer.toUpperCase()` (e.g., \"TEXAS INSTRUMENTS\")\n2. `partName.toUpperCase()` (e.g., \"CDCEL913\")\n3. Date code (YYMM format)\n\nOverride with custom `markingLines` array for non-standard markings.\n\n## Step 6: Deliver\n\n### 6a: STEP file for KiCad Desktop\n\n```javascript\nconst stepResult = await generateSTEP(kicadModPath, fpName, outputPath);\n```\n\nThen send via MCP tools:\n```\n1. send_files\n     filePaths: [\"/path/to/PART_NAME.step\"]\n     targetApp: \"kicad\"\n     destinationFolder: \"3d-models\"\n```\n\n### 6b: STEP to Fusion 360\n\n```\n1. send_files\n     filePaths: [\"/path/to/PART_NAME.step\"]\n     targetApp: \"fusion360\"\n     destinationFolder: \"fusion\"\n2. fusion_import_step\n     filePath: \"<destinationPath from step 1>\"\n```\n\n## Code Reference\n\n| File | Purpose |\n|------|---------|\n| `/home/adom/gallia/viewer/generate-step.js` | STEP/GLB export via remote KiCad service + model resolution |\n| `/home/adom/gallia/viewer/kicad-3d-model-resolver.js` | Model download, caching, path rewriting |\n| `/home/adom/gallia/viewer/kicad-3d-viewer.js` | 3dView integration (`serve3DModel`) + standalone HTML (`generate3DViewerHTML`) |\n\n## Common Pitfalls\n\n- **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\n- **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\n- **`.wrl` → `.step` substitution** — handled automatically by the remote service (`--subst-models` flag applied server-side)\n- **DynamicTexture size** — Use power-of-2 dimensions (1024, 512) for optimal GPU performance\n- **Babylon.js plane orientation** — Planes face +Z by default; use `rotation.x = Math.PI/2` to lay flat on XZ plane facing Y-up\n- **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\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "sample_prompts": [],
  "discovery_triggers": [],
  "discovery_pitch": null,
  "metadata": {},
  "created_at": "2026-05-28T05:30:21.208Z",
  "updated_at": "2026-05-28T05:30:21.208Z",
  "sub_skills": [],
  "parent_app": null
}