cadmpeg-codec-step 0.6.3

Read and write ISO 10303-21 STEP Part 21 files for AP203, AP214, and AP242.
Documentation
// SPDX-License-Identifier: Apache-2.0
//! Topology cache identity and carrier lookup.

use std::fmt::Write;

use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
use cadmpeg_ir::document::CadIr;

#[test]
fn source_identity_groups_keep_singletons_and_shared_order() {
    use std::collections::BTreeMap;

    let mut groups = BTreeMap::new();
    for source in 0..1_000 {
        super::super::push_source_identity(&mut groups, source, source);
    }
    assert_eq!(groups.len(), 1_000);
    assert_eq!(groups[&0].iter().copied().collect::<Vec<_>>(), [0]);

    let mut groups = BTreeMap::new();
    for identity in [11, 22, 33] {
        super::super::push_source_identity(&mut groups, 7, identity);
    }
    assert_eq!(groups[&7].iter().copied().collect::<Vec<_>>(), [11, 22, 33]);
}

#[test]
fn cached_void_roots_preserve_the_outer_shell_role() {
    let source = b"ISO-10303-21;HEADER;FILE_SCHEMA(('AP242'));ENDSEC;DATA;#1=CLOSED_SHELL('',());#2=CLOSED_SHELL('',());#3=CLOSED_SHELL('',());#10=BREP_WITH_VOIDS('',#1,(#2,#3));#11=BREP_WITH_VOIDS('',#1,(#3,#2));#12=BREP_WITH_VOIDS('',#2,(#1,#3));ENDSEC;END-ISO-10303-21;";
    crate::test_support::with_service_context(source, |source, ctx| {
        let (exchange, _) = crate::parse::parse_inner(source, ctx).expect("authored roots");
        let shells = (1..=3)
            .map(|id| {
                (
                    id,
                    super::super::ShellDef {
                        base: id,
                        forward: true,
                        typed: std::collections::HashSet::new(),
                    },
                )
            })
            .collect::<std::collections::BTreeMap<_, _>>();
        let key = |id| {
            super::super::root_key(
                exchange.records().get(&id).expect("authored root"),
                &exchange,
                &shells,
            )
            .expect("bounded key")
        };
        assert_eq!(key(10), key(11));
        assert_ne!(key(10), key(12));
    });
}

#[test]
fn pcurves_resolve_on_every_shell_of_one_root() {
    let mut shells = String::new();
    let mut records = String::new();
    for id in 1_000..1_100 {
        if !shells.is_empty() {
            shells.push(',');
        }
        write!(shells, "#{id}").expect("write fixture shell");
        writeln!(records, "#{id}=OPEN_SHELL('',(#54));").expect("write fixture shell");
    }
    for id in 10_000..11_000 {
        writeln!(records, "#{id}=CARTESIAN_POINT('',(0.,0.,0.));").expect("write fixture point");
    }
    let source = include_str!("data/tp12_two_admissions.p21")
        .replace(
            "#56=SHELL_BASED_SURFACE_MODEL('',(#55));",
            &format!("#56=SHELL_BASED_SURFACE_MODEL('',({shells}));"),
        )
        .replace(
            "ENDSEC;\nEND-ISO-10303-21;",
            &format!("{records}ENDSEC;\nEND-ISO-10303-21;"),
        );
    let mut ir = CadIr::empty();
    let (exchange, _) =
        crate::test_support::with_service_context(source.as_bytes(), |bytes, ctx| {
            let (exchange, diagnostics) =
                crate::parse::parse_inner(bytes, ctx).expect("authored exchange parses");
            crate::reader::geometry::decode(&exchange, &mut ir, ctx)
                .expect("authored geometry fits the preparation policy");
            (exchange, diagnostics)
        });
    let arena = DecodeArena::new();
    let policy = DecodePolicy::service();
    let (ctx, _) = DecodeContext::from_root_bytes(source.as_bytes(), &arena, &policy)
        .expect("source fits policy");
    let carriers = crate::reader::index::CarrierIndex::from_ir(&ir);
    super::super::decode(&exchange, &mut ir, &carriers, &ctx).expect("all shells decode");
    assert_eq!(ir.model.bodies.len(), 100);
    assert_eq!(ir.model.faces.len(), 100);
    assert_eq!(ir.model.coedges.len(), 400);
    assert_eq!(
        ir.model
            .coedges
            .iter()
            .filter(|coedge| !coedge.pcurves.is_empty())
            .count(),
        200
    );
}

#[test]
fn pcurves_resolve_on_every_independent_root() {
    let mut records = String::new();
    for id in 1_000..1_100 {
        writeln!(records, "#{id}=OPEN_SHELL('',(#54));").expect("write fixture shell");
        writeln!(
            records,
            "#{}=SHELL_BASED_SURFACE_MODEL('',(#{id}));",
            id + 1_000
        )
        .expect("write fixture root");
    }
    for id in 10_000..11_000 {
        writeln!(records, "#{id}=CARTESIAN_POINT('',(0.,0.,0.));").expect("write fixture point");
    }
    let source = include_str!("data/tp12_two_admissions.p21")
        .replace("#56=SHELL_BASED_SURFACE_MODEL('',(#55));", "")
        .replace(
            "ENDSEC;\nEND-ISO-10303-21;",
            &format!("{records}ENDSEC;\nEND-ISO-10303-21;"),
        )
        .replace("(#56)", "(#2000)");
    let mut ir = CadIr::empty();
    let (exchange, _) =
        crate::test_support::with_service_context(source.as_bytes(), |bytes, ctx| {
            let (exchange, diagnostics) =
                crate::parse::parse_inner(bytes, ctx).expect("authored exchange parses");
            crate::reader::geometry::decode(&exchange, &mut ir, ctx)
                .expect("authored geometry fits the preparation policy");
            (exchange, diagnostics)
        });
    let arena = DecodeArena::new();
    let policy = DecodePolicy::service();
    let (ctx, _) = DecodeContext::from_root_bytes(source.as_bytes(), &arena, &policy)
        .expect("source fits policy");
    let carriers = crate::reader::index::CarrierIndex::from_ir(&ir);
    super::super::decode(&exchange, &mut ir, &carriers, &ctx)
        .expect("all independent roots decode");
    assert_eq!(ir.model.bodies.len(), 100);
    assert_eq!(ir.model.faces.len(), 100);
    assert_eq!(ir.model.coedges.len(), 400);
    assert_eq!(
        ir.model
            .coedges
            .iter()
            .filter(|coedge| !coedge.pcurves.is_empty())
            .count(),
        200
    );
}

#[test]
fn terminal_edges_own_one_definition_map_without_cycle_nodes() {
    let records = (100..1100).fold(String::new(), |mut records, id| {
        write!(records, "#{id}=EDGE('',#1,#2);").expect("fixture string");
        records
    });
    let source = format!("ISO-10303-21;HEADER;FILE_SCHEMA(('AP242'));ENDSEC;DATA;#1=DUMMY();#2=DUMMY();{records}ENDSEC;END-ISO-10303-21;");
    let (exchange, _) =
        crate::test_support::with_service_context(source.as_bytes(), crate::parse::parse_inner)
            .expect("bounded edges");
    let policy = DecodePolicy::service();
    crate::test_support::with_policy_context(source.as_bytes(), &policy, |_, ctx| {
        let edges = super::super::edge_defs(&exchange, ctx).expect("one slot per terminal edge");
        assert_eq!(edges.len(), 1000);
        assert!(edges.values().all(|edge| matches!(
            edge.as_ref(),
            super::super::EdgeDef::Bare { start: 1, end: 2 }
        )));
    });
}

#[test]
fn coedges_need_only_their_element_definition_until_used_as_wire_edges() {
    let records = (1000..2000).fold(String::new(), |mut records, id| {
        write!(records, "#{id}=ORIENTED_EDGE('',*,*,#3,.F.);").expect("fixture string");
        records
    });
    let base = format!("ISO-10303-21;HEADER;FILE_SCHEMA(('AP242'));ENDSEC;DATA;#1=DUMMY();#2=DUMMY();#3=EDGE('',#1,#2);{records}");
    for (wire, keys) in [
        ("", vec![3]),
        ("#3000=CONNECTED_EDGE_SET('',(#1000));", vec![3, 1000]),
    ] {
        let source = format!("{base}{wire}ENDSEC;END-ISO-10303-21;");
        let (exchange, _) =
            crate::test_support::with_service_context(source.as_bytes(), crate::parse::parse_inner)
                .expect("bounded coedges");
        let policy = DecodePolicy::service();
        crate::test_support::with_policy_context(source.as_bytes(), &policy, |_, ctx| {
            let edges = super::super::edge_defs(&exchange, ctx)
                .expect("only referenced edge definitions allocate");
            assert_eq!(edges.keys().copied().collect::<Vec<_>>(), keys);
            assert_eq!(edges[&3].vertices(), (1, 2));
            if !wire.is_empty() {
                assert_eq!(edges[&1000].curve_vertices(), (2, 1));
            }
        });
    }
}