#![allow(clippy::disallowed_methods)]
mod act;
pub mod asm_header;
pub mod brep;
mod bytes;
pub mod container;
pub mod decode;
pub mod design;
pub mod f3z;
pub mod history;
mod history_records;
mod ids;
pub mod materials;
mod native;
pub mod nurbs;
mod protein;
pub mod records;
pub mod sab;
mod tsm;
pub mod validate;
mod writer;
pub mod xref;
use cadmpeg_ir::codec::{Codec, CodecError, Confidence, ContainerSummary, DecodeResult, Encoder};
use cadmpeg_ir::decode::{DecodeContext, View};
use cadmpeg_ir::document::CadIr;
use cadmpeg_ir::hash::sha256_hex;
use cadmpeg_ir::report::ExportReport;
use std::io::Write;
const ZIP_MAGIC: &[u8] = b"PK\x03\x04";
#[derive(Debug, Default, Clone, Copy)]
pub struct F3dCodec;
impl F3dCodec {
pub fn write_preserved_with_source_fidelity(
&self,
ir: &CadIr,
source_fidelity: &cadmpeg_ir::SourceFidelity,
writer: &mut dyn Write,
) -> Result<(), CodecError> {
let record = source_fidelity
.retained_record(ids::FILE_SOURCE_IMAGE_ID)
.ok_or_else(|| {
CodecError::NotImplemented("sidecar has no retained F3D source image".into())
})?;
let data = record.data.as_ref().ok_or_else(|| {
CodecError::Malformed("retained F3D source image has no bytes".into())
})?;
Self::write_preserved_bytes(ir, data, record.byte_len, &record.sha256, writer)
}
fn write_preserved_bytes(
ir: &CadIr,
data: &[u8],
byte_len: u64,
sha256: &str,
writer: &mut dyn Write,
) -> Result<(), CodecError> {
let expected = ir
.source
.as_ref()
.and_then(|source| source.attributes.get("semantic_sha256"))
.ok_or_else(|| CodecError::NotImplemented("IR has no F3D semantic baseline".into()))?;
let hash = sha256_hex(data);
if data.len() as u64 != byte_len || hash != sha256 {
return Err(CodecError::Malformed(
"retained F3D source image failed integrity validation".into(),
));
}
if decode::semantic_hash(ir) != *expected {
return writer::patch::write_semantic(ir, data, writer);
}
writer.write_all(data)?;
Ok(())
}
}
impl Codec for F3dCodec {
fn id(&self) -> &'static str {
"f3d"
}
fn detect(&self, prefix: &[u8]) -> Confidence {
if !prefix.starts_with(ZIP_MAGIC) {
return Confidence::No;
}
if container::DETECT_MARKERS
.iter()
.chain(container::F3Z_DETECT_MARKERS)
.any(|m| contains_subslice(prefix, m))
{
Confidence::High
} else {
Confidence::Low
}
}
fn inspect_impl(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<ContainerSummary, CodecError> {
let scan = container::scan(ctx, root)?;
Ok(container::summarize(&scan))
}
fn decode_impl(
&self,
ctx: &DecodeContext<'_>,
root: View<'_>,
) -> Result<DecodeResult, CodecError> {
decode::decode(ctx, root)
}
}
impl Encoder for F3dCodec {
fn id(&self) -> &'static str {
"f3d"
}
fn encode(&self, ir: &CadIr, writer: &mut dyn Write) -> Result<ExportReport, CodecError> {
writer::generate::write_new(ir, writer)?;
let validation = cadmpeg_ir::validate(ir, Vec::new());
let total_entities = validation.entity_counts.values().sum();
Ok(ExportReport {
format: "f3d".into(),
entity_counts: validation.entity_counts,
total_entities,
losses: Vec::new(),
notes: vec![
"source container regenerated from IR".into(),
"entity counts are derived from the IR".into(),
],
})
}
fn encode_with_source_fidelity(
&self,
ir: &CadIr,
source_fidelity: Option<&cadmpeg_ir::SourceFidelity>,
writer: &mut dyn Write,
) -> Result<ExportReport, CodecError> {
let replay = source_fidelity
.and_then(|sidecar| sidecar.retained_record(ids::FILE_SOURCE_IMAGE_ID))
.is_some();
if let Some(sidecar) = source_fidelity.filter(|_| replay) {
self.write_preserved_with_source_fidelity(ir, sidecar, writer)?;
} else {
writer::generate::write_new(ir, writer)?;
}
let validation = cadmpeg_ir::validate(ir, Vec::new());
let total_entities = validation.entity_counts.values().sum();
Ok(ExportReport {
format: "f3d".into(),
entity_counts: validation.entity_counts,
total_entities,
losses: Vec::new(),
notes: vec![
if replay {
"preserved source container replayed verbatim"
} else {
"source container regenerated from IR"
}
.into(),
"entity counts are derived from the IR".into(),
],
})
}
}
fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
if needle.is_empty() || haystack.len() < needle.len() {
return false;
}
haystack.windows(needle.len()).any(|w| w == needle)
}
#[cfg(test)]
mod tests;