use crate::error::{Error, Result};
use crate::model::{Model, ParserConfig};
use crate::opc::Package;
use std::collections::{HashMap, HashSet};
use std::io::Read;
use super::boolean_ops::{validate_external_model_triangles, validate_external_object_reference};
use super::secure_content::validate_encrypted_file_can_be_loaded;
pub(super) fn validate_production_external_paths<R: Read + std::io::Seek>(
package: &mut Package<R>,
model: &Model,
config: &ParserConfig,
) -> Result<()> {
let mut external_file_cache: HashMap<String, Vec<(usize, Option<String>)>> = HashMap::new();
let mut validated_files: HashSet<String> = HashSet::new();
for (idx, item) in model.build.items.iter().enumerate() {
if let Some(ref path) = item.production_path {
let normalized_path = path.trim_start_matches('/');
let is_encrypted = model
.secure_content
.as_ref()
.map(|sc| {
sc.encrypted_files.iter().any(|encrypted_path| {
let enc_normalized = encrypted_path.trim_start_matches('/');
enc_normalized == normalized_path
})
})
.unwrap_or(false);
if is_encrypted {
validate_encrypted_file_can_be_loaded(
package,
normalized_path,
path,
model,
config,
&format!("Build item {}", idx),
)?;
continue;
}
if !package.has_file(normalized_path) {
return Err(Error::InvalidModel(format!(
"Build item {}: References non-existent external file: {}\n\
The p:path attribute must reference a valid model file in the 3MF package.\n\
Check that:\n\
- The file exists in the package\n\
- The path is correct (case-sensitive)\n\
- The path format follows 3MF conventions (e.g., /3D/filename.model)",
idx, path
)));
}
validate_external_object_reference(
package,
normalized_path,
item.objectid,
&item.production_uuid,
&format!("Build item {}", idx),
&mut external_file_cache,
model,
config,
)?;
validate_external_model_triangles(
package,
normalized_path,
model,
&mut validated_files,
config,
)?;
}
}
for object in &model.resources.objects {
for (comp_idx, component) in object.components.iter().enumerate() {
if let Some(ref prod_info) = component.production
&& let Some(ref path) = prod_info.path
{
let normalized_path = path.trim_start_matches('/');
let is_encrypted = model
.secure_content
.as_ref()
.map(|sc| {
sc.encrypted_files.iter().any(|encrypted_path| {
let enc_normalized = encrypted_path.trim_start_matches('/');
enc_normalized == normalized_path
})
})
.unwrap_or(false);
if is_encrypted {
validate_encrypted_file_can_be_loaded(
package,
normalized_path,
path,
model,
config,
&format!("Object {}, Component {}", object.id, comp_idx),
)?;
continue;
}
if !package.has_file(normalized_path) {
return Err(Error::InvalidModel(format!(
"Object {}, Component {}: References non-existent external file: {}\n\
The p:path attribute must reference a valid model file in the 3MF package.\n\
Check that:\n\
- The file exists in the package\n\
- The path is correct (case-sensitive)\n\
- The path format follows 3MF conventions (e.g., /3D/filename.model)",
object.id, comp_idx, path
)));
}
validate_external_object_reference(
package,
normalized_path,
component.objectid,
&prod_info.uuid,
&format!("Object {}, Component {}", object.id, comp_idx),
&mut external_file_cache,
model,
config,
)?;
validate_external_model_triangles(
package,
normalized_path,
model,
&mut validated_files,
config,
)?;
}
}
}
Ok(())
}