Skip to main content

oxihuman_export/
threedxml_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! 3DXML (CATIA) format export stub.
6//! Note: module name is `threedxml_export` because Rust identifiers
7//! cannot start with a digit.
8
9/// A 3DXML reference occurrence.
10#[derive(Debug, Clone)]
11pub struct ThreeDXmlOccurrence {
12    pub id: u32,
13    pub name: String,
14    pub matrix: [[f32; 4]; 4],
15}
16
17/// A 3DXML representation stub.
18#[derive(Debug, Clone)]
19pub struct ThreeDXmlRep {
20    pub id: u32,
21    pub verts: Vec<[f32; 3]>,
22    pub tris: Vec<[u32; 3]>,
23}
24
25/// 3DXML export container.
26#[derive(Debug, Clone, Default)]
27pub struct ThreeDXmlExport {
28    pub schema_version: String,
29    pub occurrences: Vec<ThreeDXmlOccurrence>,
30    pub reps: Vec<ThreeDXmlRep>,
31}
32
33/// Create a new 3DXML export.
34pub fn new_threedxml_export(schema_version: &str) -> ThreeDXmlExport {
35    ThreeDXmlExport {
36        schema_version: schema_version.to_string(),
37        occurrences: Vec::new(),
38        reps: Vec::new(),
39    }
40}
41
42/// Add an occurrence; returns its id.
43pub fn add_threedxml_occurrence(export: &mut ThreeDXmlExport, name: &str) -> u32 {
44    let id = export.occurrences.len() as u32 + 1;
45    let matrix = [
46        [1.0, 0.0, 0.0, 0.0],
47        [0.0, 1.0, 0.0, 0.0],
48        [0.0, 0.0, 1.0, 0.0],
49        [0.0, 0.0, 0.0, 1.0],
50    ];
51    export.occurrences.push(ThreeDXmlOccurrence {
52        id,
53        name: name.to_string(),
54        matrix,
55    });
56    id
57}
58
59/// Add a representation; returns its id.
60pub fn add_threedxml_rep(
61    export: &mut ThreeDXmlExport,
62    verts: Vec<[f32; 3]>,
63    tris: Vec<[u32; 3]>,
64) -> u32 {
65    let id = export.reps.len() as u32 + 1;
66    export.reps.push(ThreeDXmlRep { id, verts, tris });
67    id
68}
69
70/// Return occurrence count.
71pub fn threedxml_occurrence_count(export: &ThreeDXmlExport) -> usize {
72    export.occurrences.len()
73}
74
75/// Return representation count.
76pub fn threedxml_rep_count(export: &ThreeDXmlExport) -> usize {
77    export.reps.len()
78}
79
80/// Render a stub XML header.
81pub fn threedxml_xml_header(export: &ThreeDXmlExport) -> String {
82    format!(
83        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XPDMRoot schemaVersion=\"{}\">",
84        export.schema_version
85    )
86}
87
88/// Validate triangle indices within each representation.
89pub fn validate_threedxml(export: &ThreeDXmlExport) -> bool {
90    export.reps.iter().all(|rep| {
91        let n = rep.verts.len() as u32;
92        rep.tris.iter().all(|t| t[0] < n && t[1] < n && t[2] < n)
93    })
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_new_export_empty() {
102        let exp = new_threedxml_export("4.0");
103        assert_eq!(threedxml_occurrence_count(&exp), 0);
104        assert_eq!(threedxml_rep_count(&exp), 0);
105    }
106
107    #[test]
108    fn test_add_occurrence() {
109        let mut exp = new_threedxml_export("4.0");
110        let id = add_threedxml_occurrence(&mut exp, "Part1");
111        assert_eq!(id, 1);
112        assert_eq!(threedxml_occurrence_count(&exp), 1);
113    }
114
115    #[test]
116    fn test_add_rep() {
117        let mut exp = new_threedxml_export("4.0");
118        let v = vec![[0.0f32; 3]; 3];
119        let t = vec![[0u32, 1, 2]];
120        let id = add_threedxml_rep(&mut exp, v, t);
121        assert_eq!(id, 1);
122    }
123
124    #[test]
125    fn test_xml_header_contains_schema() {
126        let exp = new_threedxml_export("4.0");
127        assert!(threedxml_xml_header(&exp).contains("4.0"));
128    }
129
130    #[test]
131    fn test_validate_valid() {
132        let mut exp = new_threedxml_export("4.0");
133        let v = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
134        let t = vec![[0u32, 1, 2]];
135        add_threedxml_rep(&mut exp, v, t);
136        assert!(validate_threedxml(&exp));
137    }
138
139    #[test]
140    fn test_occurrence_name_stored() {
141        let mut exp = new_threedxml_export("4.0");
142        add_threedxml_occurrence(&mut exp, "MyPart");
143        assert_eq!(exp.occurrences[0].name, "MyPart");
144    }
145
146    #[test]
147    fn test_identity_matrix() {
148        let mut exp = new_threedxml_export("4.0");
149        add_threedxml_occurrence(&mut exp, "X");
150        let m = exp.occurrences[0].matrix;
151        #[allow(clippy::needless_range_loop)]
152        for i in 0..4 {
153            assert!((m[i][i] - 1.0).abs() < 1e-6);
154        }
155    }
156
157    #[test]
158    fn test_schema_version_stored() {
159        let exp = new_threedxml_export("5.0");
160        assert_eq!(exp.schema_version, "5.0");
161    }
162
163    #[test]
164    fn test_validate_empty() {
165        assert!(validate_threedxml(&new_threedxml_export("4.0")));
166    }
167
168    #[test]
169    fn test_multiple_occurrences() {
170        let mut exp = new_threedxml_export("4.0");
171        for name in ["A", "B", "C"] {
172            add_threedxml_occurrence(&mut exp, name);
173        }
174        assert_eq!(threedxml_occurrence_count(&exp), 3);
175    }
176}