From 62b0553a893063ff205da0e5b7d7b201e1036d70 Mon Sep 17 00:00:00 2001 From: Irakli Kourtis Date: Tue, 13 Oct 2026 15:12:41 -0400 Subject: [PATCH] geometry: face-tagged 3MF follows SolidCraft's namespace and spec (#10) SolidCraft owns the 3MF face-tag format (its docs/formats/3mf-face-tags.md); the reader now matches its namespace, and docs/formats/tagged-mesh.md points there while documenting what ModelCraft's reader does and refuses. Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- crates/geometry/src/tagged_mesh.rs | 10 +-- docs/formats/tagged-mesh.md | 134 +++++++++-------------------- 2 files changed, 46 insertions(+), 98 deletions(-) diff --git a/crates/geometry/src/tagged_mesh.rs b/crates/geometry/src/tagged_mesh.rs index e98dfe0..e238a3f 100644 --- a/crates/geometry/src/tagged_mesh.rs +++ b/crates/geometry/src/tagged_mesh.rs @@ -1,8 +1,8 @@ //! Face-tagged meshes in 3MF packages (`docs/formats/tagged-mesh.md`). //! //! A 3MF package (3MF Core Specification 1.3) whose triangles may carry `ft:face`, the B-rep face -//! they lie on, in ModelCraft's face-tag namespace. SolidCraft writes it; any other 3MF reads as -//! untagged surfaces (one face each). Reading is plain parsing from bytes (no kernel call, no file +//! they lie on. SolidCraft defines the format and writes it (its `docs/formats/3mf-face-tags.md`); +//! any other 3MF reads as untagged surfaces (one face each). Reading is plain parsing from bytes (no kernel call, no file //! system), so it also runs in the browser. //! //! Packages are untrusted: the file, its parts, zip entries, XML nesting, vertex and triangle @@ -36,8 +36,8 @@ use modelcraft_model::MAX_COORD; const MIN_RELATIVE_DET: f64 = 1e-12; const CORE_NS: &[u8] = b"http://schemas.microsoft.com/3dmanufacturing/core/2015/02"; -/// The face-tag namespace, version 1. -pub const FACE_TAGS_NS: &str = "https://github.com/jyviko/modelcraft/formats/face-tags/2026/10"; +/// SolidCraft's face-tag namespace, version 1. +pub const FACE_TAGS_NS: &str = "https://github.com/bherbruck/solidcraft/formats/3mf-face-tags/2026/10"; /// One build item of a package: a named surface whose `tri_face` holds the face tags. #[derive(Debug, Clone, Default, PartialEq)] @@ -416,7 +416,7 @@ mod tests { use super::*; - const HEAD: &str = r#""#; + const HEAD: &str = r#""#; /// A writer built from docs/formats/tagged-mesh.md alone (SolidCraft's is a separate /// program): shortest round-trip coordinates, one object and build item per surface. diff --git a/docs/formats/tagged-mesh.md b/docs/formats/tagged-mesh.md index ba94759..ecb36eb 100644 --- a/docs/formats/tagged-mesh.md +++ b/docs/formats/tagged-mesh.md @@ -1,100 +1,48 @@ -# Face-tagged mesh exchange (3MF with face tags) +# Face-tagged meshes (3MF with B-rep face tags) -Version 1 (2026-10-13). Namespace `https://github.com/jyviko/modelcraft/formats/face-tags/2026/10`. +Besides opening a `.solidcraft` design, geometry can enter ModelCraft as a triangulated boundary +surface written by another program, with each triangle tagged by the B-rep face it lies on. +ModelCraft reads it into a `SurfaceMesh` (`modelcraft_geometry::tagged_mesh`, command +`geometry.import.mesh`) without involving the kernel. -The second way geometry enters ModelCraft, beside opening a `.solidcraft` design: a triangulated -boundary surface written by another program, each triangle tagged with the B-rep face it lies on. -ModelCraft reads it into a `SurfaceMesh` (`modelcraft_geometry::tagged_mesh`) with no kernel -involved. SolidCraft writes it (`file.export` with `format: "3mf-tagged"`). +## The format is SolidCraft's -## Decision: 3MF Core plus one optional attribute +SolidCraft defines and writes the format (**File → Export 3MF with Face Tags**, `file.export` with +`format: "3mf-tagged"`). The specification is +[`docs/formats/3mf-face-tags.md`](https://github.com/bherbruck/solidcraft/blob/main/docs/formats/3mf-face-tags.md) +in the SolidCraft repository. In short: -The file is an ordinary **3MF package** (3MF Consortium, *3D Manufacturing Format Core -Specification* 1.3, publicly available). The face tags are one attribute in a namespace of our -own, which the Core specification allows on `` and `` and which any 3MF consumer -ignores unless the namespace is listed in `requiredextensions` (it is not). +- The file is an ordinary 3MF Core 1.3 package. +- Each `` carries `ft:face` (its 0-based B-rep face index), and each `` carries + `ft:faces` (its face count). +- The namespace is `https://github.com/bherbruck/solidcraft/formats/3mf-face-tags/2026/10` + (version 1). It is not listed in `requiredextensions`. +- Coordinates are written in shortest round-trip `f64` form, so they read back exactly. +- Within an object, either every triangle is tagged or none is. -Why not a small binary format of our own (versioned header, f64 positions, u32 triangles, u32 -tags): +ModelCraft follows that specification and does not extend it. Because the format is 3MF, +untagged 3MF from any other program goes through the same reader, with one face per object. -- **It is already written and read.** SolidCraft exports and imports 3MF with hostile-input caps; - slicers, viewers and other CAD programs write it too. A tagged file opens in all of them, and - ModelCraft reads untagged 3MF from those programs through the same code ("geometry from other - tools later"). -- **One specification to maintain, published by a consortium**, not a byte layout we would have to - version, document and keep two implementations of in step. -- **Nothing is lost.** Coordinates are decimal text; written in shortest round-trip form (Rust's - `{}` for `f64`) every `f64` comes back bit for bit. Triangles and tags are integers. -- The costs (a zip and an XML parser, larger files, slower parsing than raw bytes) are acceptable - for an exchange file. Both libraries (`zip`, `quick-xml`) are MIT, pure Rust, build for wasm and - are the ones SolidCraft already uses. +## What ModelCraft's reader does -## The package - -As 3MF Core: a zip holding `[Content_Types].xml`, `_rels/.rels` (a relationship of type -`http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel` to the model part) and the model -part, conventionally `3D/3dmodel.model`. - -```xml - - - SolidCraft - - - - - - - ... - - - - ... - - - - - - - - -``` - -### Face tags - -| Attribute | On | Value | Meaning | -|---|---|---|---| -| `ft:faces` | `` | integer ≥ 1 | Number of B-rep faces of the body. Optional; when present every `ft:face` of the object is below it. Faces without triangles still count. | -| `ft:face` | `` | integer ≥ 0 | The B-rep face (0-based, the body's own face order) the triangle lies on. | - -An object's triangles are either **all tagged or none**. A body whose triangles carry no tags reads -as one face (tag 0). A file mixing tagged and untagged triangles in one object is rejected. - -### Writer rules (version 1) - -- `unit="millimeter"`, Z up. -- One `` with a `` per body, named after the body, one `` per - object, no `transform`, no components. -- Vertices shared within an object (coincident positions merged), triangles counter-clockwise - seen from outside, no degenerate triangles; coordinates in shortest round-trip decimal form. -- Colours, if written, use 3MF's own `basematerials` (`pid`/`p1`); they are independent of the tags. -- Deterministic bytes: fixed part order (`[Content_Types].xml`, `_rels/.rels`, the model), - fixed zip timestamps, no hash-ordered output. - -### Reader rules (ModelCraft) - -- Every build item becomes one `SurfaceMesh`, in millimetres (`unit` converted), named after its - object (`Mesh` when unnamed). An item's `transform` is applied; a mirroring transform reverses - the winding; a singular transform (one that flattens the object) is refused. Components are - not read yet (an error that says so). -- Untrusted input: file ≤ 512 MB, a part ≤ 1 GB uncompressed, ≤ 10 000 zip entries, ≤ 20 million - vertices and 20 million triangles in total, XML nesting ≤ 256. Coordinates must be finite with - magnitude ≤ 10⁹ mm (the FE model's limit), in the file and once placed; vertex indices must exist; tags must be below `ft:faces` when it is given. - Any violation is an error naming the problem; nothing panics. - -## Versions - -A later version gets a new namespace (`…/face-tags//`); readers accept the versions -they know and treat an unknown face-tag namespace as untagged. +- **Surfaces.** Every build item becomes one `TaggedSurface`: a `SurfaceMesh` with its name and + face count. Lengths are converted to millimetres from `unit`. The surface is named after its + object, or `Mesh` when the object is unnamed. +- **Transforms.** An item's `transform` is applied, and a mirroring transform reverses the + winding. A singular transform (one that flattens the object) is refused. Components are not read + yet; the reader returns an error that says so. +- **Tags.** + - Tags are matched by namespace URI, not by prefix. + - An untagged object becomes one face (tag 0), and an unknown face-tag namespace reads as + untagged. + - An object that mixes tagged and untagged triangles is rejected, as is any tag not below + `ft:faces`. +- **Limits.** All input is treated as untrusted: + - The file is at most 512 MB and must be a regular file, read through the virtual file system. + - Each part is at most 1 GB uncompressed, in at most 10 000 zip entries. + - At most 20 million vertices and 20 million triangles, counted over every placement. + - At most 10 000 build items, and XML nesting at most 256 deep. + - Coordinates must be finite and within ±10⁹ mm (`modelcraft_model::MAX_COORD`), both as read + and once placed. + - Every vertex index must exist. +- **Errors.** Any violation is an error that names the problem. Nothing panics.