# Technical Documentation

## Architecture

```
src/
├── ui/app.js              # Entry point — wires all modules to the DOM
├── map/
│   ├── map.js             # Leaflet init, draw controls, bbox selection
│   └── layers.js          # WMTS tile layer URLs + provider detection
├── topo/
│   ├── fetcher.js         # HTTP requests to SwissTopo / IGN APIs
│   ├── parser.js          # Raw API response → altitude arrays
│   └── grid.js            # ElevationGrid data structure
├── slicing/
│   ├── slicer.js          # Layer count + iso-contours via d3-contour
│   └── simplify.js        # Ramer-Douglas-Peucker polyline simplification
├── export/
│   ├── svg.js             # SVG generation per layer
│   ├── dxf.js             # DXF generation per layer (dxf-writer)
│   └── zip.js             # ZIP packaging (JSZip)
├── nesting/
│   └── nesting.js         # Bottom-Left-Fit bin packing
├── preview/
│   └── preview.js         # Three.js 3D scene
└── machines/
    ├── machines.json      # Preset machine profiles
    └── machine-ui.js      # Dropdown population + state
```

## Data flow

```
bbox selection
    → fetchElevationGrid()   [topo/fetcher.js]
    → ElevationGrid           [topo/grid.js]
    → computeLayers()        [slicing/slicer.js]
    → Layer[]
    → generateLayerSVG/DXF() [export/svg.js, dxf.js]
    → nestLayers()           [nesting/nesting.js]
    → Sheet[]
    → exportZip()            [export/zip.js]
```

## Event bus (CustomEvent)

Tous les modules communiquent via `document.dispatchEvent` / `document.addEventListener`. Aucun couplage direct entre modules UI.

| Événement | Émetteur | Payload (`e.detail`) | Écouteurs |
|-----------|----------|----------------------|-----------|
| `bbox:changed` | `map.js` | `{ bbox, provider, bboxMetrics }` | `app.js`, `bbox-display.js` |
| `bbox:cleared` | `map.js` | — | `app.js`, `bbox-display.js` |
| `topo:loaded` | `app.js` | `{ grid, altRange }` | `dimensions.js` |
| `dims:changed` | `dimensions.js` | `{ x, y, z, boardThickness }` | `app.js` |

## Adding a topo source

1. Add a provider constant in `src/map/layers.js`
2. Add a fetch function in `src/topo/fetcher.js` that returns `number[]` (altitude array, row-major)
3. Add a parser in `src/topo/parser.js`
4. Update `detectProvider()` in `src/map/layers.js` with the new geographic bounds

## Adding a laser machine

Add an entry to `src/machines/machines.json`:

```json
{
  "id": "my-machine",
  "name": "My Machine Name",
  "workArea": { "w": 400, "h": 300 },
  "defaultMargin": 5
}
```

## SVG output format

- Units: millimetres (`viewBox` in mm, `width`/`height` with `mm` suffix)
- `class="cut"` path: red stroke (`#ff0000`), no fill — the cut contour
- `class="engrave"` path: blue stroke (`#0000ff`), dashed — registration mark from layer above

## DXF output format

- Units: millimetres
- Layer `CUT` (color index 1, red): outer cut contour as LWPOLYLINE
- Layer `ENGRAVE` (color index 5, blue): registration contour as LWPOLYLINE

## External libraries (CDN, no build)

| Library | Version | Purpose |
|---------|---------|---------|
| Leaflet | 1.9.4 | Interactive map |
| Leaflet.draw | 1.0.4 | Rectangle selection |
| Three.js | 0.165.0 | 3D preview |
| d3-contour | 4.x | Iso-contour extraction (replaces custom marching squares) |
| JSZip | 3.10.1 | ZIP export |

## Contour extraction (d3-contour)

`slicer.js` utilise `d3-contour` pour extraire les iso-lignes à chaque seuil d'altitude. La lib pad automatiquement la grille avec des cellules virtuelles à valeur 0 sur tous les bords — ce qui garantit que les couches basses (dont le seuil est inférieur à l'altitude de tous les bords réels) génèrent un contour rectangulaire couvrant toute la zone, produisant une base plate sur la maquette.

Les coordonnées retournées sont en espace grille `[col, row]` et transformées en mm par `[col × dimX/(cols−1), row × dimY/(rows−1)]`.
