blender_mesh/
export.rs

1use crate::BlenderMesh;
2use failure::Fail;
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5
6pub type MeshesByFilename = HashMap<String, MeshesByMeshName>;
7pub type MeshesByMeshName = HashMap<String, BlenderMesh>;
8
9/// Given a buffer of standard output from Blender we parse all of the mesh JSON that was
10/// written to stdout by `blender-mesh-to-json.py`.
11///
12/// Meshes data in stdout will look like:
13///
14/// START_MESH_JSON /path/to/file.blend my_mesh_name
15/// {...}
16/// END_MESH_JSON /path/to/file.blend my_mesh_name
17///
18/// @see blender-mesh-to-json.py - This is where we write to stdout
19pub fn parse_meshes_from_blender_stdout(blender_stdout: &str) -> MeshesByFilename {
20    let mut filenames_to_meshes = HashMap::new();
21
22    let mut index = 0;
23
24    while let Some((filename_to_mesh, next_start_index)) =
25        find_first_mesh_after_index(blender_stdout, index)
26    {
27        for (filename, meshes) in filename_to_mesh.into_iter() {
28            match filenames_to_meshes.entry(filename) {
29                Entry::Vacant(v) => {
30                    v.insert(meshes);
31                }
32                Entry::Occupied(ref mut o) => {
33                    o.get_mut().extend(meshes);
34                }
35            }
36        }
37
38        index = next_start_index;
39    }
40
41    filenames_to_meshes
42}
43
44pub type FlattenedExportedMeshes = HashMap<String, BlenderMesh>;
45
46/// Convert MesheshByFilename into a HashMap<MeshName, BlenderMesh> that flattens all of the
47/// meshes across all of the files into one HashMap.
48///
49/// This will error if there are two meshes with the same name across two or more files.
50pub fn flatten_exported_meshes_owned(
51    meshes_by_filename: MeshesByFilename,
52) -> Result<FlattenedExportedMeshes, FlattenMeshError> {
53    let mut flattened_meshes = HashMap::new();
54
55    let mut duplicate_meshes: HashMap<String, Vec<String>> = HashMap::new();
56
57    for (source_filename, meshes) in meshes_by_filename.into_iter() {
58        for (mesh_name, mesh) in meshes.into_iter() {
59            match duplicate_meshes.entry(mesh_name.to_string()) {
60                Entry::Occupied(mut duplicates) => {
61                    duplicates.get_mut().push(source_filename.to_string());
62                }
63                Entry::Vacant(filenames) => {
64                    filenames.insert(vec![source_filename.to_string()]);
65                }
66            };
67
68            flattened_meshes.insert(mesh_name, mesh);
69        }
70    }
71
72    validate_no_duplicates(duplicate_meshes)?;
73
74    Ok(flattened_meshes)
75}
76
77/// Convert MesheshByFilename into a HashMap<MeshName, &BlenderMesh> that flattens all of the
78/// meshes across all of the files into one HashMap.
79///
80/// This will error if there are two meshes with the same name across two or more files.
81pub fn flatten_exported_meshes(
82    meshes_by_filename: &MeshesByFilename,
83) -> Result<HashMap<&str, &BlenderMesh>, FlattenMeshError> {
84    let mut flattened_meshes = HashMap::new();
85
86    let mut duplicate_meshes: HashMap<String, Vec<String>> = HashMap::new();
87
88    for (source_filename, meshes) in meshes_by_filename.iter() {
89        for (mesh_name, mesh) in meshes.iter() {
90            flattened_meshes.insert(mesh_name.as_str(), mesh);
91
92            match duplicate_meshes.entry(mesh_name.to_string()) {
93                Entry::Occupied(mut duplicates) => {
94                    duplicates.get_mut().push(source_filename.to_string());
95                }
96                Entry::Vacant(filenames) => {
97                    filenames.insert(vec![source_filename.to_string()]);
98                }
99            };
100        }
101    }
102
103    validate_no_duplicates(duplicate_meshes)?;
104
105    Ok(flattened_meshes)
106}
107
108fn validate_no_duplicates(
109    duplicate_meshes: HashMap<String, Vec<String>>,
110) -> Result<(), FlattenMeshError> {
111    let duplicate_meshes: HashMap<String, Vec<String>> = duplicate_meshes
112        .into_iter()
113        .filter(|(_mesh_name, filenames)| filenames.len() > 1)
114        .collect();
115
116    if duplicate_meshes.len() > 0 {
117        return Err(FlattenMeshError::DuplicateMeshNamesAcrossFiles {
118            duplicates: duplicate_meshes,
119        });
120    }
121
122    Ok(())
123}
124
125/// An error when trying to flatten your exported data across multiple files into one HashMap of
126/// mesh name to mesh data.
127#[derive(Debug, Fail)]
128pub enum FlattenMeshError {
129    #[fail(display = "Duplicate meshes found: {:#?}", duplicates)]
130    DuplicateMeshNamesAcrossFiles {
131        // HashMap<MeshName, Vec<FilesThatItAppearsIn>>
132        duplicates: HashMap<String, Vec<String>>,
133    },
134}
135
136fn find_first_mesh_after_index(
137    blender_stdout: &str,
138    index: usize,
139) -> Option<(MeshesByFilename, usize)> {
140    let blender_stdout = &blender_stdout[index as usize..];
141
142    if let Some(mesh_start_index) = blender_stdout.find("START_MESH_JSON") {
143        let mut filenames_to_meshes = HashMap::new();
144        let mut mesh_name_to_data = HashMap::new();
145
146        let mesh_end_index = blender_stdout.find("END_MESH_JSON").unwrap();
147
148        let mesh_data = &blender_stdout[mesh_start_index..mesh_end_index];
149
150        let mut lines = mesh_data.lines();
151
152        let first_line = lines.next().unwrap();
153
154        let mesh_filename: Vec<&str> = first_line.split(" ").collect();
155        let mesh_filename = mesh_filename[1].to_string();
156
157        let mesh_name = first_line.split(" ").last().unwrap().to_string();
158
159        let mesh_data: String = lines.collect();
160        let mesh_data: BlenderMesh = serde_json::from_str(&mesh_data).unwrap();
161
162        mesh_name_to_data.insert(mesh_name, mesh_data);
163        filenames_to_meshes.insert(mesh_filename, mesh_name_to_data);
164
165        return Some((filenames_to_meshes, index + mesh_end_index + 1));
166    }
167
168    return None;
169}