# NativeCAD 0.14.0 "Rozhdestvensky" — the Bambu Lab X1 Carbon print pipeline

**140 tools — ten added since 0.13.0: `cad_build_orientation`, `cad_slice`, `cad_print`, `cad_print_status`, `cad_print_prep` (a new `print` subsystem), `cad_face_blend`, `cad_sheet_feature`, `cad_sheet_miter`, `cad_padstack`, `cad_trace_section`; nothing removed or renamed.** A body built in a session now goes to a `.gcode.3mf` and, on a printer whose owner has turned Developer Mode on, to a job running on the plate — without leaving the tool surface. Command schema 1.27.0, 80 document operations, additive throughout.

## What a user can now do, end to end

```
cad_print_prep { body: "op7", printer: "x1c", nozzle: 0.4, material: "PLA", supports: "auto" }
```

One call runs five stages and answers with one report:

1. **DFM against the machine** — `cad_dfm_check` pack `fdm_x1c` (CADSF-1772): 23 sourced rules over 14 concerns, read off the X1C's own published profile numbers — the 256x256x250 mm envelope *including* the 18x28 mm front-left exclusion corner, minimum wall, overhang, bridge, free-standing pin, embossed text, elephant foot, Z anisotropy, multicolour purge cost.
2. **Orientation** — `cad_build_orientation` (CADSF-1773) ranks plate orientations by a candidate sweep over that same analysis with six normalised penalty terms and an exclusion-corner fit, and returns the row-major 4x4 the 3MF writer consumes.
3. **A Bambu project, not a mesh** — `cad_export_body format:"3mf-bambu"` (CADSF-1774) writes `Metadata/model_settings.config`, `Metadata/project_settings.config`, thumbnails and per-body filament slots mapped by colour. Bambu Studio opens it with no "not from Bambu Lab" dialog.
4. **Slice** — `cad_slice` (CADSF-1775) spawns a **user-installed** Bambu Studio or OrcaSlicer (`BAMBU_STUDIO_BIN`, then `PATH`, then the well-known install locations) and hands back the `.gcode.3mf` with the slicer's own `predictionS` and `weightG` read out of `Metadata/slice_info.config`.
5. **Send** — `cad_print` (CADSF-1776) uploads over implicit-TLS FTP on port 990, publishes MQTT `project_file` with the uploaded file's md5 and the AMS mapping, and waits for the printer itself to say `PREPARE`/`RUNNING`. `cad_print_status` follows the job, with percent and AMS trays.

Each stage is still its own tool, so any of them runs alone for the detail the single report trims. `cad_print_prep` **refuses at the first failing stage under that stage's own code**, with `details.stage` saying where it stopped — a DFM failure never pays for a slice. `force:true` carries a DFM block, `orient:"keep"` carries an orientation refusal, and `slicer_missing` falls back to the estimator instead of failing.

**No slicer installed is a first-class answer, not a gap.** `ops/printEstimate.js` gives a `{low, high}` band for time and mass from disjoint shell / skin / infill volumes, with the two fudge factors it has (`FLOW_EFFICIENCY` 0.55, `MASS_BAND` ±12 %) named in the report. `method` is **required by the output schema** — `"slice"` is measured, `"estimate"` is modelled — so an estimate can never be read as a print time.

**A printer on Bambu Cloud is also a complete answer.** `cad_print mode:"connect"` returns the file and a `bambu-connect://import-file?…` link a human opens in Bambu Connect. It needs no LAN access code, no Developer Mode, no LAN access and none of the adapter's dependencies. It is stamped `started:null`, so a Connect link is never mistaken for a started print.

**The design loop closes too.** `tools/design_loops/fdm_x1c.js` takes the L bracket with a 0.6 mm rib and a 1.0 mm pin — two ERROR rules — and converges in **three iterations**: `cad_move_face` by the pack's own measured deficit on the rib, then a radius step on the pin cylinder, then every verdict passes and the part goes through `cad_print_prep` without `force`.

## Still a human's job on the X1C chain

Nothing in this release pretends otherwise, and `docs/PRINT_PIPELINE.md` says it in full:

- **Install a slicer.** Bambu Studio is AGPL-3.0-or-later; there is no licence under which this repository could ship one, which is why `slicer_missing` is a coded refusal with per-OS install advice and not something a bundle papers over.
- **Prove a real slice.** No Bambu Studio has yet been run against this adapter: `dice.gcode.3mf` must open in Bambu Studio's preview with no "not from Bambu Lab" dialog (CADSF-1775's acceptance), the two-slot fixture must report two filaments each with `used_g > 0`, and an off-bed part must refuse `slicer_object_outside`.
- **Fill the estimator calibration.** `tests/fixtures/print/estimator_calibration.json` holds ten parts whose estimator side is real and whose **slicer side is `null` on purpose**; the ≥ 8-of-10 band gate skips, loudly, until a host with a slicer fills it in.
- **Decide about Developer Mode.** LAN-only ON → reboot → Developer Mode ON, and re-read the LAN Access Code afterwards because toggling LAN mode changes it. It takes the printer off Bambu Cloud and Handy while it is on. The owner decides; `mode:"connect"` is the answer when they say no.
- **Supply IP, serial and access code at run time.** `CADSOLVER_PRINTER_IP`, `CADSOLVER_PRINTER_SERIAL`, `CADSOLVER_PRINTER_ACCESS_CODE` — there is no third source and none of the three is ever committed or echoed back.
- **Run the live LAN acceptance.** A real X1C, a real first layer, and `cad_print_status` reporting it. The in-process mock broker and FTP server (`packages/print/test/mockPrinter.js`, real MQTT 3.1.1 and FTP on Node's own `net`/`tls`) prove every packet, topic, refusal and mapping without a printer — what they cannot prove is that a machine starts.
- **Install the LAN adapter where you want LAN.** `npm install --prefix packages/print` (`mqtt`, `basic-ftp`). The `.mcpb` deliberately does not ship it, so a packaged install refuses `mode:"lan"` with `print_deps_missing` and keeps `mode:"connect"` working.

## New refusal vocabulary

Every one carries its code in `details.code`; every print refusal names the Connect fallback.

- `cad_slice`: `slicer_missing`, `slicer_failed` (with the exit code and the **stderr tail** — `cannot open display` is the usual first one), `slicer_object_outside`, `slicer_profile_mismatch`.
- `cad_print` / `cad_print_status`: `printer_target_missing`, `printer_unreachable`, `printer_auth_failed`, `printer_not_developer_mode` (connected, published, never reported — that silence *is* the diagnosis), `print_ams_slot_unmapped` (raised **before** the upload, so a plate nobody can feed costs two seconds and not a 40 MB transfer), `print_file_rejected`, `print_deps_missing`, `printer_cert_mismatch`.
- `cad_print_prep`: `dfm_blocked` with `blockedBy`, plus the failing stage's own code and `details.stage`.
- Booleans: `tangent_contact_pinch` (CADSF-1624) — a nested tangency is recorded rather than chained through, and the pinch is refused by name.
- `cad_face_blend` (CADSF-1611): 23 named `face-blend-*` reasons. A full round is *solved*, not chosen, and an unsolvable one says which of the 23 it is.
- `cad_padstack`: 19 `padstack_*` reasons (`padstack_span_not_copper`, `padstack_pad_off_via_span`, `padstack_microvia_not_adjacent`, `padstack_backdrill_ends_in_copper`, `padstack_stub_exceeds_max`, `padstack_volume_audit_failed`, …). `cad_trace_section`: `trace_section_bad_route`, `trace_section_bad_parameter`, `trace_section_overlapping_conductors`, `trace_section_mask_thicker_than_gap`, `trace_section_volume_audit_failed`. Shaft keyways gain `keyway_unreadable` (CADSF-1856) — a keyway the B-rep cannot be read back off is refused by name, not guessed at.

## The rest of the surface since 0.13.0

- **Persistent naming.** Every executor result carries a `topoMap`: `{modified: [[oldId, newId], …], generated, deleted, counts, source, derived}` over STEP face and edge-curve ids, with an unchanged id in none of the three lists and counted in `counts.unchanged` — the OCCT / FreeCAD convention. `source` says whether the operator handed its own correspondence up or the map was derived (CADSF-1627).
- **PCB.** `cad_padstack` builds analytic PTH / microvia solids and emits the via bore column as real bodies — fill, air void, back-drill void (CADSF-1756, CADSF-1847). CADSF-1854 finishes the solid model: a meniscus fill top (spherical cap on the `ri` ring, closed-form cap volume), resin bodies in the etched-away copper sharing the pad and antipad walls, cap plating trimmed against same-side solder mask and *measured* gone, and split heights that agree across mixed vias because the bodies share **edges**, not merely coincident surfaces — with `BrepMassProperties` closing a `.F.`-oriented wrapping face on the correct pole, which is what makes those volume audits trustworthy. `cad_trace_section` (CADSF-1757) turns a trace cross-section into solids and, with CADSF-1855, sweeps it along a **route** (volume by Pappus, per body), shares interface faces between neighbouring bodies **by identity**, and does conformal plating in which paired conductors may differ in `tFoil` / `tPlating`. A `.kicad_pcb` reader feeds the neutral layout model (CADSF-1747).
- **Sheet metal.** `cad_sheet_feature` (hem, jog, sketched bend, tab, cross-break, vent) and `cad_sheet_miter` (CADSF-1613); bend methods beyond K-factor — `bendAllowance`, `bendDeduction`, `bendTable`, `gauge` + `material` through `data/sheetmetal/gauge-table.json` (CADSF-1614); overrides scoped to the document and `autoDimension` ids to the call (CADSB-1201).
- **Blends and chamfers.** `cad_face_blend` — the face-blend family, full round solved (CADSF-1611); asymmetric and distance–angle chamfers (CADSF-1612); blend **networks on by default**, `CADSOLVER_BLEND_NETWORK=0` or `networkSolver:false` to opt out (CADSF-1724).
- **Shafts.** `surface:'bore'` on the shaft verbs, shoulder and centre drill on a closed meridian loop (CADSF-1710); an internal keyseat in a bore, and `recoverKeyways` naming a non-keyway axis-parallel face instead of mistaking it for one (CADSF-1856); `_revolveProfiles` pruned like `_shaftProfiles` (CADSF-1709).
- **Assembly.** `cad_assembly_contacts` no longer reports a false interference between coaxial cylinders (CADSB-1280).
- **Aero.** `cad_wind_tunnel` gains `sizing` — a refinement zone with the uniform-vs-refined Cd delta stated (CADSF-1568).
- **Kernel.** Shewchuk exact predicates, oracle-proven and wired into the 2-D boolean arrangement (CADSF-1628, CADSF-1748); `healFace`'s seam solve widened to plane/cylinder, cylinder/cylinder and plane/cone with arc re-mint (CADSF-1720); `ops/envFlag.js` centralises `CADSOLVER_*` gate parsing and `IncrementalEval.tessKey` keys the raw environment through `envSig`, so a cache entry cannot outlive the flags that produced it (CADSF-1687, CADSF-1688); `RENDER.writePNG` is deterministic, measured then pinned then tested (CADSB-1198).
- **FEA.** `cad_fea_run.meshQuality` puts the boundary re-cut and tet-quality pass on the public tool, with no schema default (CADSF-1811).
- **Gates behind the build.** The bundle-freshness gate re-hashes every file esbuild bundled rather than a recorded subset (CADSB-1281); `commands/schema/generated/command-schema.v1.json` has a staleness gate of its own (CADSB-1282); `tools/worker.sh` repairs an unrepaired gitdir (CADSB-1283). None of them changes runtime behaviour; all of them are why this build can be trusted.
- **Certification.** 22 CSWA / CSWP / CSWPA-SM / CSWPA-SU cases, 205 checks (`tools/cert_conformance.js`).

## Known reds carried

`packages/mcp/test/tool-list-budget.test.js` — **four of its budget assertions, at 140 tools (CADSB-1273)**. Measured on this build: `tools/list` is **431,283 B across 140 tools (421.2 KiB)**, over both the CADSF-1170 / CADSB-1238 360 KiB total line *and* the 400 KiB absolute ceiling; `cad_export_body` is 18,734 B against the 16 KiB backstop its documented exception carries; `cad_face_blend` at 12,307 B is the first non-exception tool past the 12 KB top-tool line. What still holds, measured the same way: **3,080.6 B/tool — a 39.9 % reduction off the 5,128 B/tool baseline** (the growth-proof per-tool gate the 400 KiB line backstops), the 3.3 KB/tool CADSF-1170 per-tool ceiling, every tool description inside the 420 B cap (largest 420 B), `cad_dfm_check`'s ajv round-trip of 17 real call bodies, and the `cad_help` trimmed/untrimmed/unknown round-trip (11/11). **Nothing was dropped from the surface to make the number smaller** — the schema work belongs to CADSB-1273, the way CADSB-1238 did it at 0.13.0.

The two 0.13.0 reds (`cadsf-1296.test.js`, `cadsf-1448.test.js`) were not re-run for this cut; they are on the board and neither is a shipped behaviour.

## Gates

bundle-smoke over stdio on the shipped `.mcpb` (initialize 0.14.0, tools/list 140, `cad_server_info` staged git `3bc5ef2a`, licence gate refuses `cad_box` by name) · package.test.js 230 · cadsf-1148 58/58 · cadsf-1285 57/57 · schema-sync 511/511 · cadsf-1662 19/19 · extensions 37/37 · stage-kernel-guard 11/11 · `stage-kernel.js --check` clean (93 entry modules, 402-file closure, 4 verbatim assets) · tool-list-budget **red, carried** (above) · `dist/server.min.js` sha256 `6a0d1572…` identical in the tgz and the `.mcpb`.

**Downloads are served from featureboard.ai** (see `latest.json` for bytes + sha256):
https://featureboard.ai/nativecad/releases/nativecad-mcp-0.14.0.mcpb
Staged kernel: git 3bc5ef2a, tree 3665ec5e4887, 383 files. Built from the LAND-W7 worktree at 3bc5ef2a with `SOURCE_DATE_EPOCH` pinned to its commit time (1790118559), esbuild 0.28.1; no registry access, so the `.mcpb` was packed by the offline `plugin/lib/zip.js` writer rather than the `@anthropic-ai/mcpb` CLI (spec-correct ZIP; build.js ran its own manifest checks either way).
