# STEP File Processor Skill

## Purpose
This skill enables Claude to work with STEP/STP CAD files used in manufacturing.
It provides:
- PNG thumbnail extraction at 6 camera angles
- Parts manifest / BOM export to CSV with automatic Chinese→English translation
- Optional English-labeled STEP copy (source file never modified)
- Natural language geometric query interface (holes, faces, dimensions, quantities)

Designed for MPMedia's display enclosure and mounting kit product lines.

---

## When to invoke this skill

Trigger on any of:
- User provides a `.step` or `.stp` file path and asks to process, view, extract, or query it
- User asks to "generate thumbnails" or "render views" of a STEP file
- User asks to "extract BOM", "get parts list", or "show manifest" from a STEP file
- User asks a geometric question about a STEP file: holes, faces, dimensions, quantities
- User asks to "translate" a STEP file's part names
- Claude Code context where a STEP file is present in the working directory

**Read this entire SKILL.md before writing any code.**

---

## Library Stack

### Primary: build123d
- Modern OpenCASCADE Python wrapper; native Apple Silicon arm64 wheels
- Clean, Pythonic API for STEP reading, assembly traversal, geometry queries
- Offscreen PNG rendering via: build123d → export GLB/OBJ → trimesh → pyrender
- Install: `pip install build123d trimesh pyrender pillow numpy pandas anthropic`
- ARM64 OCP wheel required first — see INSTALL.md

### Fallback: FreeCAD headless
- Native macOS-arm64 build available via conda-forge (verified Nov 2024)
- Used when build123d fails to load or render a specific file
- Invoked via subprocess calling `FreeCADCmd` or by importing `FreeCAD` + `Part`
- **Never import FreeCADGui** — headless only, GUI modules will crash
- Install: `conda install -c conda-forge freecad` (arm64 native, no Rosetta)

### Translation: Claude API (claude-sonnet-4-20250514)
- Translates Chinese part names with manufacturing context awareness
- e.g. 安装支架 → "Mounting Bracket" (not just "installation support")
- Requires ANTHROPIC_API_KEY in environment

### Supporting libraries (both tracks)
- `Pillow` — PNG save/compose
- `numpy` — geometry math
- `pandas` — CSV/BOM output
- `trimesh` + `pyrender` — GLB→PNG rendering fallback path

---

## File Structure

```
step-processor/
  step_processor.py       ← CLI entry point + REPL
  modules/
    loader.py             ← STEP loading (build123d primary, FreeCAD fallback)
    bom.py                ← Assembly tree traversal + BOM extraction
    renderer.py           ← Offscreen PNG rendering (6 views)
    translator.py         ← Claude API translation layer
    rewriter.py           ← STEP label patcher (_EN copy only, never modifies source)
    query_engine.py       ← Geometric query handler (holes, faces, dimensions)
  INSTALL.md
  SKILL.md
```

---

## Dual-Track Fallback Pattern

Every module must implement this pattern:

```python
def load_step(filepath):
    try:
        return _load_via_build123d(filepath)
    except ImportError:
        logger.warning("build123d not available — falling back to FreeCAD")
        return _load_via_freecad(filepath)
    except Exception as e:
        logger.warning(f"build123d failed ({e}) — falling back to FreeCAD")
        return _load_via_freecad(filepath)
```

Always log fallbacks at WARNING level so the user knows which path ran.

FreeCAD fallback invocation pattern (subprocess, cleanest isolation):
```python
import subprocess, json, tempfile

def _load_via_freecad(filepath):
    script = f"""
import FreeCAD, Part, json
shape = Part.read("{filepath}")
# extract data, write to temp JSON
"""
    result = subprocess.run(["FreeCADCmd", "--console", "-c", script], capture_output=True)
    # parse result...
```

---

## Camera Views

Always produce these 6 views unless user specifies otherwise:

| View Name  | Camera Direction          | File Suffix      |
|------------|---------------------------|------------------|
| Front      | +Y looking toward origin  | `_front.png`     |
| Bottom     | -Z looking up             | `_bottom.png`    |
| Left       | -X looking right          | `_left.png`      |
| Right      | +X looking left           | `_right.png`     |
| Iso Left   | +X+Y+Z (left-forward)     | `_iso_left.png`  |
| Iso Right  | -X+Y+Z (right-forward)    | `_iso_right.png` |

Default resolution: 1024×768. All PNGs saved to same folder as source STEP file.

---

## Output Naming Convention

Given input: `/path/to/EnclosureA.step`

| Output              | Path                              |
|---------------------|-----------------------------------|
| Thumbnails (×6)     | `/path/to/EnclosureA_front.png` etc. |
| BOM CSV             | `/path/to/EnclosureA_bom.csv`    |
| English STEP copy   | `/path/to/EnclosureA_EN.step`    |

**Never overwrite or modify the source STEP file.**

`_EN.step` is only created when Chinese labels are detected. If all labels are
already ASCII/English, skip silently and note it in output.

---

## BOM CSV Schema

```
part_number, part_name_original, part_name_english, quantity, level, parent,
bbox_x_mm, bbox_y_mm, bbox_z_mm, notes
```

- `level` = assembly depth (0 = root, 1 = direct children, etc.)
- `parent` = part_name_english of parent assembly
- `notes` = translator flags (e.g. "machine-translated", "ambiguous term")
- Bounding boxes in millimeters, 2 decimal places
- If no translation needed, `part_name_original` == `part_name_english`

---

## Translation Logic

```
bom.py extracts all part names
  → translator.py checks each name for CJK unicode range (\u4e00-\u9fff)
  → if any found: batch call Claude API with this system prompt:

    "You are a mechanical engineering translator specializing in Chinese
     manufacturing CAD files for display and enclosure products. Translate
     the following part names from Chinese to English. Preserve technical
     precision. Use standard hardware terminology. Output ONLY a JSON object
     mapping original → translated, nothing else.
     Example: {\"安装支架\": \"Mounting Bracket\", \"螺钉M4\": \"M4 Screw\"}"

  → inject translations into BOM CSV
  → if translation requested: produce _EN.step via rewriter.py
```

Flag uncertain translations in the `notes` column rather than silently guessing.

---

## STEP Label Rewriting (_EN copy)

`rewriter.py` produces a translated copy — line-by-line, targeted replacement:

1. Read source file line by line
2. Identify lines containing `PRODUCT('`, `PRODUCT_DEFINITION_CONTEXT(` etc.
3. Extract quoted name strings (first argument of PRODUCT entity)
4. Replace CJK strings with translated equivalents from the translation map
5. Write to `{filename}_EN.step`
6. Validate: entity `#` count before == after. If mismatch → abort, warn, delete partial output

**Do not use broad regex that could touch entity reference numbers (#123 etc.)**
Target only the quoted string arguments of known STEP entity types.

---

## Geometric Query Engine

`query_engine.py` handles natural language queries about loaded geometry.

### Supported Query Types

| Query pattern                    | What it extracts                                      |
|----------------------------------|-------------------------------------------------------|
| "list all mounting holes"        | Cylindrical faces, axis ⊥ to primary face, dia < 15mm|
| "list all holes"                 | All cylindrical through-features                      |
| "holes diameter [N]mm"           | Filter holes by diameter                              |
| "tapped holes" / "threaded"      | Cylinders with helical features (if present in model) |
| "face count"                     | Total face count by type (planar, cylindrical, etc.)  |
| "bounding box"                   | Overall model extents in mm                           |
| "part [name] dimensions"         | Bounding box of a specific named part                 |
| "largest face"                   | Largest planar face area in mm²                       |
| "wall thickness"                 | Min distance between opposing parallel faces          |
| "all parts"                      | Full assembly listing with quantities                 |

### Query Invocation

**CLI (single query, exit after):**
```bash
python step_processor.py enclosure.step --query "list all mounting holes with diameter and depth"
```

**REPL (interactive, geometry stays loaded in memory between queries):**
```bash
python step_processor.py enclosure.step --repl
> list mounting holes
> bounding box
> how many M4 holes
> exit
```

### Query Output Format

Always return a structured table:
```
MOUNTING HOLES — EnclosureA.step
─────────────────────────────────────────────────────────────
 #   Part Name          Diameter    Depth      Position (x,y,z)
─────────────────────────────────────────────────────────────
 1   Front Panel        4.20 mm     8.00 mm    (12.50, 0.00, 45.20)
 2   Front Panel        4.20 mm     8.00 mm    (87.50, 0.00, 45.20)
─────────────────────────────────────────────────────────────
Total: 8 holes across 3 parts
```

---

## CLI Reference

```
python step_processor.py <file.step> [options]

Options:
  --thumbnails          Generate 6 PNG views (default: on)
  --no-thumbnails       Skip PNG generation
  --bom                 Export BOM CSV (default: on)
  --no-bom              Skip BOM export
  --translate           Auto-translate Chinese labels in BOM + produce _EN.step
  --no-translate        Skip translation even if Chinese labels detected
  --query "..."         Run a single geometric query and exit
  --repl                Enter interactive query REPL
  --resolution WxH      Thumbnail resolution (default: 1024x768)
  --views front,iso_left,iso_right  Comma-separated subset of views
  --verbose             Show library selection, fallback notices, timing
```

**Default behavior (no flags):** `--thumbnails --bom` plus auto-detect translation.

---

## Error Handling

| Condition                          | Behavior                                               |
|------------------------------------|--------------------------------------------------------|
| File not found                     | Exit with clear message, no partial output             |
| Neither library available          | Exit with INSTALL.md reference                         |
| build123d render fails             | Auto-fallback to FreeCAD path, log warning             |
| Translation API unavailable        | Write BOM with original names only, note in output     |
| STEP has no assembly structure     | Treat as single part, one-row BOM                      |
| Chinese detected, no translate flag| Auto-translate by default (skip with --no-translate)   |
| _EN.step entity count mismatch     | Abort rewrite, warn, delete partial file               |
| FreeCAD headless GUI module import | Catch ImportError, skip that module, continue          |

---

## INSTALL.md Summary

**build123d (primary — native arm64, no conda):**
```bash
# 1. Install the arm64 OCP wheel from CadQuery GitHub releases:
#    https://github.com/CadQuery/OCP/releases — pick macosx_*_arm64.whl for your Python
pip install path/to/cadquery_ocp-*.whl

# 2. Install build123d and rendering stack
pip install build123d trimesh pyrender pillow numpy pandas anthropic

# 3. Set API key
export ANTHROPIC_API_KEY=sk-ant-...
```

**FreeCAD (fallback — native arm64 via conda):**
```bash
# Install Miniforge arm64 if not already present:
# https://github.com/conda-forge/miniforge/releases

conda install -c conda-forge freecad
# Verify headless works:
FreeCADCmd --version
```

No Rosetta. No x64 sub-environment. Both libraries run natively on Apple Silicon.

---

## Notes for Claude

- Default run = thumbnails + BOM + auto-translate if Chinese detected
- When a user asks a geometry question, invoke query_engine — don't describe the STEP file from the BOM alone
- Always confirm output file locations at end of run
- If Chinese labels found, proactively note it and offer to show the translation map
- In REPL mode, keep responses tight — it's a terminal session
- When describing holes or geometry, lead with the table, not prose
- If build123d fallback fires, tell the user which library ran

---

## Sub-skill: External Dimensional Diagram

### Purpose
Generates a standardized external dimensional diagram for installation planning,
product reference, and non-CAD users. NOT a manufacturing drawing.
Think: installation reference sheet with clean orthographic views + one ISO.

### Terminology
Use: "external dimensional diagram"
Never use: shop drawing, manufacturing drawing, fabrication drawing

### Entry point
```python
from modules.external_diagram import step_external_diagram

meta = step_external_diagram(
    path="enclosure.step",
    mode="enclosure_only",              # or enclosure_plus_mounting, mounting_only
    mapping_file="MR28UW_mapping.json", # optional
    datablock_file="MR28UW.md",         # optional, auto-detected if omitted
    options={
        "style": "line_drawing",        # or "rendered" — auto if omitted
        "pdf": True,                    # default False
        "mounting_variant": "Wall Mount", # for enclosure_plus_mounting mode
        "render_variants": True,        # generate diagram per mounting variant
    }
)
```

### Modes
- `enclosure_only` — enclosure body only (default when no mapping file)
- `enclosure_plus_mounting` — enclosure + selected mounting subassembly
- `mounting_only` — mounting geometry only

### Styles
- `line_drawing` — pure SVG wireframe (MR28 reference style). Auto-selected for simple enclosures.
- `rendered` — rendered ISO views composited with dimensioned orthographic line views
  (MR16 reference style). Auto-selected for complex assemblies (>200 faces).

### Layout auto-selection
- `single_sheet` — default for most models
- `multi_page` — auto-selected when any dimension > 1200mm or part count > 60

### Outputs per run
| File | Always? | Description |
|------|---------|-------------|
| `{stem}__external-diagram.svg` | Yes | Source SVG, always retained |
| `{stem}__external-diagram.png` | Yes | PNG export via cairosvg |
| `{stem}__external-diagram.pdf` | Optional (`--pdf` flag) | PDF via cairosvg |
| `{stem}__meta.json` | Yes | Full metadata per schema |
| Individual view PNGs | Rendered style only | front, side, rear, iso |
| Variant diagrams | If `render_variants=True` | One set per mounting variant |

### Dimensions shown
Required: overall width, height, depth
Auto-detected and shown when found:
- Active area / screen aperture (with diagonal in mm and inches)
- Mounting hole chain spacing (horizontal or vertical, auto-selected by readability)
- VESA pattern if detected
- Edge offsets to mounting geometry
Units: metric primary (mm) with imperial in parentheses, smaller italic

### Parts mapping file
JSON file per product model — classifies parts as enclosure/mounting/internal/fastener.
Drives show/hide logic per mode. Supports mounting variants.
Schema: `schemas/parts_mapping_schema.json`
Example: `schemas/parts_mapping_schema.json` → `examples` array

### Datablock / title block
Sourced from a `.md` file alongside the STEP file (auto-detected by matching stem name)
or passed explicitly as `datablock_file`.
Template: `templates/datablock_template.md`
Edit the template to configure: model number, display name, revision, date, company,
and any custom fields. Custom fields appear in the data block footer.

### Metadata schema
Full JSON schema: `schemas/external_diagram_schema.json`
Designed for AI consumption, Odoo product data integration, search indexing,
and CoWork documentation workflows. All geometry in mm.

### CLI
```bash
# Basic — enclosure only, auto style
python step_processor.py enclosure.step --diagram

# With options
python step_processor.py enclosure.step --diagram --diagram-mode enclosure_plus_mounting \
  --diagram-style line_drawing --diagram-pdf \
  --mapping mapping/MR28UW_mapping.json \
  --datablock MR28UW.md

# Render all mounting variants
python step_processor.py enclosure.step --diagram --diagram-mode enclosure_plus_mounting \
  --diagram-variants
```

### Notes for Claude
- When a user says "generate a diagram", "make a dimensional sheet", or "create reference drawing" → invoke this sub-skill
- Always confirm mode with user if unclear (enclosure_only vs with mounting)
- If no mapping file exists, default to enclosure_only and note it
- If multiple mounting variants detected in assembly tree, offer to render each
- The meta.json output is the primary artifact for downstream AI/workflow consumption
- cairosvg is required for PNG/PDF export: `pip install cairosvg`
- Weight/mass fields in meta.json are reserved for future Odoo product data integration
