cadmpeg-codec-step 0.5.3

Read and write ISO 10303-21 STEP Part 21 files for AP203, AP214, and AP242.
Documentation
// SPDX-License-Identifier: Apache-2.0
//! Part 21 anchor, local-reference, and value-instance tests.

#![allow(clippy::unwrap_used)]
#![allow(clippy::default_trait_access)]
#![allow(unused_imports)]

use std::fmt::Write as _;
use std::io::Cursor;

use cadmpeg_core::decode::{DecodeMode, InspectOptions};
use cadmpeg_ir::codec::{Codec, CodecBackend, Confidence, DecodeOptions};
use cadmpeg_ir::eval::{
    model_curve_point_by_id, model_surface_partials_by_id, model_surface_point_by_id, pcurve_uv,
};
use cadmpeg_ir::examples::unit_cube;
use cadmpeg_ir::geometry::{
    Curve, CurveGeometry, NurbsCurve, NurbsSurface, PcurveGeometry, Surface, SurfaceGeometry,
};
use cadmpeg_ir::ids::{CurveId, ProceduralCurveId, SurfaceId};
use cadmpeg_ir::index::ModelIndex;
use cadmpeg_ir::math::{Point2, Point3, Vector3};
use cadmpeg_ir::transform::Transform;
use cadmpeg_ir::units::{LengthUnit, Units};
use cadmpeg_ir::CadIr;
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipWriter};

use crate::ids::StepIdentity;
use crate::test_support::{decode_inline, export};
use crate::{
    write_step, StepCodec, StepError, StepSchema, StepUnsupportedPolicy, StepWriteOptions,
};

#[test]
fn parser_accepts_external_instance_references_in_edition_three() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<external>=#100;ENDSEC;REFERENCE;#100=<part.step#root>;ENDSEC;DATA;#1=ITEM(<external>);ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("external reference");

    assert!(diagnostics.is_empty());
    assert_eq!(exchange.references[0].name, "#100");
    assert_eq!(exchange.references[0].uri, "part.step#root");
    assert_eq!(
        exchange.anchors[0].value,
        crate::parse::Value::Reference(100)
    );
    assert_eq!(
        exchange.records[&1].partials[0].parameters,
        vec![crate::parse::Value::Reference(100)]
    );
}

#[test]
fn parser_resolves_local_entity_reference_anchors_before_schema_decoding() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<shape>=#2;ENDSEC;REFERENCE;#10=<#shape>;ENDSEC;DATA;#1=ITEM(#10);#2=TARGET();ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("local entity reference");

    assert!(diagnostics.is_empty());
    assert_eq!(
        exchange.records[&1].partials[0].parameters,
        vec![crate::parse::Value::Reference(2)]
    );
}

#[test]
fn parser_resolves_local_value_reference_anchors_and_nulls_invalid_targets() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;3');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<length>=3.;<shape>=#2;ENDSEC;REFERENCE;@10=<#length>;@11=<#shape>;#12=<missing>;#13=<external.step>;ENDSEC;DATA;#1=ITEM(@10,@11,#12,#13);#2=TARGET();ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("local value references");

    assert!(diagnostics.is_empty());
    assert_eq!(
        exchange.records[&1].partials[0].parameters,
        vec![
            crate::parse::Value::Real(3.0),
            crate::parse::Value::Omitted,
            crate::parse::Value::Omitted,
            crate::parse::Value::Omitted,
        ]
    );
}

#[test]
fn parser_checks_edition_three_syntax_before_local_reference_substitution() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<length>=3.;ENDSEC;REFERENCE;@10=<#length>;ENDSEC;DATA;#1=ITEM(@10);ENDSEC;END-ISO-10303-21;";
    let error = crate::parse::parse(source).expect_err("class-2 value occurrence");

    assert!(error
        .to_string()
        .contains("this implementation level forbids value instances"));
}

#[test]
fn parser_resolves_cyclic_local_references_to_null_values() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<cycle>=#10;ENDSEC;REFERENCE;#10=<#cycle>;ENDSEC;DATA;#1=ITEM(#10);ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("cyclic reference");

    assert!(diagnostics.is_empty());
    assert_eq!(exchange.anchors[0].value, crate::parse::Value::Omitted);
    assert_eq!(
        exchange.records[&1].partials[0].parameters,
        vec![crate::parse::Value::Omitted]
    );
}

#[test]
fn parser_requires_numeric_reference_left_hand_sides() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;REFERENCE;<external>=<part.step#root>;ENDSEC;DATA;#1=ITEM();ENDSEC;END-ISO-10303-21;";
    let error = crate::parse::parse(source).expect_err("resource reference name");
    assert!(error.to_string().contains("expected reference name"));
}

#[test]
fn parser_accepts_value_instances_and_express_constants_in_edition_three() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;3');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<constant_entity>=#PI; <constant_value>=@E; <external_value>=@100;ENDSEC;REFERENCE;#200=<part.step#entity>;@100=<part.step#value>;ENDSEC;DATA;#1=ITEM(#PI,@E,@100,#200);ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("edition-3 occurrences");

    assert!(diagnostics.is_empty());
    assert_eq!(
        exchange
            .references
            .iter()
            .map(|entry| entry.name.as_str())
            .collect::<Vec<_>>(),
        ["#200", "@100"]
    );
    assert_eq!(
        exchange.anchors[0].value,
        crate::parse::Value::ConstantEntity("PI".into())
    );
    assert_eq!(
        exchange.anchors[1].value,
        crate::parse::Value::ConstantValue("E".into())
    );
    assert_eq!(
        exchange.records[&1].partials[0].parameters,
        vec![
            crate::parse::Value::ConstantEntity("PI".into()),
            crate::parse::Value::ConstantValue("E".into()),
            crate::parse::Value::ValueReference(100),
            crate::parse::Value::Reference(200),
        ]
    );
}

#[test]
fn parser_retains_anchor_tags_and_resolves_their_references() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;3');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<shape>=#1 {source:<part.step#shape>} {width:@100};ENDSEC;REFERENCE;@100=<part.step#width>;ENDSEC;DATA;#1=ITEM();ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("anchor tags");

    assert!(diagnostics.is_empty());
    assert_eq!(exchange.anchors[0].name, "shape");
    assert_eq!(exchange.anchors[0].tags.len(), 2);
    assert_eq!(exchange.anchors[0].tags[0].name, "source");
    assert_eq!(
        exchange.anchors[0].tags[0].value,
        crate::parse::Value::Resource("part.step#shape".into())
    );
    assert_eq!(exchange.anchors[0].tags[1].name, "width");
    assert_eq!(
        exchange.anchors[0].tags[1].value,
        crate::parse::Value::ValueReference(100)
    );
}

#[test]
fn parser_enforces_anchor_name_and_item_grammar() {
    let cases = [
        ("<123>=1;", "anchor name must contain a non-digit character"),
        ("<>=1;", "anchor name must contain a non-digit character"),
        ("<a>=*;", "invalid anchor item"),
        ("<a>=TYPE(1);", "invalid anchor item"),
        ("<a>=1 {tag:TYPE(1)};", "invalid anchor tag item"),
    ];
    for (entry, message) in cases {
        let source = format!(
            "ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;{entry}ENDSEC;DATA;#1=ITEM();ENDSEC;END-ISO-10303-21;"
        );
        let error = crate::parse::parse(source.as_bytes()).expect_err("invalid anchor entry");
        assert!(
            error.to_string().contains(message),
            "expected {message:?}, got {error}"
        );
    }

    let valid = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<a>=(1,(2));ENDSEC;DATA;#1=ITEM();ENDSEC;END-ISO-10303-21;";
    crate::parse::parse(valid).expect("nested anchor item");
}

#[test]
fn parser_rejects_unresolved_or_colliding_value_instances() {
    let unresolved = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;3');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;DATA;#1=ITEM(@100);ENDSEC;END-ISO-10303-21;";
    let error = crate::parse::parse(unresolved).expect_err("unresolved value instance");
    assert!(error
        .to_string()
        .contains("unresolved value instance reference"));

    let collision = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;3');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;REFERENCE;@100=<part.step#value>;ENDSEC;DATA;#100=ITEM();ENDSEC;END-ISO-10303-21;";
    let error = crate::parse::parse(collision).expect_err("colliding value instance");
    assert!(error
        .to_string()
        .contains("external value instance collides with a DATA instance"));
}

#[test]
fn parser_rejects_edition_three_occurrences_in_historical_data() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'2;1');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;DATA;#1=ITEM(#PI);ENDSEC;END-ISO-10303-21;";
    let error = crate::parse::parse(source).expect_err("historical occurrence name");
    assert!(error
        .to_string()
        .contains("historical implementation levels forbid edition-3 occurrence names"));
}

#[test]
fn parser_resolves_anchor_before_repairing_omitted_entity_names() {
    let source = b"ISO-10303-21;HEADER;FILE_DESCRIPTION(('test'),'4;2');FILE_NAME('','',(''),(''),'','','');FILE_SCHEMA(('AP242'));ENDSEC;ANCHOR;<line_name>='anchored line';ENDSEC;DATA;#1=CARTESIAN_POINT('',(0.,0.,0.));#2=DIRECTION('',(1.,0.,0.));#3=VECTOR('',#2,1.);#4=LINE(<line_name>,#1,#3);ENDSEC;END-ISO-10303-21;";
    let (exchange, diagnostics) = crate::parse::parse(source).expect("anchored line name");

    assert!(diagnostics
        .iter()
        .all(|diagnostic| diagnostic.kind != crate::parse::ParseDiagnosticKind::OmittedEntityName));
    assert_eq!(
        exchange.records[&4].partials[0].parameters,
        vec![
            crate::parse::Value::String(b"anchored line".to_vec()),
            crate::parse::Value::Reference(1),
            crate::parse::Value::Reference(3),
        ]
    );
}