Skip to content

feat(mcp): STEP import keeps B-rep instead of baking to mesh - #808

Open
ecto wants to merge 4 commits into
mainfrom
claude/modest-hodgkin-bb1f3a
Open

feat(mcp): STEP import keeps B-rep instead of baking to mesh#808
ecto wants to merge 4 commits into
mainfrom
claude/modest-hodgkin-bb1f3a

Conversation

@ecto

@ecto ecto commented Aug 16, 2026

Copy link
Copy Markdown
Owner

What

import_step tessellated every body on the way in, so an imported part arrived as a triangle soup: no analytic faces, booleans and fillets degraded to mesh operations, and export_cad STEP refused the part it had just imported. Round-tripping a vendor model through vcad was impossible.

Imports now default to B-rep. Each body becomes a lazy step_import node carrying a path plus a solid index, resolved through a kernel-side registry of STEP bytes (the WASM kernel has no filesystem, so the bytes are handed to it explicitly). as_mesh: true keeps the old tessellated import for the cases that actually want a mesh — rendering, physics colliders.

Why the registry needs a rebind hook

The registry lives in WASM memory, so it is empty for any document this process did not import itself. A document-entry hook re-registers a document's STEP sources when it is opened or rehydrated after a restart — otherwise a reopened save would evaluate to nothing. step_import resolution failures are a hard error naming the path, not a silent "no geometry", because a document that quietly evaluates to nothing because its STEP moved is far harder to diagnose.

open_in_browser bakes step_import nodes to meshes for the trip only — the browser cannot resolve a reference into this server's registry and would render an empty part. The session's own document keeps its B-rep.

Two pre-existing bugs this surfaced

  1. STEP export of any imported solid panicked. Topology::half_edge_dest asserted next.is_some(), but a STEP import builds both directions of every edge and only loop-used half-edges get next wired — so an edge on a skipped face has a loop-less side. Exporting an imported body died with half-edge has no next. It now falls back to the twin's origin, which is the same vertex the loop would have named. This blocked the CLI too; it was just unreachable through MCP while import was mesh-only.

  2. A STEP assembly reads as "no geometry." The flat reader follows the product anchor exactly one hop, so bodies reached via SHAPE_REPRESENTATION_RELATIONSHIP are invisible to it — and vendor files routinely are assemblies. The failure now names the assembly and its part/placement counts instead of claiming the file is empty.

Reviewer notes

  • Security: registerDocumentStepSources reads paths out of a caller-supplied document, so it is confined to the same export root import_step writes into — a crafted document cannot turn a reopen into an arbitrary file read.
  • The import_step hook is registered once per engine (createServer runs per connection).
  • tool-surface.fixture.json is regenerated for the intended import_step description + as_mesh schema change, and nothing else.
  • Three artifact-store tests now pass as_mesh: true: they cover report surfacing and large-result offload against stub engines that only implement the mesh entry point. Real B-rep import is covered in step-import.test.ts against the live kernel.
  • Generated packages/kernel-wasm/vcad_kernel_wasm* artifacts are deliberately not committed, per CLAUDE.md — the kernel needs a rebuild (or the wasm-refresh workflow on main) for registerStepSource to exist.

Known gap

The named verification target — a SolidWorks RMD-X6-P20-60 vendor file — still cannot be imported end-to-end, because it is an assembly (16 part definitions, 24 placements). This is not a regression: the old mesh import failed on it identically. Real assembly import needs per-instance placement in the IR and world-transform composition through nested instances; placing bodies without transforms would stack them at the origin, so it is left as follow-up rather than half-implemented.

Testing

  • cargo test -p vcad-eval -p vcad-ir -p vcad-kernel-step -p vcad-kernel-topo — green, including 3 new tests in crates/vcad-eval/tests/step_import_brep.rs (B-rep survives + can_export_step; solid_index selects the right body; a missing source errors with the path named).
  • cargo clippy on all touched crates — clean.
  • Full TS suite: 1572 passed. The 6 remaining failures are all in packages/app (last-opened, circuit-build, ecad-feature-inspector, parameter-influence, pcb-interference) and were confirmed failing identically on a stashed clean tree — pre-existing, untouched by this branch.
  • STEP round-trip verified against the live kernel: import → boolean → export_cad STEP now succeeds where it previously panicked.

🤖 Generated with Claude Code

import_step tessellated every body on the way in, so an imported part
arrived as a triangle soup: no analytic faces, booleans and fillets
degraded to mesh operations, and export_cad STEP refused the part it had
just imported. Round-tripping a vendor model through vcad was impossible.

Imports now default to B-rep. Each body becomes a lazy `step_import` node
carrying a path plus a solid index, resolved through a kernel-side registry
of STEP bytes (the WASM kernel has no filesystem, so the bytes are handed
to it explicitly). Analytic faces survive into booleans, fillets, and STEP
export. `as_mesh: true` keeps the old tessellated import for the cases that
actually want a mesh — rendering and physics colliders.

Because the registry lives in WASM memory, it is empty for any document
this process did not import itself. A document-entry hook re-registers a
document's STEP sources when it is opened or rehydrated after a restart,
so a reopened save stays B-rep-backed instead of failing to evaluate. Paths
are confined to the same export root import_step writes into, so a crafted
document cannot turn a reopen into an arbitrary file read.

open_in_browser bakes step_import nodes to meshes for the trip only: the
browser cannot resolve a reference into this server's registry and would
otherwise render an empty part. The session keeps its B-rep.

Two bugs surfaced while verifying this, both pre-existing:

- Topology::half_edge_dest asserted `next.is_some()`, but a STEP import
  builds both directions of every edge and only loop-used half-edges get
  `next` wired. Exporting any imported solid panicked with "half-edge has
  no next". It now falls back to the twin's origin — the same vertex the
  loop would have named. This blocked the CLI too; it was simply
  unreachable through MCP while import was mesh-only.

- A STEP assembly (vendor files routinely are one) reads as "no geometry":
  the flat reader follows the product anchor a single hop, so bodies
  reached through SHAPE_REPRESENTATION_RELATIONSHIP are invisible to it.
  That failure now names the assembly and its part/placement counts
  instead of claiming the file is empty. Traversing assembly structure on
  import is left as follow-up work — placing bodies without their
  transforms would stack them at the origin, which is worse than a clear
  error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vcad-mcp Building Building Preview Aug 16, 2026 2:14am
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
mecheval Ignored Ignored Aug 16, 2026 2:14am
vcad Ignored Ignored Aug 16, 2026 2:14am
vcad-docs Ignored Ignored Aug 16, 2026 2:14am

Request Review

…-bb1f3a

# Conflicts:
#	crates/vcad-eval/src/lib.rs
#	packages/engine/src/index.ts
ecto and others added 2 commits August 15, 2026 22:08
cargo fmt --check flagged the signature of to_wasm_report as too long
for one line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
import-step emits one node per solid, but every one of them was built
without a solid index — so before this field existed they all resolved to
solid 0, and a multi-body STEP became N copies of the first body. Now each
node names the body it came from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant