use super::attributes::unknown_record_id;
use super::geometry::is_edge_record;
use super::{id, AsmBrep, Carriers};
use crate::ids::{brep_id, IdFormat};
use crate::sab::Record;
use cadmpeg_ir::geometry::{
CurveGeometry, SolvedCurveGeometry, SolvedSurfaceGeometry, SurfaceGeometry,
};
use cadmpeg_ir::ids::{AttributeId, ProceduralCurveId, ProceduralSurfaceId};
use std::collections::HashMap;
pub enum AnnotationTag {
Record(String),
ProceduralSurface,
ProceduralCurve,
ProceduralSupport,
ProceduralCurveChild,
}
impl AnnotationTag {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Record(name) => name,
Self::ProceduralSurface => "procedural_surface",
Self::ProceduralCurve => "procedural_curve",
Self::ProceduralSupport => "procedural_support",
Self::ProceduralCurveChild => "procedural_curve_child",
}
}
}
pub struct AnnotationRecord {
pub id: String,
pub stream: String,
pub offset: u64,
pub tag: AnnotationTag,
pub derived_fields: Vec<&'static str>,
}
pub(super) fn emit_annotation_records(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
out: &mut AsmBrep,
records: &[Record],
by_index: &HashMap<i64, &Record>,
carriers: &Carriers,
stream: &str,
format: IdFormat,
) -> Result<(), cadmpeg_core::CodecError> {
let mut index_storage = ctx.reserve_scoped(0, "ASM annotation indices")?;
let curve_geometries = index_storage.with_storage(|| {
ctx.collect_hash_map(
out.curves
.iter()
.map(|curve| (curve.id.as_str(), &curve.geometry)),
"ASM annotation curve geometry index",
)
})?;
let surface_geometries = index_storage.with_storage(|| {
ctx.collect_hash_map(
out.surfaces
.iter()
.map(|surface| (surface.id.as_str(), &surface.geometry)),
"ASM annotation surface geometry index",
)
})?;
let ranged_edges = index_storage.with_storage(|| {
ctx.collect_hash_set(
out.edges
.iter()
.filter(|edge| edge.param_range().is_some())
.map(|edge| edge.id.as_str()),
"ASM annotation ranged edges",
)
})?;
let emitted_ids = index_storage.with_storage(|| {
ctx.collect_hash_set(
out.bodies
.iter()
.map(|entity| entity.id.as_str())
.chain(out.regions.iter().map(|entity| entity.id.as_str()))
.chain(out.shells.iter().map(|entity| entity.id.as_str()))
.chain(out.faces.iter().map(|entity| entity.id.as_str()))
.chain(out.loops.iter().map(|entity| entity.id.as_str()))
.chain(out.coedges.iter().map(|entity| entity.id.as_str()))
.chain(out.edges.iter().map(|entity| entity.id.as_str()))
.chain(out.vertices.iter().map(|entity| entity.id.as_str()))
.chain(out.points.iter().map(|entity| entity.id.as_str()))
.chain(out.surfaces.iter().map(|entity| entity.id.as_str()))
.chain(out.curves.iter().map(|entity| entity.id.as_str()))
.chain(out.pcurves.iter().map(|entity| entity.id.as_str())),
"ASM annotation emitted IDs",
)
})?;
let attribute_ids = index_storage.with_storage(|| {
ctx.collect_hash_set(
out.attributes.iter().map(|attribute| attribute.id.as_str()),
"ASM annotation attribute IDs",
)
})?;
let unknown_ids = index_storage.with_storage(|| {
ctx.collect_hash_set(
out.unknowns.iter().map(|unknown| unknown.id().as_str()),
"ASM annotation unknown IDs",
)
})?;
let procedural_ids = index_storage.with_storage(|| {
ctx.collect_hash_set(
out.procedural_surfaces
.iter()
.map(|(_, entity)| entity.id.as_str())
.chain(
out.procedural_curves
.iter()
.map(|(_, entity)| entity.id.as_str()),
),
"ASM annotation procedural IDs",
)
})?;
for record in records {
let entity_id = id(
format,
i64::try_from(record.index).map_err(|_| {
ctx.refuse_codec_limit(
"ASM record index",
9_223_372_036_854_775_807,
cadmpeg_core::decode::u64_from_index(record.index),
)
})?,
)
.into_string();
if emitted_ids.contains(entity_id.as_str()) {
let mut derived_fields = Vec::new();
match record.head() {
"plane"
if matches!(
surface_geometries.get(entity_id.as_str()),
Some(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Plane(_)))
) =>
{
derived_fields.extend(["geometry.normal", "geometry.u_axis"]);
}
"cone"
if matches!(
surface_geometries.get(entity_id.as_str()),
Some(SurfaceGeometry::Solved(
SolvedSurfaceGeometry::Cone(_) | SolvedSurfaceGeometry::Cylinder(_)
))
) =>
{
derived_fields.extend(["geometry.axis", "geometry.ref_direction"]);
}
"sphere"
if matches!(
surface_geometries.get(entity_id.as_str()),
Some(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Sphere(_)))
) =>
{
derived_fields.extend(["geometry.axis", "geometry.ref_direction"]);
}
"torus"
if matches!(
surface_geometries.get(entity_id.as_str()),
Some(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Torus(_)))
) =>
{
derived_fields.extend(["geometry.axis", "geometry.ref_direction"]);
}
"straight"
if matches!(
curve_geometries.get(entity_id.as_str()),
Some(CurveGeometry::Solved(SolvedCurveGeometry::Line(_)))
) =>
{
derived_fields.push("geometry.direction");
}
"ellipse" => match curve_geometries.get(entity_id.as_str()) {
Some(CurveGeometry::Solved(SolvedCurveGeometry::Circle(_))) => {
derived_fields.extend(["geometry.axis", "geometry.ref_direction"]);
}
Some(CurveGeometry::Solved(SolvedCurveGeometry::Ellipse(_))) => {
derived_fields.extend(["geometry.axis", "geometry.major_direction"]);
}
_ => {}
},
_ => {}
}
if is_edge_record(record) && ranged_edges.contains(entity_id.as_str()) {
if let Some(curve) = record
.ref_at(8)
.and_then(|reference| by_index.get(&reference))
{
if curve.head() == "ellipse" {
derived_fields.push("carrier.param_range");
}
}
}
ctx.reserve_vec(&mut out.annotation_records, 1, "ASM annotation records")?;
out.annotation_records.push(AnnotationRecord {
id: entity_id,
stream: ctx.copy_retained_text(stream, "ASM annotation stream")?,
offset: cadmpeg_core::decode::u64_from_index(record.offset),
tag: AnnotationTag::Record(
ctx.copy_retained_text(&record.name, "ASM annotation record name")?,
),
derived_fields,
});
}
let attribute_id = brep_id!(format, AttributeId, "attribute", record.index).into_string();
if attribute_ids.contains(attribute_id.as_str()) {
ctx.reserve_vec(&mut out.annotation_records, 1, "ASM annotation records")?;
out.annotation_records.push(AnnotationRecord {
id: attribute_id,
stream: ctx.copy_retained_text(stream, "ASM annotation stream")?,
offset: cadmpeg_core::decode::u64_from_index(record.offset),
tag: AnnotationTag::Record(
ctx.copy_retained_text(&record.name, "ASM annotation record name")?,
),
derived_fields: Vec::new(),
});
}
let unknown_id = unknown_record_id(ctx, record, format)?;
if unknown_ids.contains(unknown_id.as_str()) {
ctx.reserve_vec(&mut out.annotation_records, 1, "ASM annotation records")?;
out.annotation_records.push(AnnotationRecord {
id: unknown_id.into_string(),
stream: ctx.copy_retained_text(stream, "ASM annotation stream")?,
offset: cadmpeg_core::decode::u64_from_index(record.offset),
tag: AnnotationTag::Record(
ctx.copy_retained_text(&record.name, "ASM annotation record name")?,
),
derived_fields: Vec::new(),
});
}
for (synthetic_id, tag) in [
(
brep_id!(
format,
ProceduralSurfaceId,
"procedural_surface",
record.index
)
.into_string(),
AnnotationTag::ProceduralSurface,
),
(
brep_id!(format, ProceduralCurveId, "procedural_curve", record.index).into_string(),
AnnotationTag::ProceduralCurve,
),
] {
if procedural_ids.contains(synthetic_id.as_str()) {
ctx.reserve_vec(&mut out.annotation_records, 1, "ASM annotation records")?;
out.annotation_records.push(AnnotationRecord {
id: synthetic_id,
stream: ctx.copy_retained_text(stream, "ASM annotation stream")?,
offset: cadmpeg_core::decode::u64_from_index(record.offset),
tag,
derived_fields: Vec::new(),
});
}
}
}
for (index, entity_id, tag) in carriers
.procedural_support_sources
.iter()
.map(|(index, id)| (index, id.as_str(), AnnotationTag::ProceduralSupport))
.chain(
carriers
.procedural_curve_child_sources
.iter()
.map(|(index, id)| (index, id.as_str(), AnnotationTag::ProceduralCurveChild)),
)
{
let record = by_index.get(index).ok_or_else(|| {
cadmpeg_core::CodecError::malformed(format_args!(
"synthetic entity {entity_id} source record {index} is missing"
))
})?;
ctx.reserve_vec(&mut out.annotation_records, 1, "ASM annotation records")?;
out.annotation_records.push(AnnotationRecord {
id: ctx.copy_retained_text(entity_id, "ASM synthetic annotation id")?,
stream: ctx.copy_retained_text(stream, "ASM annotation stream")?,
offset: cadmpeg_core::decode::u64_from_index(record.offset),
tag,
derived_fields: Vec::new(),
});
}
Ok(())
}
#[cfg(test)]
mod tests;