Skip to main content

oxihuman_export/
export_preset.rs

1//! Save and load export presets for repeated export configurations.
2
3#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum ExportTarget {
6    Glb,
7    Obj,
8    Fbx,
9    Usdz,
10    Ply,
11    Stl,
12    Collada,
13    Custom(String),
14}
15
16#[allow(dead_code)]
17#[derive(Clone)]
18pub struct ExportPreset {
19    pub name: String,
20    pub target: ExportTarget,
21    pub include_normals: bool,
22    pub include_uvs: bool,
23    pub include_animations: bool,
24    pub include_materials: bool,
25    pub scale: f32,
26    pub up_axis: String,
27    pub custom_options: Vec<(String, String)>,
28}
29
30#[allow(dead_code)]
31pub struct PresetLibraryExport {
32    pub presets: Vec<ExportPreset>,
33    pub default_preset: Option<String>,
34}
35
36#[allow(dead_code)]
37pub fn default_glb_preset() -> ExportPreset {
38    ExportPreset {
39        name: "Default GLB".to_string(),
40        target: ExportTarget::Glb,
41        include_normals: true,
42        include_uvs: true,
43        include_animations: true,
44        include_materials: true,
45        scale: 1.0,
46        up_axis: "Y".to_string(),
47        custom_options: Vec::new(),
48    }
49}
50
51#[allow(dead_code)]
52pub fn default_obj_preset() -> ExportPreset {
53    ExportPreset {
54        name: "Default OBJ".to_string(),
55        target: ExportTarget::Obj,
56        include_normals: true,
57        include_uvs: true,
58        include_animations: false,
59        include_materials: true,
60        scale: 1.0,
61        up_axis: "Y".to_string(),
62        custom_options: Vec::new(),
63    }
64}
65
66#[allow(dead_code)]
67pub fn new_preset_library() -> PresetLibraryExport {
68    PresetLibraryExport {
69        presets: Vec::new(),
70        default_preset: None,
71    }
72}
73
74#[allow(dead_code)]
75pub fn add_preset(lib: &mut PresetLibraryExport, preset: ExportPreset) {
76    lib.presets.push(preset);
77}
78
79#[allow(dead_code)]
80pub fn get_preset_by_name<'a>(
81    lib: &'a PresetLibraryExport,
82    name: &str,
83) -> Option<&'a ExportPreset> {
84    lib.presets.iter().find(|p| p.name == name)
85}
86
87#[allow(dead_code)]
88pub fn remove_preset(lib: &mut PresetLibraryExport, name: &str) -> bool {
89    let before = lib.presets.len();
90    lib.presets.retain(|p| p.name != name);
91    let removed = lib.presets.len() < before;
92    if removed {
93        if let Some(ref default) = lib.default_preset.clone() {
94            if default == name {
95                lib.default_preset = None;
96            }
97        }
98    }
99    removed
100}
101
102#[allow(dead_code)]
103pub fn set_default_preset(lib: &mut PresetLibraryExport, name: &str) {
104    lib.default_preset = Some(name.to_string());
105}
106
107#[allow(dead_code)]
108pub fn preset_count(lib: &PresetLibraryExport) -> usize {
109    lib.presets.len()
110}
111
112#[allow(dead_code)]
113pub fn presets_for_target<'a>(
114    lib: &'a PresetLibraryExport,
115    target: &ExportTarget,
116) -> Vec<&'a ExportPreset> {
117    lib.presets.iter().filter(|p| &p.target == target).collect()
118}
119
120#[allow(dead_code)]
121pub fn preset_to_json(preset: &ExportPreset) -> String {
122    let target_str = match &preset.target {
123        ExportTarget::Glb => "glb",
124        ExportTarget::Obj => "obj",
125        ExportTarget::Fbx => "fbx",
126        ExportTarget::Usdz => "usdz",
127        ExportTarget::Ply => "ply",
128        ExportTarget::Stl => "stl",
129        ExportTarget::Collada => "collada",
130        ExportTarget::Custom(s) => s.as_str(),
131    };
132    let custom_opts: Vec<String> = preset
133        .custom_options
134        .iter()
135        .map(|(k, v)| format!("{{\"key\":\"{k}\",\"value\":\"{v}\"}}"))
136        .collect();
137    format!(
138        "{{\"name\":\"{}\",\"target\":\"{}\",\"include_normals\":{},\"include_uvs\":{},\
139         \"include_animations\":{},\"include_materials\":{},\"scale\":{},\"up_axis\":\"{}\",\
140         \"custom_options\":[{}]}}",
141        preset.name,
142        target_str,
143        preset.include_normals,
144        preset.include_uvs,
145        preset.include_animations,
146        preset.include_materials,
147        preset.scale,
148        preset.up_axis,
149        custom_opts.join(",")
150    )
151}
152
153#[allow(dead_code)]
154pub fn preset_library_to_json(lib: &PresetLibraryExport) -> String {
155    let presets_json: Vec<String> = lib.presets.iter().map(preset_to_json).collect();
156    let default_str = match &lib.default_preset {
157        Some(name) => format!("\"{name}\""),
158        None => "null".to_string(),
159    };
160    format!(
161        "{{\"presets\":[{}],\"default_preset\":{}}}",
162        presets_json.join(","),
163        default_str
164    )
165}
166
167#[allow(dead_code)]
168pub fn clone_preset(preset: &ExportPreset, new_name: &str) -> ExportPreset {
169    let mut cloned = preset.clone();
170    cloned.name = new_name.to_string();
171    cloned
172}
173
174#[allow(dead_code)]
175pub fn add_custom_option(preset: &mut ExportPreset, key: &str, value: &str) {
176    preset
177        .custom_options
178        .push((key.to_string(), value.to_string()));
179}
180
181#[allow(dead_code)]
182pub fn target_extension(target: &ExportTarget) -> &'static str {
183    match target {
184        ExportTarget::Glb => ".glb",
185        ExportTarget::Obj => ".obj",
186        ExportTarget::Fbx => ".fbx",
187        ExportTarget::Usdz => ".usdz",
188        ExportTarget::Ply => ".ply",
189        ExportTarget::Stl => ".stl",
190        ExportTarget::Collada => ".dae",
191        ExportTarget::Custom(_) => "",
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_default_glb_preset() {
201        let p = default_glb_preset();
202        assert_eq!(p.target, ExportTarget::Glb);
203        assert!(p.include_normals);
204        assert!(p.include_animations);
205        assert!((p.scale - 1.0).abs() < f32::EPSILON);
206    }
207
208    #[test]
209    fn test_default_obj_preset() {
210        let p = default_obj_preset();
211        assert_eq!(p.target, ExportTarget::Obj);
212        assert!(!p.include_animations);
213        assert!(p.include_uvs);
214    }
215
216    #[test]
217    fn test_new_library() {
218        let lib = new_preset_library();
219        assert!(lib.presets.is_empty());
220        assert!(lib.default_preset.is_none());
221    }
222
223    #[test]
224    fn test_add_preset() {
225        let mut lib = new_preset_library();
226        add_preset(&mut lib, default_glb_preset());
227        assert_eq!(preset_count(&lib), 1);
228    }
229
230    #[test]
231    fn test_get_preset_by_name() {
232        let mut lib = new_preset_library();
233        add_preset(&mut lib, default_glb_preset());
234        let found = get_preset_by_name(&lib, "Default GLB");
235        assert!(found.is_some());
236        assert_eq!(found.expect("should succeed").target, ExportTarget::Glb);
237    }
238
239    #[test]
240    fn test_get_preset_by_name_not_found() {
241        let lib = new_preset_library();
242        assert!(get_preset_by_name(&lib, "Missing").is_none());
243    }
244
245    #[test]
246    fn test_remove_preset() {
247        let mut lib = new_preset_library();
248        add_preset(&mut lib, default_glb_preset());
249        let removed = remove_preset(&mut lib, "Default GLB");
250        assert!(removed);
251        assert_eq!(preset_count(&lib), 0);
252    }
253
254    #[test]
255    fn test_remove_preset_not_found() {
256        let mut lib = new_preset_library();
257        let removed = remove_preset(&mut lib, "Nonexistent");
258        assert!(!removed);
259    }
260
261    #[test]
262    fn test_set_default_preset() {
263        let mut lib = new_preset_library();
264        add_preset(&mut lib, default_glb_preset());
265        set_default_preset(&mut lib, "Default GLB");
266        assert_eq!(lib.default_preset.as_deref(), Some("Default GLB"));
267    }
268
269    #[test]
270    fn test_preset_count() {
271        let mut lib = new_preset_library();
272        assert_eq!(preset_count(&lib), 0);
273        add_preset(&mut lib, default_glb_preset());
274        add_preset(&mut lib, default_obj_preset());
275        assert_eq!(preset_count(&lib), 2);
276    }
277
278    #[test]
279    fn test_presets_for_target() {
280        let mut lib = new_preset_library();
281        add_preset(&mut lib, default_glb_preset());
282        add_preset(&mut lib, default_obj_preset());
283        let glb_presets = presets_for_target(&lib, &ExportTarget::Glb);
284        assert_eq!(glb_presets.len(), 1);
285        assert_eq!(glb_presets[0].target, ExportTarget::Glb);
286    }
287
288    #[test]
289    fn test_preset_to_json_non_empty() {
290        let p = default_glb_preset();
291        let json = preset_to_json(&p);
292        assert!(!json.is_empty());
293        assert!(json.contains("glb"));
294        assert!(json.contains("Default GLB"));
295    }
296
297    #[test]
298    fn test_preset_library_to_json() {
299        let mut lib = new_preset_library();
300        add_preset(&mut lib, default_glb_preset());
301        let json = preset_library_to_json(&lib);
302        assert!(!json.is_empty());
303        assert!(json.contains("presets"));
304    }
305
306    #[test]
307    fn test_clone_preset() {
308        let p = default_glb_preset();
309        let cloned = clone_preset(&p, "My GLB Clone");
310        assert_eq!(cloned.name, "My GLB Clone");
311        assert_eq!(cloned.target, ExportTarget::Glb);
312    }
313
314    #[test]
315    fn test_add_custom_option() {
316        let mut p = default_glb_preset();
317        add_custom_option(&mut p, "compress", "true");
318        assert_eq!(p.custom_options.len(), 1);
319        assert_eq!(p.custom_options[0].0, "compress");
320        assert_eq!(p.custom_options[0].1, "true");
321    }
322
323    #[test]
324    fn test_target_extension() {
325        assert_eq!(target_extension(&ExportTarget::Glb), ".glb");
326        assert_eq!(target_extension(&ExportTarget::Obj), ".obj");
327        assert_eq!(target_extension(&ExportTarget::Fbx), ".fbx");
328        assert_eq!(target_extension(&ExportTarget::Usdz), ".usdz");
329        assert_eq!(target_extension(&ExportTarget::Ply), ".ply");
330        assert_eq!(target_extension(&ExportTarget::Stl), ".stl");
331        assert_eq!(target_extension(&ExportTarget::Collada), ".dae");
332        assert_eq!(
333            target_extension(&ExportTarget::Custom("abc".to_string())),
334            ""
335        );
336    }
337
338    #[test]
339    fn test_remove_default_preset_clears_default() {
340        let mut lib = new_preset_library();
341        add_preset(&mut lib, default_glb_preset());
342        set_default_preset(&mut lib, "Default GLB");
343        remove_preset(&mut lib, "Default GLB");
344        assert!(lib.default_preset.is_none());
345    }
346}