forked from 1b-agent/fieldcraft-upstream
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 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+46
-98
No files matched your search
@@ -1,8 +1,8 @@
|
|||||||
//! Face-tagged meshes in 3MF packages (`docs/formats/tagged-mesh.md`).
|
//! 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
|
//! 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
|
//! they lie on. SolidCraft defines the format and writes it (its `docs/formats/3mf-face-tags.md`);
|
||||||
//! untagged surfaces (one face each). Reading is plain parsing from bytes (no kernel call, no file
|
//! 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.
|
//! system), so it also runs in the browser.
|
||||||
//!
|
//!
|
||||||
//! Packages are untrusted: the file, its parts, zip entries, XML nesting, vertex and triangle
|
//! 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 MIN_RELATIVE_DET: f64 = 1e-12;
|
||||||
|
|
||||||
const CORE_NS: &[u8] = b"http://schemas.microsoft.com/3dmanufacturing/core/2015/02";
|
const CORE_NS: &[u8] = b"http://schemas.microsoft.com/3dmanufacturing/core/2015/02";
|
||||||
/// The face-tag namespace, version 1.
|
/// SolidCraft's face-tag namespace, version 1.
|
||||||
pub const FACE_TAGS_NS: &str = "https://github.com/jyviko/modelcraft/formats/face-tags/2026/10";
|
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.
|
/// One build item of a package: a named surface whose `tri_face` holds the face tags.
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
@@ -416,7 +416,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const HEAD: &str = r#"<model unit="millimeter" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:ft="https://github.com/jyviko/modelcraft/formats/face-tags/2026/10"><resources>"#;
|
const HEAD: &str = r#"<model unit="millimeter" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:ft="https://github.com/bherbruck/solidcraft/formats/3mf-face-tags/2026/10"><resources>"#;
|
||||||
|
|
||||||
/// A writer built from docs/formats/tagged-mesh.md alone (SolidCraft's is a separate
|
/// 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.
|
/// program): shortest round-trip coordinates, one object and build item per surface.
|
||||||
|
|||||||
+41
-93
@@ -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
|
## The format is SolidCraft's
|
||||||
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"`).
|
|
||||||
|
|
||||||
## 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
|
- The file is an ordinary 3MF Core 1.3 package.
|
||||||
Specification* 1.3, publicly available). The face tags are one attribute in a namespace of our
|
- Each `<triangle>` carries `ft:face` (its 0-based B-rep face index), and each `<object>` carries
|
||||||
own, which the Core specification allows on `<triangle>` and `<object>` and which any 3MF consumer
|
`ft:faces` (its face count).
|
||||||
ignores unless the namespace is listed in `requiredextensions` (it is not).
|
- 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
|
ModelCraft follows that specification and does not extend it. Because the format is 3MF,
|
||||||
tags):
|
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;
|
## What ModelCraft's reader does
|
||||||
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.
|
|
||||||
|
|
||||||
## The package
|
- **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
|
||||||
As 3MF Core: a zip holding `[Content_Types].xml`, `_rels/.rels` (a relationship of type
|
object, or `Mesh<n>` when the object is unnamed.
|
||||||
`http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel` to the model part) and the model
|
- **Transforms.** An item's `transform` is applied, and a mirroring transform reverses the
|
||||||
part, conventionally `3D/3dmodel.model`.
|
winding. A singular transform (one that flattens the object) is refused. Components are not read
|
||||||
|
yet; the reader returns an error that says so.
|
||||||
```xml
|
- **Tags.**
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
- Tags are matched by namespace URI, not by prefix.
|
||||||
<model unit="millimeter" xml:lang="en-US"
|
- An untagged object becomes one face (tag 0), and an unknown face-tag namespace reads as
|
||||||
xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02"
|
untagged.
|
||||||
xmlns:ft="https://github.com/jyviko/modelcraft/formats/face-tags/2026/10">
|
- An object that mixes tagged and untagged triangles is rejected, as is any tag not below
|
||||||
<metadata name="Application">SolidCraft</metadata>
|
`ft:faces`.
|
||||||
<resources>
|
- **Limits.** All input is treated as untrusted:
|
||||||
<object id="2" type="model" name="Body1" ft:faces="6">
|
- The file is at most 512 MB and must be a regular file, read through the virtual file system.
|
||||||
<mesh>
|
- Each part is at most 1 GB uncompressed, in at most 10 000 zip entries.
|
||||||
<vertices>
|
- At most 20 million vertices and 20 million triangles, counted over every placement.
|
||||||
<vertex x="0" y="0" z="0"/>
|
- At most 10 000 build items, and XML nesting at most 256 deep.
|
||||||
<vertex x="10" y="0" z="0"/>
|
- Coordinates must be finite and within ±10⁹ mm (`modelcraft_model::MAX_COORD`), both as read
|
||||||
...
|
and once placed.
|
||||||
</vertices>
|
- Every vertex index must exist.
|
||||||
<triangles>
|
- **Errors.** Any violation is an error that names the problem. Nothing panics.
|
||||||
<triangle v1="0" v2="2" v3="1" ft:face="0"/>
|
|
||||||
...
|
|
||||||
</triangles>
|
|
||||||
</mesh>
|
|
||||||
</object>
|
|
||||||
</resources>
|
|
||||||
<build>
|
|
||||||
<item objectid="2"/>
|
|
||||||
</build>
|
|
||||||
</model>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Face tags
|
|
||||||
|
|
||||||
| Attribute | On | Value | Meaning |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `ft:faces` | `<object>` | 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` | `<triangle>` | 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 `<object type="model">` with a `<mesh>` per body, named after the body, one `<item>` 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<n>` 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/<year>/<month>`); readers accept the versions
|
|
||||||
they know and treat an unknown face-tag namespace as untagged.
|
|
||||||
Reference in new issue
Block a user