3MF: export, and 3MF/STL import as mesh bodies

Export: ExportCommand/CLI --out/MCP export with .3mf write a 3MF Core package
([Content_Types].xml, _rels/.rels, 3D/3dmodel.model; millimetres), one
mesh object and build item per body, named after it, vertices shared and
welded watertight (T-junctions between faces split), base-material colours
when bodies carry one (STEP colours now ride on bodies). Tessellation as
for STL. File > Export 3MF.

Import: doc.open/drag-drop/CLI/MCP open accept .3mf and .stl; Insert Mesh
(ParaMeshInsertAlignCommand) adds them to a design as a MeshImport feature.
Kernel mesh bodies render, measure exactly, move and export; solid
features refuse them with a clear error. 3MF reading flattens components
and build transforms, converts units, reads colours; sizes, entries,
nesting and element counts are capped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-07 21:30:38 +00:00
1 parent 043a542e29
commit ea3dd0ed6e
30 files changed
+1361 -47

No files matched your search

+22 -1
View File
@@ -291,6 +291,25 @@ pub enum FeatureKind {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
components: Vec<solvecraft_kernel::StepNode>,
},
/// A non-parametric base feature: mesh bodies imported from a 3MF or STL file (kept in the
/// design). Mesh bodies render, measure, move and export; solid features cannot use them.
MeshImport {
/// Source file name (display only).
file: String,
meshes: Vec<MeshData>,
},
}
/// Triangles of one imported mesh body (millimetres).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MeshData {
pub name: String,
/// x, y, z per vertex.
pub positions: Vec<f64>,
/// Three vertex indices per triangle.
pub triangles: Vec<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<[f32; 3]>,
}
/// One loft section: profiles of a sketch.
@@ -391,6 +410,7 @@ impl FeatureKind {
FeatureKind::Split { .. } => "SplitBodyFeature",
FeatureKind::Move { .. } => "MoveFeature",
FeatureKind::Import { .. } => "BaseFeature",
FeatureKind::MeshImport { .. } => "MeshFeature",
}
}
/// Default name prefix (`Extrude` → `Extrude1`).
@@ -418,6 +438,7 @@ impl FeatureKind {
FeatureKind::Split { .. } => "Split",
FeatureKind::Move { .. } => "Move",
FeatureKind::Import { .. } => "Import",
FeatureKind::MeshImport { .. } => "Mesh",
}
}
/// Every expression the feature uses.
@@ -478,7 +499,7 @@ impl FeatureKind {
v.push(a);
}
}
FeatureKind::Import { .. } => {}
FeatureKind::Import { .. } | FeatureKind::MeshImport { .. } => {}
}
v
}