//! IGES reading: the manifold solids (type 186) of a file, their shells,
//! faces, loops and edges (514/510/508/504/502), and its trimmed and bounded
//! surfaces (144/142 and 143/141), over spline surfaces and curves
//! (128/126), lines and arcs (110/100) and composite curves (102), as
//! kernel bodies.
use std::collections::HashMap;
use kernel::brep::{Circle3, Curve3, EdgeKey, Line3, Surface, VertexKey};
use kernel::space::{NurbsCurve3, NurbsSurface3, Plane};
use super::brep_build::{Builder, FaceSpec};
/// One parameter: a number, or a Hollerith string.
#[derive(Debug, Clone)]
enum P {
Num(f64),
Str(String),
}
impl P {
fn num(&self) -> f64 {
match self {
P::Num(v) => *v,
P::Str(_) => 0.0,
}
}
}
/// Splits a section's text into parameters at `delimiter`, up to the first
/// `terminator`, reading `nH…` strings whole.
fn parameters(text: &str, delimiter: char, terminator: char) -> Vec
{
let chars: Vec = text.chars().collect();
let mut out = Vec::new();
let mut i = 0;
loop {
while chars.get(i).is_some_and(|c| *c == ' ') {
i += 1;
}
let start = i;
while chars.get(i).is_some_and(|c| c.is_ascii_digit()) {
i += 1;
}
if i > start && matches!(chars.get(i), Some('H' | 'h')) {
let count: usize = chars[start..i].iter().collect::().parse().unwrap_or(0);
let body: String = chars.iter().skip(i + 1).take(count).collect();
i += 1 + count;
out.push(P::Str(body));
} else {
i = start;
while chars
.get(i)
.is_some_and(|c| *c != delimiter && *c != terminator)
{
i += 1;
}
let raw: String = chars[start..i].iter().collect::().replace(['D', 'd'], "E");
out.push(P::Num(raw.trim().parse().unwrap_or(0.0)));
}
while chars.get(i).is_some_and(|c| *c == ' ') {
i += 1;
}
match chars.get(i) {
Some(c) if *c == delimiter => i += 1,
_ => return out,
}
}
}
/// A directory entry and its parameters.
struct Entity {
kind: i32,
/// Pointer to a transformation matrix entity, or zero.
transform: usize,
/// Colour number, or a negated pointer to a colour definition.
color: i64,
params: Vec
,
}
struct Doc {
/// Keyed by the entry's first directory line number.
entities: HashMap,
/// File units to drawing units.
scale: f64,
/// Points closer than this are one point, in drawing units.
resolution: f64,
}
/// A 3×4 matrix applied as `R·p + T`.
type Matrix = [[f64; 4]; 3];
impl Doc {
fn get(&self, pointer: f64) -> Option<&Entity> {
self.entities.get(&(pointer.abs() as usize))
}
/// An entry's colour: a numbered one, or a definition's RGB percentages
/// — as the nearest standard index colour when it is one exactly.
fn color(&self, number: i64) -> codec::types::Color {
use codec::types::Color;
let rgb = match number {
1 => [0, 0, 0],
2 => [255, 0, 0],
3 => [0, 255, 0],
4 => [0, 0, 255],
5 => [255, 255, 0],
6 => [255, 0, 255],
7 => [0, 255, 255],
8 => [255, 255, 255],
n if n < 0 => match self.entities.get(&(-n as usize)).filter(|e| e.kind == 314) {
Some(e) => [1, 2, 3].map(|k| {
(e.params.get(k).map_or(0.0, P::num) * 2.55).round().clamp(0.0, 255.0) as u8
}),
None => return Color::ByLayer,
},
_ => return Color::ByLayer,
};
match rgb {
[255, 0, 0] => Color::Index(1),
[255, 255, 0] => Color::Index(2),
[0, 255, 0] => Color::Index(3),
[0, 255, 255] => Color::Index(4),
[0, 0, 255] => Color::Index(5),
[255, 0, 255] => Color::Index(6),
[255, 255, 255] => Color::Index(7),
[r, g, b] => Color::Rgb { r, g, b },
}
}
fn matrix(&self, pointer: usize) -> Option {
let entity = self.entities.get(&pointer).filter(|e| e.kind == 124)?;
let v = |k: usize| entity.params.get(k).map_or(0.0, P::num);
let mut m = [[v(1), v(2), v(3), v(4)], [v(5), v(6), v(7), v(8)], [v(9), v(10), v(11), v(12)]];
// Matrices chain through their own transformation pointer.
if let Some(outer) = (entity.transform != 0).then(|| self.matrix(entity.transform)).flatten() {
let mut product = [[0.0; 4]; 3];
for r in 0..3 {
for c in 0..4 {
product[r][c] = (0..3).map(|k| outer[r][k] * m[k][c]).sum::()
+ if c == 3 { outer[r][3] } else { 0.0 };
}
}
m = product;
}
Some(m)
}
/// `entity`'s placement applied to a point it defines.
fn place(&self, entity: &Entity) -> impl Fn([f64; 3]) -> [f64; 3] {
let m = (entity.transform != 0).then(|| self.matrix(entity.transform)).flatten();
let scale = self.scale;
move |p| {
let p = match m {
Some(m) => [0, 1, 2].map(|r| m[r][0] * p[0] + m[r][1] * p[1] + m[r][2] * p[2] + m[r][3]),
None => p,
};
p.map(|c| c * scale)
}
}
fn curve(&self, pointer: f64) -> Option {
let entity = self.get(pointer)?;
let p = &entity.params;
let v = |k: usize| p.get(k).map_or(0.0, P::num);
let place = self.place(entity);
Some(match entity.kind {
110 => {
let a = place([v(1), v(2), v(3)]);
let b = place([v(4), v(5), v(6)]);
Curve3::Line(Line3 {
origin: a,
direction: [b[0] - a[0], b[1] - a[1], b[2] - a[2]],
})
}
100 => {
let centre = place([v(2), v(3), v(1)]);
let x = place([v(2) + 1.0, v(3), v(1)]);
let y = place([v(2), v(3) + 1.0, v(1)]);
let start = place([v(4), v(5), v(1)]);
let radius = (0..3).map(|k| (start[k] - centre[k]).powi(2)).sum::().sqrt();
Curve3::Circle(Circle3 {
plane: Plane::orthonormal(
centre,
[x[0] - centre[0], x[1] - centre[1], x[2] - centre[2]],
{
let (a, b) = (
[x[0] - centre[0], x[1] - centre[1], x[2] - centre[2]],
[y[0] - centre[0], y[1] - centre[1], y[2] - centre[2]],
);
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
},
)?,
radius,
})
}
126 => {
let k = v(1) as usize;
let degree = v(2) as usize;
let knots = (0..k + degree + 2).map(|n| v(7 + n)).collect();
let w0 = 7 + k + degree + 2;
let weights = (0..=k).map(|n| v(w0 + n)).collect();
let c0 = w0 + k + 1;
let points = (0..=k)
.map(|n| place([v(c0 + 3 * n), v(c0 + 3 * n + 1), v(c0 + 3 * n + 2)]))
.collect();
Curve3::Nurbs(NurbsCurve3::new(degree, points, knots, Some(weights))?)
}
_ => return None,
})
}
/// A boundary curve as its pieces — each a curve and the points it runs
/// from and to — composite curves taken apart.
fn pieces(&self, pointer: f64) -> Option> {
let entity = self.get(pointer)?;
if entity.kind == 102 {
let count = entity.params.get(1).map_or(0.0, P::num) as usize;
let mut out = Vec::new();
for k in 0..count {
out.extend(self.pieces(entity.params.get(2 + k)?.num())?);
}
return Some(out);
}
let curve = self.curve(pointer)?;
let v = |k: usize| entity.params.get(k).map_or(0.0, P::num);
let place = self.place(entity);
let (from, to) = match (&curve, entity.kind) {
(_, 110) => (place([v(1), v(2), v(3)]), place([v(4), v(5), v(6)])),
(_, 100) => (place([v(4), v(5), v(1)]), place([v(6), v(7), v(1)])),
(Curve3::Nurbs(nurbs), _) => {
let (t0, t1) = nurbs.domain();
(nurbs.point_at_knot(t0), nurbs.point_at_knot(t1))
}
_ => return None,
};
Some(vec![(curve, from, to)])
}
/// The model-space pieces of a curve on a surface (142) or a boundary
/// (141), each of the latter's curves in the sense it is run.
fn boundary(&self, pointer: f64) -> Option> {
let entity = self.get(pointer)?;
let v = |k: usize| entity.params.get(k).map_or(0.0, P::num);
match entity.kind {
142 => self.pieces(v(4)),
141 => {
let mut out = Vec::new();
let mut at = 5;
for _ in 0..v(4) as usize {
let mut pieces = self.pieces(v(at))?;
if v(at + 1) == 2.0 {
pieces.reverse();
for piece in &mut pieces {
std::mem::swap(&mut piece.1, &mut piece.2);
}
}
out.extend(pieces);
at += 3 + v(at + 2) as usize;
}
Some(out)
}
_ => self.pieces(pointer),
}
}
fn surface(&self, pointer: f64) -> Option {
let entity = self.get(pointer)?;
if entity.kind != 128 {
return None;
}
let v = |k: usize| entity.params.get(k).map_or(0.0, P::num);
let place = self.place(entity);
let (k1, k2, m1, m2) = (v(1) as usize, v(2) as usize, v(3) as usize, v(4) as usize);
let s0 = 10;
let t0 = s0 + k1 + m1 + 2;
let w0 = t0 + k2 + m2 + 2;
let c0 = w0 + (k1 + 1) * (k2 + 1);
let u_knots = (s0..t0).map(v).collect();
let v_knots = (t0..w0).map(v).collect();
// Points run with the first index fastest.
let at = |i: usize, j: usize| j * (k1 + 1) + i;
let weights = (0..=k1)
.map(|i| (0..=k2).map(|j| v(w0 + at(i, j))).collect())
.collect();
let points = (0..=k1)
.map(|i| {
(0..=k2)
.map(|j| {
let n = c0 + 3 * at(i, j);
place([v(n), v(n + 1), v(n + 2)])
})
.collect()
})
.collect();
NurbsSurface3::new(m1, m2, points, u_knots, v_knots, Some(weights)).map(Surface::Nurbs)
}
}
struct Assembly<'a> {
doc: &'a Doc,
build: Builder,
vertices: HashMap<(usize, usize), VertexKey>,
edges: HashMap<(usize, usize), EdgeKey>,
/// Vertices made where boundary curves meet, by position.
corners: Vec<([f64; 3], VertexKey)>,
}
fn distance(a: [f64; 3], b: [f64; 3]) -> f64 {
(0..3).map(|k| (a[k] - b[k]).powi(2)).sum::().sqrt()
}
impl Assembly<'_> {
fn vertex(&mut self, list: f64, index: f64) -> Option {
let key = (list.abs() as usize, index as usize);
if let Some(&v) = self.vertices.get(&key) {
return Some(v);
}
let entity = self.doc.get(list).filter(|e| e.kind == 502)?;
let n = 2 + 3 * (key.1.checked_sub(1)?);
let p = |k: usize| entity.params.get(k).map(P::num);
let point = self.doc.place(entity)([p(n)?, p(n + 1)?, p(n + 2)?]);
let v = self.build.vertex(point);
self.vertices.insert(key, v);
Some(v)
}
fn edge(&mut self, list: f64, index: f64) -> Option {
let key = (list.abs() as usize, index as usize);
if let Some(&e) = self.edges.get(&key) {
return Some(e);
}
let entity = self.doc.get(list).filter(|e| e.kind == 504)?;
let n = 2 + 5 * key.1.checked_sub(1)?;
let p = |k: usize| entity.params.get(k).map(P::num);
let curve = self.doc.curve(p(n)?)?;
let start = self.vertex(p(n + 1)?, p(n + 2)?)?;
let end = self.vertex(p(n + 3)?, p(n + 4)?)?;
let e = self.build.edge(curve, start, end)?;
self.edges.insert(key, e);
Some(e)
}
/// The vertex at a point, shared with one already there.
fn corner(&mut self, point: [f64; 3]) -> VertexKey {
let tolerance = self.doc.resolution;
if let Some((_, v)) = self.corners.iter().find(|(p, _)| distance(*p, point) <= tolerance) {
return *v;
}
let v = self.build.vertex(point);
self.corners.push((point, v));
v
}
/// A loop along curve pieces, in their order.
fn curve_ring(&mut self, pieces: Vec<(Curve3, [f64; 3], [f64; 3])>) -> Option> {
let mut ring = Vec::new();
for (curve, from, to) in pieces {
if distance(from, to) <= self.doc.resolution && matches!(curve, Curve3::Line(_)) {
continue;
}
let (a, b) = (self.corner(from), self.corner(to));
ring.push((self.build.edge(curve, a, b)?, true));
}
(!ring.is_empty()).then_some(ring)
}
/// A loop round the edge of a spline surface's parameter domain; sides
/// that close up to a point (a pole) are left out.
fn domain_ring(&mut self, surface: &NurbsSurface3) -> Option> {
let ((u0, u1), (v0, v1)) = surface.domain();
let corners = [(u0, v0), (u1, v0), (u1, v1), (u0, v1)];
let sides = [(1, v0), (0, u1), (1, v1), (0, u0)];
let mut pieces = Vec::new();
for (k, (axis, at)) in sides.into_iter().enumerate() {
let (a, b) = (corners[k], corners[(k + 1) % 4]);
let (from, to) = (surface.point_at_knot(a.0, a.1), surface.point_at_knot(b.0, b.1));
let middle = surface.point_at_knot(0.5 * (a.0 + b.0), 0.5 * (a.1 + b.1));
if distance(from, middle) <= self.doc.resolution && distance(to, middle) <= self.doc.resolution {
continue;
}
pieces.push((Curve3::Nurbs(surface.isocurve(axis, at)?), from, to));
}
self.curve_ring(pieces)
}
/// Turns a loop to run counter-clockwise round the surface's normal (an
/// outer one) or clockwise (a hole), judged by the area it encloses in
/// the surface's parameters.
fn orient(&self, surface: &Surface, ring: &mut [(EdgeKey, bool)], outer: bool) {
let mut uv = Vec::new();
for (edge, forward) in ring.iter() {
let Some(edge) = self.build.body.edges.get(*edge) else { return };
let Some(curve) = self.build.body.curves.get(edge.curve) else { return };
let (t0, t1) = (edge.start_parameter, edge.end_parameter);
for k in 0..8 {
let s = k as f64 / 8.0;
let s = if *forward { s } else { 1.0 - s };
if let Some(p) = surface.parameters_at(curve.point_at(t0 + (t1 - t0) * s)) {
uv.push(p);
}
}
}
let area: f64 = (0..uv.len())
.map(|k| {
let (a, b) = (uv[k], uv[(k + 1) % uv.len()]);
a.0 * b.1 - b.0 * a.1
})
.sum();
if (area > 0.0) != outer {
ring.reverse();
for (_, forward) in ring.iter_mut() {
*forward = !*forward;
}
}
}
/// A trimmed (144) or bounded (143) surface as a one-face sheet.
fn sheet(&mut self, entity: &Entity) -> Option<()> {
let v = |k: usize| entity.params.get(k).map_or(0.0, P::num);
let (surface_at, outer, holes): (f64, Option, Vec) = match entity.kind {
144 => (
v(1),
(v(2) != 0.0).then(|| v(4)),
(0..v(3) as usize).map(|k| v(5 + k)).collect(),
),
143 => {
let count = v(3) as usize;
(v(2), (count > 0).then(|| v(4)), (1..count).map(|k| v(4 + k)).collect())
}
_ => return None,
};
let Surface::Nurbs(nurbs) = self.doc.surface(surface_at)? else { return None };
let surface = Surface::Nurbs(nurbs.clone());
let mut first = match outer {
Some(boundary) => {
let pieces = self.doc.boundary(boundary)?;
self.curve_ring(pieces)?
}
None => self.domain_ring(&nurbs)?,
};
self.orient(&surface, &mut first, true);
let mut loops = vec![first];
for hole in holes {
let pieces = self.doc.boundary(hole)?;
let mut ring = self.curve_ring(pieces)?;
self.orient(&surface, &mut ring, false);
loops.push(ring);
}
self.build.lump(vec![vec![FaceSpec {
surface,
forward: true,
loops,
}]])
}
fn ring(&mut self, pointer: f64) -> Option> {
let entity = self.doc.get(pointer).filter(|e| e.kind == 508)?;
let p = |k: usize| entity.params.get(k).map(P::num);
let mut ring = Vec::new();
let mut at = 2;
for _ in 0..p(1)? as usize {
let (kind, list, index, agrees, pcurves) = (p(at)?, p(at + 1)?, p(at + 2)?, p(at + 3)?, p(at + 4)?);
if kind == 0.0 {
ring.push((self.edge(list, index)?, agrees != 0.0));
}
at += 5 + 2 * pcurves as usize;
}
Some(ring)
}
fn face(&mut self, pointer: f64, agrees: bool) -> Option {
let entity = self.doc.get(pointer).filter(|e| e.kind == 510)?;
let p = |k: usize| entity.params.get(k).map(P::num);
let surface = self.doc.surface(p(1)?)?;
let loops = (0..p(2)? as usize)
.map(|n| self.ring(p(4 + n)?))
.collect::