# File format — .mbl, .mblext and import/export

A bench travels as a single JSON document with the **.mbl** extension. The guiding decision (from the plan) is that **the DSL source is the authority and the compiled document is a cache** — so a .mbl always carries the source, and can always be recompiled if the cache is missing or stale.

## The .mbl container

```jsonc
{
  "mblVersion": 1,
  "type": "bench",
  "engineApiVersion": 1,
  "name": "Bancada 260x60",
  "workbenches": ["static"],        // categories the bench uses
  "units": "mm",                     // always millimeters
  "sources": [{ "name": "main.js", "content": "export default defineBench(…)" }],
  "entrySourceName": "main.js",
  "document": { /* compiled cache — components/connections/drivers */ },
  "meta": {
    "envelope": { "x": 2600, "y": 902, "z": 600 },
    "bom": { "items": [ … ], "totals": { … } },
    "createdWith": "mybenchlab"
  }
}
```

- **sources / entrySourceName** — the authority. Multi-source benches (with `group({ source })`) list every file; the entry is the one compiled first.
- **document** — a cache. It may be absent (an exported converter output, for instance); the importer recompiles from the sources.
- **meta.envelope / meta.bom** — the declared outer box and the bill of materials, filled from the compiled document at export time.

### Importing and exporting

From the benches list, **Import .mbl** reads a file and inserts it as a new bench for you; **Export .mbl** downloads the current bench (recompiled so the cache and BOM are fresh). The same operations are available as the Meteor methods `benches.importMbl` / `benches.exportMbl` and — pure and framework-free — as `serializeMbl` / `parseMbl` in the engine's `sim/mbl` layer. `parseMbl` validates the version, units, sources and type, and tolerates a missing `document`.

## Custom extensions (.mblext)

A distributable pack of reusable kinds/junctions is the **same container** with `type: "extension"` and a `manifest`:

```jsonc
{
  "mblVersion": 1,
  "type": "extension",
  "manifest": { "id": "demo.brackets", "version": "1.0.0", "categories": ["static"] },
  "sources": [{ "name": "kinds.js", "content": "/* register*/defineComponent code */" }]
}
```

A bench references extensions through `extensions: [{ id, version, embedded? }]`. **v1 supports embedded extensions only** — the bench carries a full copy of the pack inline for portability; its sources are prepended to the bench's own sources at compile time (namespaced `<extId>/<name>`). A bare `{ id, version }` reference without an embedded copy cannot be resolved yet: there is no registry or marketplace in v1.

> **Security.** An extension's sources are **executable code**, at the same trust level as a bench's own sources — which the platform already evaluates in a sandbox (`new Function`, no `window`/DOM/Meteor). Importing a third-party extension therefore runs third-party code under that sandbox. A permission/registry model is a prerequisite before any marketplace; v1 stays with embedded, user-provided packs, and `benches.importMbl` rejects standalone extension files.

## Importing a scene3d scene

The MetalScout **scene3d** format (declarative JSON: parts positioned by origin, extruded along +Z, XYZ-degree rotations) converts to a .mbl bench with `convertScene3dToMbl({ scene })`. Each part maps to a static kind — `tubeSquare`→`steelTubeSquare`, `tubeRectangular`→`steelTubeRectangular`, `sheet`→`steelSheet`, `angle`→`steelAngle`, a wood `box`→`woodPanel`, and anything else to `genericSolid` (a bounding-box stand-in). The converter reconciles the two coordinate conventions — scene3d's origin-extrusion vs the engine's centered boxes — so the world bounding boxes match exactly. **v1 emits explicit transforms only, with no inferred junctions**; connectivity still holds because touching parts count as one body. The converter stamps `statics.importedFrom('scene3d')` into the generated source, which tells the static validator this is machine-import provenance — so contact-only connectivity is accepted silently (an *authored* bench without junctions gets the `W_STATIC_CONTACT_NO_JOINT` nudge instead).

## Bill of materials

`computeBom({ document })` groups identical parts (same kind + same params, ignoring cosmetic color) into lines with a quantity, a unit weight and a rolled-up total, plus grand totals. Weight comes from each kind's `dimensions().weightKg` — the same honest figure the validators and doc blocks report. The BOM is embedded in `meta.bom` on export: the natural bridge to a MetalScout material list, with no extra AI step.

Each BOM line also carries a **structured descriptor** (plan §19) — `{ material, format, dims }`, canonical English fields in millimeters derived from the part's params (e.g. `{ material: 'steel', format: 'rectangular-tube', dims: { w: 30, h: 50, wall: 2, length: 1200 } }`). It is purely additive to `meta.bom` — the `mblVersion` is unchanged — so older readers ignore it and newer ones can map each part straight onto a catalog item. The workspace **Materiais** panel exports the list as CSV or JSON, and the `bench_bom` MCP tool returns the same structured BOM (descriptors + quantity + weight + a pt-BR label) for agents to consume.
