Skip to main content

oxiphysics_io/cad_io/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#![allow(clippy::needless_range_loop)]
6#[allow(unused_imports)]
7use super::functions::*;
8/// Basic IGES file reader.
9///
10/// Parses the fixed-column IGES format to extract geometric entities.
11#[derive(Debug, Clone)]
12pub struct IgesReader {
13    /// Parsed entities.
14    pub entities: Vec<IgesEntity>,
15    /// Global section data.
16    pub global_params: Vec<String>,
17    /// Units identifier.
18    pub units: LengthUnit,
19}
20impl IgesReader {
21    /// Create a new IGES reader.
22    pub fn new() -> Self {
23        Self {
24            entities: Vec::new(),
25            global_params: Vec::new(),
26            units: LengthUnit::Millimeter,
27        }
28    }
29    /// Parse IGES content from a string.
30    ///
31    /// IGES files have a fixed 80-column format with section identifiers
32    /// in column 73.
33    #[allow(dead_code)]
34    pub fn parse(&mut self, content: &str) -> Result<(), String> {
35        self.entities.clear();
36        self.global_params.clear();
37        let lines: Vec<&str> = content.lines().collect();
38        let mut _start_lines = Vec::new();
39        let mut global_lines = Vec::new();
40        let mut directory_lines = Vec::new();
41        let mut parameter_lines = Vec::new();
42        for line in &lines {
43            if line.len() < 73 {
44                continue;
45            }
46            let section = line.chars().nth(72).unwrap_or(' ');
47            match section {
48                'S' => _start_lines.push(*line),
49                'G' => global_lines.push(*line),
50                'D' => directory_lines.push(*line),
51                'P' => parameter_lines.push(*line),
52                _ => {}
53            }
54        }
55        let global_text: String = global_lines
56            .iter()
57            .map(|l| &l[..72.min(l.len())])
58            .collect::<Vec<_>>()
59            .join("");
60        self.global_params = global_text
61            .split(',')
62            .map(|s| s.trim().to_string())
63            .collect();
64        let mut i = 0;
65        while i + 1 < directory_lines.len() {
66            let line1 = directory_lines[i];
67            let _line2 = directory_lines[i + 1];
68            let entity_type_str = line1[0..8.min(line1.len())].trim();
69            if let Ok(etype_num) = entity_type_str.parse::<u32>() {
70                let etype = IgesEntityType::from_type_number(etype_num);
71                let seq = i / 2 + 1;
72                let entity = IgesEntity::new(seq, etype);
73                self.entities.push(entity);
74            }
75            i += 2;
76        }
77        let param_text: String = parameter_lines
78            .iter()
79            .map(|l| &l[..64.min(l.len())])
80            .collect::<Vec<_>>()
81            .join("");
82        let records: Vec<&str> = param_text.split(';').collect();
83        for (idx, record) in records.iter().enumerate() {
84            if idx >= self.entities.len() {
85                break;
86            }
87            let values: Vec<f64> = record
88                .split(',')
89                .filter_map(|s| {
90                    let s = s.trim();
91                    if s.is_empty() {
92                        None
93                    } else {
94                        s.parse::<f64>().ok()
95                    }
96                })
97                .collect();
98            self.entities[idx].parameters = values;
99        }
100        Ok(())
101    }
102    /// Get all entities of a specific type.
103    #[allow(dead_code)]
104    pub fn find_entities(&self, etype: IgesEntityType) -> Vec<&IgesEntity> {
105        self.entities
106            .iter()
107            .filter(|e| e.entity_type == etype)
108            .collect()
109    }
110    /// Get the number of parsed entities.
111    #[allow(dead_code)]
112    pub fn entity_count(&self) -> usize {
113        self.entities.len()
114    }
115    /// Extract points (entity type 116).
116    #[allow(dead_code)]
117    pub fn extract_points(&self) -> Vec<[f64; 3]> {
118        let mut points = Vec::new();
119        for entity in self.find_entities(IgesEntityType::Point) {
120            if entity.parameters.len() >= 3 {
121                points.push([
122                    entity.parameters[0],
123                    entity.parameters[1],
124                    entity.parameters[2],
125                ]);
126            }
127        }
128        points
129    }
130    /// Extract lines (entity type 110).
131    #[allow(dead_code)]
132    pub fn extract_lines(&self) -> Vec<([f64; 3], [f64; 3])> {
133        let mut lines = Vec::new();
134        for entity in self.find_entities(IgesEntityType::Line) {
135            if entity.parameters.len() >= 6 {
136                let p1 = [
137                    entity.parameters[0],
138                    entity.parameters[1],
139                    entity.parameters[2],
140                ];
141                let p2 = [
142                    entity.parameters[3],
143                    entity.parameters[4],
144                    entity.parameters[5],
145                ];
146                lines.push((p1, p2));
147            }
148        }
149        lines
150    }
151    /// Extract circular arcs (entity type 100).
152    #[allow(dead_code)]
153    pub fn extract_circular_arcs(&self) -> Vec<IgesCircularArc> {
154        let mut arcs = Vec::new();
155        for entity in self.find_entities(IgesEntityType::CircularArc) {
156            if entity.parameters.len() >= 6 {
157                arcs.push(IgesCircularArc {
158                    z_displacement: entity.parameters[0],
159                    center: [entity.parameters[1], entity.parameters[2]],
160                    start: [entity.parameters[3], entity.parameters[4]],
161                    end: if entity.parameters.len() >= 7 {
162                        [entity.parameters[5], entity.parameters[6]]
163                    } else {
164                        [entity.parameters[3], entity.parameters[4]]
165                    },
166                });
167            }
168        }
169        arcs
170    }
171}
172/// Circular arc data from IGES entity type 100.
173#[derive(Debug, Clone)]
174pub struct IgesCircularArc {
175    /// Z-displacement of the arc plane.
176    pub z_displacement: f64,
177    /// Center point \[x, y\].
178    pub center: [f64; 2],
179    /// Start point \[x, y\].
180    pub start: [f64; 2],
181    /// End point \[x, y\].
182    pub end: [f64; 2],
183}
184impl IgesCircularArc {
185    /// Compute the radius.
186    #[allow(dead_code)]
187    pub fn radius(&self) -> f64 {
188        let dx = self.start[0] - self.center[0];
189        let dy = self.start[1] - self.center[1];
190        (dx * dx + dy * dy).sqrt()
191    }
192    /// Convert to 3D center point.
193    #[allow(dead_code)]
194    pub fn center_3d(&self) -> [f64; 3] {
195        [self.center[0], self.center[1], self.z_displacement]
196    }
197}
198/// A component in an assembly.
199#[derive(Debug, Clone)]
200pub struct AssemblyComponent {
201    /// Component name.
202    pub name: String,
203    /// BREP solid geometry.
204    pub solid: BrepSolid,
205    /// Transform relative to parent.
206    pub transform: AssemblyTransform,
207    /// Child components.
208    pub children: Vec<AssemblyComponent>,
209}
210impl AssemblyComponent {
211    /// Create a new component.
212    pub fn new(name: &str, solid: BrepSolid, transform: AssemblyTransform) -> Self {
213        Self {
214            name: name.to_string(),
215            solid,
216            transform,
217            children: Vec::new(),
218        }
219    }
220    /// Add a child component.
221    #[allow(dead_code)]
222    pub fn add_child(&mut self, child: AssemblyComponent) {
223        self.children.push(child);
224    }
225    /// Count total components (including self).
226    #[allow(dead_code)]
227    pub fn total_components(&self) -> usize {
228        1 + self
229            .children
230            .iter()
231            .map(|c| c.total_components())
232            .sum::<usize>()
233    }
234    /// Compute the bounding box in world space.
235    #[allow(dead_code)]
236    pub fn world_bounding_box(&self) -> BoundingBox {
237        let mut bb = BoundingBox::empty();
238        for v in &self.solid.vertices {
239            let world_pos = self.transform.apply(v.position);
240            bb.include_point(world_pos);
241        }
242        for child in &self.children {
243            let child_bb = child.world_bounding_box();
244            bb.include_box(&child_bb);
245        }
246        bb
247    }
248    /// Collect all leaf component names.
249    #[allow(dead_code)]
250    pub fn leaf_names(&self) -> Vec<String> {
251        if self.children.is_empty() {
252            vec![self.name.clone()]
253        } else {
254            self.children.iter().flat_map(|c| c.leaf_names()).collect()
255        }
256    }
257}
258/// Assembly structure holding the root component.
259#[derive(Debug, Clone)]
260pub struct Assembly {
261    /// Assembly name.
262    pub name: String,
263    /// Root component.
264    pub root: AssemblyComponent,
265    /// Units used in the assembly.
266    pub units: LengthUnit,
267}
268impl Assembly {
269    /// Create a new assembly.
270    pub fn new(name: &str, root: AssemblyComponent, units: LengthUnit) -> Self {
271        Self {
272            name: name.to_string(),
273            root,
274            units,
275        }
276    }
277    /// Get the total number of components.
278    #[allow(dead_code)]
279    pub fn total_components(&self) -> usize {
280        self.root.total_components()
281    }
282    /// Compute the assembly bounding box.
283    #[allow(dead_code)]
284    pub fn bounding_box(&self) -> BoundingBox {
285        self.root.world_bounding_box()
286    }
287    /// Export the assembly to STL by tessellating all components.
288    #[allow(dead_code)]
289    pub fn to_stl(&self) -> String {
290        let exporter = StlExporter::new(&self.name);
291        let mut mesh = TriangleMesh::new();
292        self.collect_meshes(&self.root, &AssemblyTransform::identity(), &mut mesh);
293        exporter.to_ascii_stl(&mesh)
294    }
295    /// Recursively collect meshes from all components.
296    #[allow(dead_code)]
297    fn collect_meshes(
298        &self,
299        component: &AssemblyComponent,
300        parent_transform: &AssemblyTransform,
301        mesh: &mut TriangleMesh,
302    ) {
303        let world_transform = parent_transform.compose(&component.transform);
304        let component_mesh = tessellate_brep(&component.solid, 10);
305        let offset = mesh.vertices.len();
306        for v in &component_mesh.vertices {
307            mesh.vertices.push(world_transform.apply(*v));
308        }
309        for tri in &component_mesh.triangles {
310            mesh.triangles
311                .push([tri[0] + offset, tri[1] + offset, tri[2] + offset]);
312        }
313        for child in &component.children {
314            self.collect_meshes(child, &world_transform, mesh);
315        }
316    }
317}
318/// Basic STEP file parser for extracting entities.
319///
320/// Parses ISO 10303-21 (STEP Part 21) format files to extract entity
321/// definitions. Does not perform full schema validation.
322#[derive(Debug, Clone)]
323pub struct StepParser {
324    /// Parsed entities.
325    pub entities: Vec<StepEntity>,
326    /// File description.
327    pub description: String,
328    /// Schema identifier.
329    pub schema: String,
330}
331impl StepParser {
332    /// Create a new empty STEP parser.
333    pub fn new() -> Self {
334        Self {
335            entities: Vec::new(),
336            description: String::new(),
337            schema: String::new(),
338        }
339    }
340    /// Parse STEP content from a string.
341    #[allow(dead_code)]
342    pub fn parse(&mut self, content: &str) -> Result<(), String> {
343        self.entities.clear();
344        self.description.clear();
345        self.schema.clear();
346        let mut in_data = false;
347        for line in content.lines() {
348            let trimmed = line.trim();
349            if trimmed == "DATA;" {
350                in_data = true;
351                continue;
352            }
353            if trimmed == "ENDSEC;" {
354                if in_data {
355                    in_data = false;
356                }
357                continue;
358            }
359            if trimmed.starts_with("FILE_DESCRIPTION") {
360                self.description = trimmed.to_string();
361                continue;
362            }
363            if trimmed.starts_with("FILE_SCHEMA") {
364                self.schema = trimmed.to_string();
365                continue;
366            }
367            if in_data
368                && trimmed.starts_with('#')
369                && let Some(entity) = self.parse_entity_line(trimmed)
370            {
371                self.entities.push(entity);
372            }
373        }
374        Ok(())
375    }
376    /// Parse a single entity line like "#123=CARTESIAN_POINT('name',(1.0,2.0,3.0));".
377    #[allow(dead_code)]
378    fn parse_entity_line(&self, line: &str) -> Option<StepEntity> {
379        let line = line.trim_end_matches(';');
380        let eq_pos = line.find('=')?;
381        let id_str = &line[1..eq_pos];
382        let id: usize = id_str.parse().ok()?;
383        let rest = &line[eq_pos + 1..];
384        let paren_pos = rest.find('(');
385        let (entity_type, params) = if let Some(pos) = paren_pos {
386            let etype = rest[..pos].trim();
387            let params = &rest[pos..];
388            (etype, params)
389        } else {
390            (rest.trim(), "")
391        };
392        Some(StepEntity::new(id, entity_type, params))
393    }
394    /// Find all entities of a given type.
395    #[allow(dead_code)]
396    pub fn find_entities(&self, entity_type: &str) -> Vec<&StepEntity> {
397        self.entities
398            .iter()
399            .filter(|e| e.entity_type == entity_type)
400            .collect()
401    }
402    /// Find an entity by ID.
403    #[allow(dead_code)]
404    pub fn find_by_id(&self, id: usize) -> Option<&StepEntity> {
405        self.entities.iter().find(|e| e.id == id)
406    }
407    /// Get the total number of parsed entities.
408    #[allow(dead_code)]
409    pub fn entity_count(&self) -> usize {
410        self.entities.len()
411    }
412    /// Extract Cartesian points from parsed STEP entities.
413    #[allow(dead_code)]
414    pub fn extract_cartesian_points(&self) -> Vec<(usize, [f64; 3])> {
415        let mut points = Vec::new();
416        for entity in self.find_entities("CARTESIAN_POINT") {
417            if let Some(coords) = self.parse_point_coordinates(&entity.parameters) {
418                points.push((entity.id, coords));
419            }
420        }
421        points
422    }
423    /// Parse point coordinates from a STEP parameter string.
424    #[allow(dead_code)]
425    fn parse_point_coordinates(&self, params: &str) -> Option<[f64; 3]> {
426        let inner_start = params.rfind('(')?;
427        let inner_end = params[inner_start..].find(')')? + inner_start;
428        let coord_str = &params[inner_start + 1..inner_end];
429        let coords: Vec<f64> = coord_str
430            .split(',')
431            .filter_map(|s| s.trim().parse::<f64>().ok())
432            .collect();
433        if coords.len() >= 3 {
434            Some([coords[0], coords[1], coords[2]])
435        } else if coords.len() == 2 {
436            Some([coords[0], coords[1], 0.0])
437        } else {
438            None
439        }
440    }
441    /// Extract direction entities.
442    #[allow(dead_code)]
443    pub fn extract_directions(&self) -> Vec<(usize, [f64; 3])> {
444        let mut dirs = Vec::new();
445        for entity in self.find_entities("DIRECTION") {
446            if let Some(dir) = self.parse_point_coordinates(&entity.parameters) {
447                dirs.push((entity.id, dir));
448            }
449        }
450        dirs
451    }
452}
453/// Axis-aligned bounding box (AABB) for 3D geometry.
454#[derive(Debug, Clone, Copy)]
455pub struct BoundingBox {
456    /// Minimum corner.
457    pub min: [f64; 3],
458    /// Maximum corner.
459    pub max: [f64; 3],
460}
461impl BoundingBox {
462    /// Create an empty (invalid) bounding box.
463    pub fn empty() -> Self {
464        Self {
465            min: [f64::INFINITY; 3],
466            max: [f64::NEG_INFINITY; 3],
467        }
468    }
469    /// Create a bounding box from min and max corners.
470    pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
471        Self { min, max }
472    }
473    /// Expand the bounding box to include a point.
474    #[allow(dead_code)]
475    pub fn include_point(&mut self, p: [f64; 3]) {
476        for i in 0..3 {
477            self.min[i] = self.min[i].min(p[i]);
478            self.max[i] = self.max[i].max(p[i]);
479        }
480    }
481    /// Expand to include another bounding box.
482    #[allow(dead_code)]
483    pub fn include_box(&mut self, other: &BoundingBox) {
484        for i in 0..3 {
485            self.min[i] = self.min[i].min(other.min[i]);
486            self.max[i] = self.max[i].max(other.max[i]);
487        }
488    }
489    /// Compute from a set of points.
490    #[allow(dead_code)]
491    pub fn from_points(points: &[[f64; 3]]) -> Self {
492        let mut bb = Self::empty();
493        for p in points {
494            bb.include_point(*p);
495        }
496        bb
497    }
498    /// Get the center of the bounding box.
499    #[allow(dead_code)]
500    pub fn center(&self) -> [f64; 3] {
501        [
502            (self.min[0] + self.max[0]) * 0.5,
503            (self.min[1] + self.max[1]) * 0.5,
504            (self.min[2] + self.max[2]) * 0.5,
505        ]
506    }
507    /// Get the size (extent) of the bounding box.
508    #[allow(dead_code)]
509    pub fn size(&self) -> [f64; 3] {
510        [
511            self.max[0] - self.min[0],
512            self.max[1] - self.min[1],
513            self.max[2] - self.min[2],
514        ]
515    }
516    /// Get the diagonal length.
517    #[allow(dead_code)]
518    pub fn diagonal(&self) -> f64 {
519        let s = self.size();
520        (s[0] * s[0] + s[1] * s[1] + s[2] * s[2]).sqrt()
521    }
522    /// Get the volume.
523    #[allow(dead_code)]
524    pub fn volume(&self) -> f64 {
525        let s = self.size();
526        s[0] * s[1] * s[2]
527    }
528    /// Get the surface area.
529    #[allow(dead_code)]
530    pub fn surface_area(&self) -> f64 {
531        let s = self.size();
532        2.0 * (s[0] * s[1] + s[1] * s[2] + s[2] * s[0])
533    }
534    /// Check if a point is inside the bounding box.
535    #[allow(dead_code)]
536    pub fn contains_point(&self, p: [f64; 3]) -> bool {
537        p[0] >= self.min[0]
538            && p[0] <= self.max[0]
539            && p[1] >= self.min[1]
540            && p[1] <= self.max[1]
541            && p[2] >= self.min[2]
542            && p[2] <= self.max[2]
543    }
544    /// Check if this box intersects another.
545    #[allow(dead_code)]
546    pub fn intersects(&self, other: &BoundingBox) -> bool {
547        self.min[0] <= other.max[0]
548            && self.max[0] >= other.min[0]
549            && self.min[1] <= other.max[1]
550            && self.max[1] >= other.min[1]
551            && self.min[2] <= other.max[2]
552            && self.max[2] >= other.min[2]
553    }
554    /// Check if the bounding box is valid (non-empty).
555    #[allow(dead_code)]
556    pub fn is_valid(&self) -> bool {
557        self.min[0] <= self.max[0] && self.min[1] <= self.max[1] && self.min[2] <= self.max[2]
558    }
559    /// Apply a unit conversion to the bounding box.
560    #[allow(dead_code)]
561    pub fn convert_units(&self, converter: &UnitConverter) -> BoundingBox {
562        BoundingBox {
563            min: converter.convert_point(self.min),
564            max: converter.convert_point(self.max),
565        }
566    }
567}
568/// A vertex in the BREP representation.
569#[derive(Debug, Clone)]
570pub struct BrepVertex {
571    /// Unique identifier.
572    pub id: usize,
573    /// 3D position.
574    pub position: [f64; 3],
575    /// Tolerance for vertex coincidence.
576    pub tolerance: f64,
577}
578impl BrepVertex {
579    /// Create a new vertex.
580    pub fn new(id: usize, position: [f64; 3]) -> Self {
581        Self {
582            id,
583            position,
584            tolerance: 1e-6,
585        }
586    }
587}
588/// An edge in the BREP representation.
589#[derive(Debug, Clone)]
590pub struct BrepEdge {
591    /// Unique identifier.
592    pub id: usize,
593    /// Start vertex index.
594    pub start_vertex: usize,
595    /// End vertex index.
596    pub end_vertex: usize,
597    /// Curve type.
598    pub curve_type: CurveType,
599    /// Parameter range \[t_start, t_end\].
600    pub parameter_range: [f64; 2],
601    /// Intermediate control/sample points.
602    pub control_points: Vec<[f64; 3]>,
603}
604impl BrepEdge {
605    /// Create a new linear edge.
606    pub fn line(id: usize, start: usize, end: usize) -> Self {
607        Self {
608            id,
609            start_vertex: start,
610            end_vertex: end,
611            curve_type: CurveType::Line,
612            parameter_range: [0.0, 1.0],
613            control_points: Vec::new(),
614        }
615    }
616    /// Create a new arc edge.
617    pub fn arc(id: usize, start: usize, end: usize, center: [f64; 3]) -> Self {
618        Self {
619            id,
620            start_vertex: start,
621            end_vertex: end,
622            curve_type: CurveType::CircularArc,
623            parameter_range: [0.0, 1.0],
624            control_points: vec![center],
625        }
626    }
627    /// Evaluate a point on the edge at parameter t in \[0,1\].
628    #[allow(dead_code)]
629    pub fn evaluate(&self, t: f64, vertices: &[BrepVertex]) -> [f64; 3] {
630        let p0 = vertices[self.start_vertex].position;
631        let p1 = vertices[self.end_vertex].position;
632        match self.curve_type {
633            CurveType::Line => lerp3(p0, p1, t),
634            CurveType::CircularArc => {
635                if let Some(center) = self.control_points.first() {
636                    let r0 = sub3(p0, *center);
637                    let r1 = sub3(p1, *center);
638                    let angle = t * std::f64::consts::PI * 0.5;
639                    let cos_a = angle.cos();
640                    let sin_a = angle.sin();
641                    let r = add3(scale3(r0, cos_a), scale3(r1, sin_a));
642                    let radius = (len3(r0) + len3(r1)) * 0.5;
643                    let r_len = len3(r);
644                    if r_len < 1e-15 {
645                        *center
646                    } else {
647                        add3(*center, scale3(r, radius / r_len))
648                    }
649                } else {
650                    lerp3(p0, p1, t)
651                }
652            }
653            CurveType::BSpline | CurveType::Nurbs => lerp3(p0, p1, t),
654        }
655    }
656    /// Compute the approximate length of the edge.
657    #[allow(dead_code)]
658    pub fn approximate_length(&self, vertices: &[BrepVertex]) -> f64 {
659        let n = 20;
660        let mut length = 0.0;
661        let mut prev = self.evaluate(0.0, vertices);
662        for i in 1..=n {
663            let t = i as f64 / n as f64;
664            let curr = self.evaluate(t, vertices);
665            length += len3(sub3(curr, prev));
666            prev = curr;
667        }
668        length
669    }
670}
671/// Surface types in BREP.
672#[derive(Debug, Clone, Copy, PartialEq)]
673pub enum SurfaceType {
674    /// Planar surface.
675    Plane,
676    /// Cylindrical surface.
677    Cylinder,
678    /// Spherical surface.
679    Sphere,
680    /// Conical surface.
681    Cone,
682    /// Toroidal surface.
683    Torus,
684    /// B-spline surface.
685    BSpline,
686}
687/// Complete BREP solid.
688#[derive(Debug, Clone)]
689pub struct BrepSolid {
690    /// Name of the solid.
691    pub name: String,
692    /// Vertices.
693    pub vertices: Vec<BrepVertex>,
694    /// Edges.
695    pub edges: Vec<BrepEdge>,
696    /// Faces.
697    pub faces: Vec<BrepFace>,
698}
699impl BrepSolid {
700    /// Create a new empty BREP solid.
701    pub fn new(name: &str) -> Self {
702        Self {
703            name: name.to_string(),
704            vertices: Vec::new(),
705            edges: Vec::new(),
706            faces: Vec::new(),
707        }
708    }
709    /// Add a vertex and return its index.
710    #[allow(dead_code)]
711    pub fn add_vertex(&mut self, position: [f64; 3]) -> usize {
712        let id = self.vertices.len();
713        self.vertices.push(BrepVertex::new(id, position));
714        id
715    }
716    /// Add a line edge and return its index.
717    #[allow(dead_code)]
718    pub fn add_line_edge(&mut self, start: usize, end: usize) -> usize {
719        let id = self.edges.len();
720        self.edges.push(BrepEdge::line(id, start, end));
721        id
722    }
723    /// Add a planar face and return its index.
724    #[allow(dead_code)]
725    pub fn add_planar_face(
726        &mut self,
727        edge_loop: Vec<usize>,
728        normal: [f64; 3],
729        origin: [f64; 3],
730    ) -> usize {
731        let id = self.faces.len();
732        self.faces
733            .push(BrepFace::planar(id, edge_loop, normal, origin));
734        id
735    }
736    /// Compute the bounding box of the solid.
737    #[allow(dead_code)]
738    pub fn bounding_box(&self) -> BoundingBox {
739        BoundingBox::from_points(&self.vertices.iter().map(|v| v.position).collect::<Vec<_>>())
740    }
741    /// Validate the topology: check that all edge vertex references are valid.
742    #[allow(dead_code)]
743    pub fn validate(&self) -> Result<(), String> {
744        let n_verts = self.vertices.len();
745        let n_edges = self.edges.len();
746        for edge in &self.edges {
747            if edge.start_vertex >= n_verts {
748                return Err(format!(
749                    "Edge {} has invalid start vertex {}",
750                    edge.id, edge.start_vertex
751                ));
752            }
753            if edge.end_vertex >= n_verts {
754                return Err(format!(
755                    "Edge {} has invalid end vertex {}",
756                    edge.id, edge.end_vertex
757                ));
758            }
759        }
760        for face in &self.faces {
761            for loop_edges in &face.edge_loops {
762                for &eidx in loop_edges {
763                    if eidx >= n_edges {
764                        return Err(format!("Face {} references invalid edge {}", face.id, eidx));
765                    }
766                }
767            }
768        }
769        Ok(())
770    }
771    /// Get Euler characteristic: V - E + F.
772    #[allow(dead_code)]
773    pub fn euler_characteristic(&self) -> i64 {
774        self.vertices.len() as i64 - self.edges.len() as i64 + self.faces.len() as i64
775    }
776    /// Create a box (cuboid) BREP solid.
777    #[allow(dead_code)]
778    pub fn create_box(name: &str, sx: f64, sy: f64, sz: f64) -> Self {
779        let mut solid = Self::new(name);
780        let v0 = solid.add_vertex([0.0, 0.0, 0.0]);
781        let v1 = solid.add_vertex([sx, 0.0, 0.0]);
782        let v2 = solid.add_vertex([sx, sy, 0.0]);
783        let v3 = solid.add_vertex([0.0, sy, 0.0]);
784        let v4 = solid.add_vertex([0.0, 0.0, sz]);
785        let v5 = solid.add_vertex([sx, 0.0, sz]);
786        let v6 = solid.add_vertex([sx, sy, sz]);
787        let v7 = solid.add_vertex([0.0, sy, sz]);
788        let e0 = solid.add_line_edge(v0, v1);
789        let e1 = solid.add_line_edge(v1, v2);
790        let e2 = solid.add_line_edge(v2, v3);
791        let e3 = solid.add_line_edge(v3, v0);
792        let e4 = solid.add_line_edge(v4, v5);
793        let e5 = solid.add_line_edge(v5, v6);
794        let e6 = solid.add_line_edge(v6, v7);
795        let e7 = solid.add_line_edge(v7, v4);
796        let e8 = solid.add_line_edge(v0, v4);
797        let e9 = solid.add_line_edge(v1, v5);
798        let e10 = solid.add_line_edge(v2, v6);
799        let e11 = solid.add_line_edge(v3, v7);
800        solid.add_planar_face(vec![e0, e1, e2, e3], [0.0, 0.0, -1.0], [0.0, 0.0, 0.0]);
801        solid.add_planar_face(vec![e4, e5, e6, e7], [0.0, 0.0, 1.0], [0.0, 0.0, sz]);
802        solid.add_planar_face(vec![e0, e9, e4, e8], [0.0, -1.0, 0.0], [0.0, 0.0, 0.0]);
803        solid.add_planar_face(vec![e2, e11, e6, e10], [0.0, 1.0, 0.0], [0.0, sy, 0.0]);
804        solid.add_planar_face(vec![e3, e8, e7, e11], [-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
805        solid.add_planar_face(vec![e1, e10, e5, e9], [1.0, 0.0, 0.0], [sx, 0.0, 0.0]);
806        solid
807    }
808}
809/// A face in the BREP representation.
810#[derive(Debug, Clone)]
811pub struct BrepFace {
812    /// Unique identifier.
813    pub id: usize,
814    /// Edge loops bounding this face (outer loop first, then holes).
815    pub edge_loops: Vec<Vec<usize>>,
816    /// Surface type.
817    pub surface_type: SurfaceType,
818    /// Surface normal (for planar faces).
819    pub normal: [f64; 3],
820    /// Surface origin (for parametric surfaces).
821    pub origin: [f64; 3],
822    /// Surface parameters (type-dependent).
823    pub params: Vec<f64>,
824}
825impl BrepFace {
826    /// Create a new planar face.
827    pub fn planar(id: usize, edge_loop: Vec<usize>, normal: [f64; 3], origin: [f64; 3]) -> Self {
828        Self {
829            id,
830            edge_loops: vec![edge_loop],
831            surface_type: SurfaceType::Plane,
832            normal,
833            origin,
834            params: Vec::new(),
835        }
836    }
837    /// Create a cylindrical face.
838    pub fn cylindrical(
839        id: usize,
840        edge_loop: Vec<usize>,
841        axis: [f64; 3],
842        origin: [f64; 3],
843        radius: f64,
844    ) -> Self {
845        Self {
846            id,
847            edge_loops: vec![edge_loop],
848            surface_type: SurfaceType::Cylinder,
849            normal: axis,
850            origin,
851            params: vec![radius],
852        }
853    }
854    /// Create a spherical face.
855    pub fn spherical(id: usize, edge_loop: Vec<usize>, center: [f64; 3], radius: f64) -> Self {
856        Self {
857            id,
858            edge_loops: vec![edge_loop],
859            surface_type: SurfaceType::Sphere,
860            normal: [0.0, 0.0, 1.0],
861            origin: center,
862            params: vec![radius],
863        }
864    }
865    /// Get the outer edge loop.
866    #[allow(dead_code)]
867    pub fn outer_loop(&self) -> &[usize] {
868        &self.edge_loops[0]
869    }
870    /// Get hole loops (if any).
871    #[allow(dead_code)]
872    pub fn hole_loops(&self) -> &[Vec<usize>] {
873        if self.edge_loops.len() > 1 {
874            &self.edge_loops[1..]
875        } else {
876            &[]
877        }
878    }
879}
880/// IGES entity types (100-199 range: geometry).
881#[derive(Debug, Clone, Copy, PartialEq)]
882pub enum IgesEntityType {
883    /// Type 100: Circular Arc.
884    CircularArc,
885    /// Type 102: Composite Curve.
886    CompositeCurve,
887    /// Type 104: Conic Arc.
888    ConicArc,
889    /// Type 106: Copious Data.
890    CopiousData,
891    /// Type 108: Plane.
892    Plane,
893    /// Type 110: Line.
894    Line,
895    /// Type 112: Parametric Spline Curve.
896    ParametricSpline,
897    /// Type 114: Parametric Spline Surface.
898    ParametricSplineSurface,
899    /// Type 116: Point.
900    Point,
901    /// Type 118: Ruled Surface.
902    RuledSurface,
903    /// Type 120: Surface of Revolution.
904    SurfaceOfRevolution,
905    /// Type 122: Tabulated Cylinder.
906    TabulatedCylinder,
907    /// Type 124: Transformation Matrix.
908    TransformationMatrix,
909    /// Type 126: Rational B-Spline Curve.
910    RationalBSplineCurve,
911    /// Type 128: Rational B-Spline Surface.
912    RationalBSplineSurface,
913    /// Type 130: Offset Curve.
914    OffsetCurve,
915    /// Type 140: Offset Surface.
916    OffsetSurface,
917    /// Type 142: Curve on Parametric Surface.
918    CurveOnSurface,
919    /// Type 144: Trimmed Parametric Surface.
920    TrimmedSurface,
921    /// Unknown entity type.
922    Unknown(u32),
923}
924impl IgesEntityType {
925    /// Convert from entity type number.
926    #[allow(dead_code)]
927    pub fn from_type_number(num: u32) -> Self {
928        match num {
929            100 => IgesEntityType::CircularArc,
930            102 => IgesEntityType::CompositeCurve,
931            104 => IgesEntityType::ConicArc,
932            106 => IgesEntityType::CopiousData,
933            108 => IgesEntityType::Plane,
934            110 => IgesEntityType::Line,
935            112 => IgesEntityType::ParametricSpline,
936            114 => IgesEntityType::ParametricSplineSurface,
937            116 => IgesEntityType::Point,
938            118 => IgesEntityType::RuledSurface,
939            120 => IgesEntityType::SurfaceOfRevolution,
940            122 => IgesEntityType::TabulatedCylinder,
941            124 => IgesEntityType::TransformationMatrix,
942            126 => IgesEntityType::RationalBSplineCurve,
943            128 => IgesEntityType::RationalBSplineSurface,
944            130 => IgesEntityType::OffsetCurve,
945            140 => IgesEntityType::OffsetSurface,
946            142 => IgesEntityType::CurveOnSurface,
947            144 => IgesEntityType::TrimmedSurface,
948            _ => IgesEntityType::Unknown(num),
949        }
950    }
951    /// Convert to entity type number.
952    #[allow(dead_code)]
953    pub fn to_type_number(&self) -> u32 {
954        match self {
955            IgesEntityType::CircularArc => 100,
956            IgesEntityType::CompositeCurve => 102,
957            IgesEntityType::ConicArc => 104,
958            IgesEntityType::CopiousData => 106,
959            IgesEntityType::Plane => 108,
960            IgesEntityType::Line => 110,
961            IgesEntityType::ParametricSpline => 112,
962            IgesEntityType::ParametricSplineSurface => 114,
963            IgesEntityType::Point => 116,
964            IgesEntityType::RuledSurface => 118,
965            IgesEntityType::SurfaceOfRevolution => 120,
966            IgesEntityType::TabulatedCylinder => 122,
967            IgesEntityType::TransformationMatrix => 124,
968            IgesEntityType::RationalBSplineCurve => 126,
969            IgesEntityType::RationalBSplineSurface => 128,
970            IgesEntityType::OffsetCurve => 130,
971            IgesEntityType::OffsetSurface => 140,
972            IgesEntityType::CurveOnSurface => 142,
973            IgesEntityType::TrimmedSurface => 144,
974            IgesEntityType::Unknown(n) => *n,
975        }
976    }
977    /// Check if this is a curve entity.
978    #[allow(dead_code)]
979    pub fn is_curve(&self) -> bool {
980        matches!(
981            self,
982            IgesEntityType::CircularArc
983                | IgesEntityType::CompositeCurve
984                | IgesEntityType::ConicArc
985                | IgesEntityType::Line
986                | IgesEntityType::ParametricSpline
987                | IgesEntityType::RationalBSplineCurve
988                | IgesEntityType::OffsetCurve
989        )
990    }
991    /// Check if this is a surface entity.
992    #[allow(dead_code)]
993    pub fn is_surface(&self) -> bool {
994        matches!(
995            self,
996            IgesEntityType::Plane
997                | IgesEntityType::RuledSurface
998                | IgesEntityType::SurfaceOfRevolution
999                | IgesEntityType::TabulatedCylinder
1000                | IgesEntityType::RationalBSplineSurface
1001                | IgesEntityType::ParametricSplineSurface
1002                | IgesEntityType::OffsetSurface
1003                | IgesEntityType::TrimmedSurface
1004        )
1005    }
1006}
1007/// Length units supported by CAD files.
1008#[derive(Debug, Clone, Copy, PartialEq)]
1009pub enum LengthUnit {
1010    /// Meters (SI base unit).
1011    Meter,
1012    /// Millimeters.
1013    Millimeter,
1014    /// Centimeters.
1015    Centimeter,
1016    /// Inches.
1017    Inch,
1018    /// Feet.
1019    Foot,
1020    /// Micrometers.
1021    Micrometer,
1022}
1023impl LengthUnit {
1024    /// Conversion factor to meters.
1025    #[allow(dead_code)]
1026    pub fn to_meters_factor(&self) -> f64 {
1027        match self {
1028            LengthUnit::Meter => 1.0,
1029            LengthUnit::Millimeter => 0.001,
1030            LengthUnit::Centimeter => 0.01,
1031            LengthUnit::Inch => 0.0254,
1032            LengthUnit::Foot => 0.3048,
1033            LengthUnit::Micrometer => 1e-6,
1034        }
1035    }
1036    /// Convert a length from this unit to another unit.
1037    #[allow(dead_code)]
1038    pub fn convert(&self, value: f64, to: LengthUnit) -> f64 {
1039        value * self.to_meters_factor() / to.to_meters_factor()
1040    }
1041    /// Convert a 3D point from this unit to another.
1042    #[allow(dead_code)]
1043    pub fn convert_point(&self, point: [f64; 3], to: LengthUnit) -> [f64; 3] {
1044        let factor = self.to_meters_factor() / to.to_meters_factor();
1045        [point[0] * factor, point[1] * factor, point[2] * factor]
1046    }
1047}
1048/// Unit converter for CAD data.
1049#[derive(Debug, Clone)]
1050pub struct UnitConverter {
1051    /// Source unit.
1052    pub from: LengthUnit,
1053    /// Target unit.
1054    pub to: LengthUnit,
1055}
1056impl UnitConverter {
1057    /// Create a new unit converter.
1058    pub fn new(from: LengthUnit, to: LengthUnit) -> Self {
1059        Self { from, to }
1060    }
1061    /// Get the conversion factor.
1062    #[allow(dead_code)]
1063    pub fn factor(&self) -> f64 {
1064        self.from.to_meters_factor() / self.to.to_meters_factor()
1065    }
1066    /// Convert a scalar value.
1067    #[allow(dead_code)]
1068    pub fn convert_scalar(&self, value: f64) -> f64 {
1069        value * self.factor()
1070    }
1071    /// Convert a 3D point.
1072    #[allow(dead_code)]
1073    pub fn convert_point(&self, point: [f64; 3]) -> [f64; 3] {
1074        let f = self.factor();
1075        [point[0] * f, point[1] * f, point[2] * f]
1076    }
1077    /// Convert an array of points.
1078    #[allow(dead_code)]
1079    pub fn convert_points(&self, points: &[[f64; 3]]) -> Vec<[f64; 3]> {
1080        let f = self.factor();
1081        points
1082            .iter()
1083            .map(|p| [p[0] * f, p[1] * f, p[2] * f])
1084            .collect()
1085    }
1086}
1087/// STL file writer for triangle meshes.
1088#[derive(Debug, Clone)]
1089pub struct StlExporter {
1090    /// Name of the solid in the STL file.
1091    pub solid_name: String,
1092}
1093impl StlExporter {
1094    /// Create a new STL exporter.
1095    pub fn new(solid_name: &str) -> Self {
1096        Self {
1097            solid_name: solid_name.to_string(),
1098        }
1099    }
1100    /// Generate ASCII STL content from a triangle mesh.
1101    #[allow(dead_code)]
1102    pub fn to_ascii_stl(&self, mesh: &TriangleMesh) -> String {
1103        let mut output = format!("solid {}\n", self.solid_name);
1104        for (i, tri) in mesh.triangles.iter().enumerate() {
1105            let p0 = mesh.vertices[tri[0]];
1106            let p1 = mesh.vertices[tri[1]];
1107            let p2 = mesh.vertices[tri[2]];
1108            let normal = if i < mesh.normals.len() {
1109                mesh.normals[i]
1110            } else {
1111                let e1 = sub3(p1, p0);
1112                let e2 = sub3(p2, p0);
1113                normalize3(cross3(e1, e2))
1114            };
1115            output += &format!(
1116                "  facet normal {:.6} {:.6} {:.6}\n",
1117                normal[0], normal[1], normal[2]
1118            );
1119            output += "    outer loop\n";
1120            output += &format!("      vertex {:.6} {:.6} {:.6}\n", p0[0], p0[1], p0[2]);
1121            output += &format!("      vertex {:.6} {:.6} {:.6}\n", p1[0], p1[1], p1[2]);
1122            output += &format!("      vertex {:.6} {:.6} {:.6}\n", p2[0], p2[1], p2[2]);
1123            output += "    endloop\n";
1124            output += "  endfacet\n";
1125        }
1126        output += &format!("endsolid {}\n", self.solid_name);
1127        output
1128    }
1129    /// Generate binary STL content as bytes.
1130    #[allow(dead_code)]
1131    pub fn to_binary_stl(&self, mesh: &TriangleMesh) -> Vec<u8> {
1132        let mut data = Vec::new();
1133        let mut header = [0u8; 80];
1134        let name_bytes = self.solid_name.as_bytes();
1135        let copy_len = name_bytes.len().min(80);
1136        header[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
1137        data.extend_from_slice(&header);
1138        let n_tri = mesh.triangles.len() as u32;
1139        data.extend_from_slice(&n_tri.to_le_bytes());
1140        for (i, tri) in mesh.triangles.iter().enumerate() {
1141            let p0 = mesh.vertices[tri[0]];
1142            let p1 = mesh.vertices[tri[1]];
1143            let p2 = mesh.vertices[tri[2]];
1144            let normal = if i < mesh.normals.len() {
1145                mesh.normals[i]
1146            } else {
1147                let e1 = sub3(p1, p0);
1148                let e2 = sub3(p2, p0);
1149                normalize3(cross3(e1, e2))
1150            };
1151            for &c in &normal {
1152                data.extend_from_slice(&(c as f32).to_le_bytes());
1153            }
1154            for p in &[p0, p1, p2] {
1155                for &c in p {
1156                    data.extend_from_slice(&(c as f32).to_le_bytes());
1157                }
1158            }
1159            data.extend_from_slice(&0u16.to_le_bytes());
1160        }
1161        data
1162    }
1163    /// Export a BREP solid as ASCII STL.
1164    #[allow(dead_code)]
1165    pub fn export_brep_ascii(&self, solid: &BrepSolid) -> String {
1166        let mesh = tessellate_brep(solid, 10);
1167        self.to_ascii_stl(&mesh)
1168    }
1169}
1170/// A parsed STEP entity.
1171#[derive(Debug, Clone)]
1172pub struct StepEntity {
1173    /// Entity ID (e.g., #123).
1174    pub id: usize,
1175    /// Entity type name (e.g., "CARTESIAN_POINT").
1176    pub entity_type: String,
1177    /// Raw parameter string.
1178    pub parameters: String,
1179}
1180impl StepEntity {
1181    /// Create a new STEP entity.
1182    pub fn new(id: usize, entity_type: &str, parameters: &str) -> Self {
1183        Self {
1184            id,
1185            entity_type: entity_type.to_string(),
1186            parameters: parameters.to_string(),
1187        }
1188    }
1189}
1190/// Parsed IGES entity.
1191#[derive(Debug, Clone)]
1192pub struct IgesEntity {
1193    /// Directory entry sequence number.
1194    pub sequence_number: usize,
1195    /// Entity type.
1196    pub entity_type: IgesEntityType,
1197    /// Parameter data pointer.
1198    pub parameter_pointer: usize,
1199    /// Status flags.
1200    pub status: u32,
1201    /// Entity label.
1202    pub label: String,
1203    /// Raw parameter data values.
1204    pub parameters: Vec<f64>,
1205}
1206impl IgesEntity {
1207    /// Create a new IGES entity.
1208    pub fn new(seq: usize, etype: IgesEntityType) -> Self {
1209        Self {
1210            sequence_number: seq,
1211            entity_type: etype,
1212            parameter_pointer: 0,
1213            status: 0,
1214            label: String::new(),
1215            parameters: Vec::new(),
1216        }
1217    }
1218}
1219/// Transform for positioning a component in an assembly.
1220#[derive(Debug, Clone)]
1221pub struct AssemblyTransform {
1222    /// 3x3 rotation matrix (row-major).
1223    pub rotation: [[f64; 3]; 3],
1224    /// Translation vector.
1225    pub translation: [f64; 3],
1226}
1227impl AssemblyTransform {
1228    /// Create an identity transform.
1229    pub fn identity() -> Self {
1230        Self {
1231            rotation: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
1232            translation: [0.0; 3],
1233        }
1234    }
1235    /// Create a translation-only transform.
1236    pub fn translation(t: [f64; 3]) -> Self {
1237        Self {
1238            rotation: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
1239            translation: t,
1240        }
1241    }
1242    /// Apply the transform to a point.
1243    #[allow(dead_code)]
1244    pub fn apply(&self, p: [f64; 3]) -> [f64; 3] {
1245        let rotated = [
1246            self.rotation[0][0] * p[0] + self.rotation[0][1] * p[1] + self.rotation[0][2] * p[2],
1247            self.rotation[1][0] * p[0] + self.rotation[1][1] * p[1] + self.rotation[1][2] * p[2],
1248            self.rotation[2][0] * p[0] + self.rotation[2][1] * p[1] + self.rotation[2][2] * p[2],
1249        ];
1250        add3(rotated, self.translation)
1251    }
1252    /// Compose two transforms: self * other.
1253    #[allow(dead_code)]
1254    pub fn compose(&self, other: &AssemblyTransform) -> AssemblyTransform {
1255        let mut new_rot = [[0.0; 3]; 3];
1256        for i in 0..3 {
1257            for j in 0..3 {
1258                for k in 0..3 {
1259                    new_rot[i][j] += self.rotation[i][k] * other.rotation[k][j];
1260                }
1261            }
1262        }
1263        let new_trans = self.apply(other.translation);
1264        AssemblyTransform {
1265            rotation: new_rot,
1266            translation: new_trans,
1267        }
1268    }
1269    /// Get the inverse transform.
1270    #[allow(dead_code)]
1271    pub fn inverse(&self) -> AssemblyTransform {
1272        let r_inv = [
1273            [
1274                self.rotation[0][0],
1275                self.rotation[1][0],
1276                self.rotation[2][0],
1277            ],
1278            [
1279                self.rotation[0][1],
1280                self.rotation[1][1],
1281                self.rotation[2][1],
1282            ],
1283            [
1284                self.rotation[0][2],
1285                self.rotation[1][2],
1286                self.rotation[2][2],
1287            ],
1288        ];
1289        let t_inv = [
1290            -(r_inv[0][0] * self.translation[0]
1291                + r_inv[0][1] * self.translation[1]
1292                + r_inv[0][2] * self.translation[2]),
1293            -(r_inv[1][0] * self.translation[0]
1294                + r_inv[1][1] * self.translation[1]
1295                + r_inv[1][2] * self.translation[2]),
1296            -(r_inv[2][0] * self.translation[0]
1297                + r_inv[2][1] * self.translation[1]
1298                + r_inv[2][2] * self.translation[2]),
1299        ];
1300        AssemblyTransform {
1301            rotation: r_inv,
1302            translation: t_inv,
1303        }
1304    }
1305}
1306/// Triangle mesh from tessellation.
1307#[derive(Debug, Clone)]
1308pub struct TriangleMesh {
1309    /// Vertex positions.
1310    pub vertices: Vec<[f64; 3]>,
1311    /// Triangle indices (each triple is one triangle).
1312    pub triangles: Vec<[usize; 3]>,
1313    /// Per-triangle normals (optional).
1314    pub normals: Vec<[f64; 3]>,
1315}
1316impl TriangleMesh {
1317    /// Create a new empty mesh.
1318    pub fn new() -> Self {
1319        Self {
1320            vertices: Vec::new(),
1321            triangles: Vec::new(),
1322            normals: Vec::new(),
1323        }
1324    }
1325    /// Add a vertex and return its index.
1326    #[allow(dead_code)]
1327    pub fn add_vertex(&mut self, position: [f64; 3]) -> usize {
1328        let idx = self.vertices.len();
1329        self.vertices.push(position);
1330        idx
1331    }
1332    /// Add a triangle.
1333    #[allow(dead_code)]
1334    pub fn add_triangle(&mut self, v0: usize, v1: usize, v2: usize) {
1335        self.triangles.push([v0, v1, v2]);
1336    }
1337    /// Compute normals for all triangles.
1338    #[allow(dead_code)]
1339    pub fn compute_normals(&mut self) {
1340        self.normals.clear();
1341        for tri in &self.triangles {
1342            let p0 = self.vertices[tri[0]];
1343            let p1 = self.vertices[tri[1]];
1344            let p2 = self.vertices[tri[2]];
1345            let e1 = sub3(p1, p0);
1346            let e2 = sub3(p2, p0);
1347            let n = normalize3(cross3(e1, e2));
1348            self.normals.push(n);
1349        }
1350    }
1351    /// Compute the bounding box.
1352    #[allow(dead_code)]
1353    pub fn bounding_box(&self) -> BoundingBox {
1354        BoundingBox::from_points(&self.vertices)
1355    }
1356    /// Compute the total surface area.
1357    #[allow(dead_code)]
1358    pub fn surface_area(&self) -> f64 {
1359        let mut area = 0.0;
1360        for tri in &self.triangles {
1361            let p0 = self.vertices[tri[0]];
1362            let p1 = self.vertices[tri[1]];
1363            let p2 = self.vertices[tri[2]];
1364            let e1 = sub3(p1, p0);
1365            let e2 = sub3(p2, p0);
1366            area += len3(cross3(e1, e2)) * 0.5;
1367        }
1368        area
1369    }
1370    /// Count the number of vertices.
1371    #[allow(dead_code)]
1372    pub fn vertex_count(&self) -> usize {
1373        self.vertices.len()
1374    }
1375    /// Count the number of triangles.
1376    #[allow(dead_code)]
1377    pub fn triangle_count(&self) -> usize {
1378        self.triangles.len()
1379    }
1380    /// Merge another mesh into this one.
1381    #[allow(dead_code)]
1382    pub fn merge(&mut self, other: &TriangleMesh) {
1383        let offset = self.vertices.len();
1384        self.vertices.extend_from_slice(&other.vertices);
1385        for tri in &other.triangles {
1386            self.triangles
1387                .push([tri[0] + offset, tri[1] + offset, tri[2] + offset]);
1388        }
1389        self.normals.extend_from_slice(&other.normals);
1390    }
1391}
1392/// Types of curves in BREP.
1393#[derive(Debug, Clone, Copy, PartialEq)]
1394pub enum CurveType {
1395    /// Straight line segment.
1396    Line,
1397    /// Circular arc.
1398    CircularArc,
1399    /// B-spline curve.
1400    BSpline,
1401    /// NURBS curve.
1402    Nurbs,
1403}