use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
use crate::decode::Scan;
use crate::intersection::{self, CurveScan};
use crate::parasolid::{Stream, StreamKind};
use crate::topology::{self, BlendSurface, Graph, OffsetSurface, SurfaceCurve, TrimmedCurve};
pub(crate) fn semantic_streams(scan: &Scan) -> Vec<Vec<u8>> {
let mut semantic = topology_streams(scan);
let pairs = paired_delta_streams(scan);
let paired_deltas = pairs.values().flatten().copied().collect::<BTreeSet<_>>();
for (delta, stream) in scan.streams.iter().enumerate() {
if stream.kind == StreamKind::Deltas && !paired_deltas.contains(&delta) {
semantic[delta]
.extend_from_slice(&crate::deltas::procedural_residual(&stream.inflated));
}
}
for (partition, deltas) in pairs {
for delta in deltas {
semantic[partition].extend_from_slice(&crate::deltas::procedural_residual(
&scan.streams[delta].inflated,
));
semantic[delta].clear();
}
}
semantic
}
pub(crate) fn topology_streams(scan: &Scan) -> Vec<Vec<u8>> {
let mut semantic = scan
.streams
.iter()
.map(|stream| stream.inflated.clone())
.collect::<Vec<_>>();
let pairs = paired_delta_streams(scan);
let paired_deltas = pairs.values().flatten().copied().collect::<BTreeSet<_>>();
for (delta, stream) in scan.streams.iter().enumerate() {
if stream.kind == StreamKind::Deltas && !paired_deltas.contains(&delta) {
let census = crate::deltas::walk(&stream.inflated);
if !census.records.is_empty() || !census.tombstones.is_empty() {
semantic[delta] = crate::deltas::merge_full_records(&[], &stream.inflated);
}
}
}
for (partition, deltas) in pairs {
for delta in deltas {
semantic[partition] =
crate::deltas::merge_full_records(&semantic[partition], &semantic[delta]);
semantic[delta].clear();
}
}
semantic
}
pub(crate) fn paired_delta_streams(scan: &Scan) -> BTreeMap<usize, Vec<usize>> {
let links = super::segments::segment_stream_links(&scan.container, &scan.streams);
let linked_deltas = links
.iter()
.filter(|link| link.stream_kind == "deltas")
.map(|link| link.stream_ordinal as usize)
.collect::<BTreeSet<_>>();
pair_stream_indices(&scan.streams, (!links.is_empty()).then_some(&linked_deltas))
}
pub(crate) fn pair_stream_indices(
streams: &[Stream],
eligible_deltas: Option<&BTreeSet<usize>>,
) -> BTreeMap<usize, Vec<usize>> {
let mut pairs = BTreeMap::<usize, Vec<usize>>::new();
for (delta, stream) in streams.iter().enumerate() {
if stream.kind != StreamKind::Deltas
|| eligible_deltas.is_some_and(|eligible| !eligible.contains(&delta))
{
continue;
}
let partition = streams[..delta]
.iter()
.enumerate()
.rev()
.find(|(_, candidate)| {
candidate.kind == StreamKind::Partition && candidate.schema == stream.schema
})
.map(|(partition, _)| partition);
if let Some(partition) = partition {
pairs.entry(partition).or_default().push(delta);
}
}
pairs
}
pub(crate) struct StreamView {
pub(crate) graph: Graph,
pub(crate) offset_surfaces: Vec<OffsetSurface>,
pub(crate) blend_surfaces: Vec<BlendSurface>,
pub(crate) trimmed_curves: Vec<TrimmedCurve>,
pub(crate) surface_curves: Vec<SurfaceCurve>,
pub(crate) intersections: CurveScan,
}
impl StreamView {
fn empty() -> Self {
StreamView {
graph: Graph::default(),
offset_surfaces: Vec::new(),
blend_surfaces: Vec::new(),
trimmed_curves: Vec::new(),
surface_curves: Vec::new(),
intersections: CurveScan::default(),
}
}
fn parse_uniform(bytes: &[u8]) -> Self {
StreamView {
graph: Graph::parse(bytes),
offset_surfaces: topology::offset_surfaces(bytes),
blend_surfaces: topology::blend_surfaces(bytes),
trimmed_curves: topology::trimmed_curves(bytes),
surface_curves: topology::surface_curves(bytes),
intersections: intersection::scan(bytes),
}
}
fn parse_semantic(
topology_bytes: &[u8],
semantic_bytes: &[u8],
scan: &Scan,
paired_deltas: Option<&Vec<usize>>,
) -> Self {
let intersections = if let Some(delta_indices) = paired_deltas {
let replacement_streams = delta_indices
.iter()
.map(|delta| scan.streams[*delta].inflated.as_slice())
.collect::<Vec<_>>();
intersection::scan_with_auxiliary_replacements(
semantic_bytes,
topology_bytes,
&replacement_streams,
)
} else {
intersection::scan(semantic_bytes)
};
StreamView {
graph: Graph::parse(topology_bytes),
offset_surfaces: topology::offset_surfaces(semantic_bytes),
blend_surfaces: topology::blend_surfaces(semantic_bytes),
trimmed_curves: topology::trimmed_curves(semantic_bytes),
surface_curves: topology::surface_curves(semantic_bytes),
intersections,
}
}
}
pub(crate) struct StreamParses {
raw: Rc<StreamView>,
semantic: Rc<StreamView>,
}
impl StreamParses {
pub(crate) fn view_for_records(&self) -> &StreamView {
&self.raw
}
pub(crate) fn view_for_geometry(&self) -> &StreamView {
&self.semantic
}
}
pub(crate) struct ParsedStreams {
per_stream: Vec<StreamParses>,
semantic_streams: Vec<Vec<u8>>,
}
impl ParsedStreams {
pub(crate) fn parse(scan: &Scan) -> Self {
let semantic_streams = semantic_streams(scan);
let topology_streams = topology_streams(scan);
let delta_pairs = paired_delta_streams(scan);
let per_stream = scan
.streams
.iter()
.enumerate()
.map(|(si, stream)| {
if !stream.kind.is_parasolid() {
let empty = Rc::new(StreamView::empty());
return StreamParses {
raw: empty.clone(),
semantic: empty,
};
}
let raw = Rc::new(StreamView::parse_uniform(&stream.inflated));
let paired = delta_pairs.get(&si);
let identical = paired.is_none()
&& topology_streams[si] == stream.inflated
&& semantic_streams[si] == stream.inflated;
let semantic = if identical {
Rc::clone(&raw)
} else {
Rc::new(StreamView::parse_semantic(
&topology_streams[si],
&semantic_streams[si],
scan,
paired,
))
};
StreamParses { raw, semantic }
})
.collect();
ParsedStreams {
per_stream,
semantic_streams,
}
}
pub(crate) fn stream(&self, ordinal: usize) -> &StreamParses {
&self.per_stream[ordinal]
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (usize, &StreamParses)> {
self.per_stream.iter().enumerate()
}
pub(crate) fn semantic_bytes(&self, ordinal: usize) -> &[u8] {
&self.semantic_streams[ordinal]
}
}
#[cfg(test)]
mod tests {
#![allow(unused_imports)]
use std::io::{Cursor, Write};
use flate2::write::ZlibEncoder;
use flate2::Compression;
use cadmpeg_ir::codec::{Codec, CodecEntry, Confidence, DecodeOptions};
use cadmpeg_ir::geometry::{
BlendCrossSection, BlendRadiusLaw, CurveGeometry, PcurveGeometry,
ProceduralCurveDefinition, ProceduralSurfaceDefinition, SurfaceGeometry,
};
use cadmpeg_ir::math::{Point2, Vector3};
use cadmpeg_ir::report::LossCategory;
use cadmpeg_ir::Exactness;
use crate::container;
use crate::parasolid::{self, StreamKind};
use crate::test_support::*;
use crate::NxCodec;
use super::*;
#[test]
fn segment_order_pairs_delta_across_intervening_non_history_stream() {
use crate::parasolid::{Stream, StreamKind};
use std::collections::BTreeSet;
let stream = |kind, schema: Option<&str>, file_offset| Stream {
file_offset,
consumed: 0,
inflated: Vec::new(),
kind,
schema: schema.map(str::to_string),
};
let streams = vec![
stream(StreamKind::Partition, Some("SCH_A"), 10),
stream(StreamKind::Preview, None, 20),
stream(StreamKind::Deltas, Some("SCH_A"), 30),
stream(StreamKind::Partition, Some("SCH_B"), 40),
stream(StreamKind::Deltas, Some("SCH_A"), 50),
stream(StreamKind::Deltas, Some("SCH_B"), 60),
];
let eligible = BTreeSet::from([2usize, 5]);
assert_eq!(
super::pair_stream_indices(&streams, Some(&eligible)),
std::collections::BTreeMap::from([(0, vec![2]), (3, vec![5])])
);
}
}