Face colours and opacity in STEP and 3MF export (and STEP import)

- Bodies carry a Paint for export (opacity, per-face colours), set by
  Document::painted from the appearance model. The paint comes from a
  face appearance, a body/component appearance's opacity or a material
  look (Glass), and falls back to the paint the body was imported with.
- STEP: faces with colours of their own get a STYLED_ITEM on their
  ADVANCED_FACE. See-through looks add SURFACE_STYLE_TRANSPARENT.
  Differently coloured pieces are not merged into one face.
- STEP import reads face styles and transparency. The reader tracks the
  file face of each kernel face (bands split in two keep it), so imported
  face colours land on the right faces; the design shows them through
  face_colors.
- 3MF: per-triangle colours (pid/p1 into the base materials, carried
  through welding) and the object's alpha (#RRGGBBAA).
- Tests: a blue half-transparent cube with a red top exports, re-imports
  with its opacity and the red top face, and keeps it when opened and
  exported again. 3MF writes the alpha and the face entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 14:02:10 +00:00
1 parent 8045904c54
commit 294597cc96
12 files changed
+313 -49

No files matched your search

+9 -1
View File
@@ -125,7 +125,15 @@ impl Document {
/// The faces of a body with appearances of their own: (B-rep face index, look). A later
/// assignment to the same face wins.
pub fn face_colors(&self, b: &crate::ModelBody) -> Vec<(usize, Look)> {
let mut out: Vec<(usize, Look)> = Vec::new();
// Face colours the body was imported with, under the design's own.
let mut out: Vec<(usize, Look)> = b
.body
.paint()
.map(|p| {
let to8 = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
p.faces.iter().map(|f| (f.face, Look::custom(f.color.map(to8), f.opacity as f64))).collect()
})
.unwrap_or_default();
let names = crate::naming::face_names(b);
for f in self.appearances.faces.iter().filter(|f| f.body == b.name) {
let (p, look) = (f.point, f.look.clone());
+12 -3
View File
@@ -1197,13 +1197,22 @@ impl Document {
self.body_look(&b.name, b.feature).map(|l| l.rgb()).or_else(|| b.body.color())
}
/// The model with each body carrying the colour it is shown in (for export).
/// The model with each body carrying the colour, opacity and face colours it is shown with
/// (for export).
pub fn painted(&self, state: &crate::ModelState) -> crate::ModelState {
let mut st = state.clone();
for b in &mut st.bodies {
let c = self.body_color(b);
if c != b.body.color() {
b.body = b.body.clone().with_color(c);
let look = self.body_look(&b.name, b.feature);
let faces: Vec<solvecraft_kernel::FacePaint> = self
.face_colors(b)
.into_iter()
.map(|(face, l)| solvecraft_kernel::FacePaint { face, color: l.rgb(), opacity: l.opacity as f32 })
.collect();
let opacity = look.map(|l| l.opacity as f32).or_else(|| b.body.paint().map(|p| p.opacity)).unwrap_or(1.0);
let paint = solvecraft_kernel::Paint { opacity, faces };
if c != b.body.color() || b.body.paint() != Some(&paint) {
b.body = b.body.clone().with_color(c).with_paint(Some(paint));
}
}
st