cadmpeg-ir 0.6.2

CAD data model, validation, diffing, and codec API for cadmpeg.
// SPDX-License-Identifier: Apache-2.0

use crate::eval::model_curve_differential_by_id;
use crate::eval::model_curve_parameter_near_point_with_tolerance;
use crate::eval::model_curve_point_by_id;
use crate::geometry::Curve;
use crate::geometry::CurveGeometry;
use crate::geometry::ProceduralCurve;
use crate::geometry::ProceduralCurveDefinition;
use crate::geometry::SolvedCurveGeometry;
use crate::ids::CurveId;
use crate::math::Point3;
use crate::math::Vector3;
use crate::scalar::NonNegativeLength;
use crate::CadIr;

use crate::ids::ProceduralCurveId;

#[test]
fn cached_subset_retains_local_parameters_for_points_derivatives_and_inversion() {
    let source = CurveId::mint("test:model:curve#source").unwrap();
    let subset = CurveId::mint("test:model:curve#subset").unwrap();
    let line = CurveGeometry::Solved(SolvedCurveGeometry::Line(
        crate::geometry::analytic::LineCurve::try_new(
            Point3::new(0.0, 0.0, 0.0),
            Vector3::new(1.0, 0.0, 0.0),
        )
        .unwrap(),
    ));
    for sense in [true, false] {
        let mut ir = CadIr::empty();
        for id in [&source, &subset] {
            ir.model.curves.push(Curve {
                parameter_range: None,
                id: id.clone(),
                geometry: line.clone(),
                source_object: None,
            });
        }
        ir.model
            .add_procedural_curve(
                &subset,
                ProceduralCurve::new(
                    ProceduralCurveId::mint("test:model:procedural-curve#subset").unwrap(),
                    ProceduralCurveDefinition::Subset(
                        crate::geometry::curve_payloads::SubsetCurveConstruction::try_new(
                            source.clone(),
                            [2.0, 5.0],
                            sense,
                            None,
                        )
                        .unwrap(),
                    ),
                ),
            )
            .unwrap();
        let index = crate::index::ModelIndex::build(&ir);
        let expected = Point3::new(if sense { 3.0 } else { 4.0 }, 0.0, 0.0);
        assert_eq!(
            model_curve_point_by_id(
                crate::eval::admission::EvaluationAdmission::Standard,
                &index,
                &subset,
                1.0
            )
            .map(crate::features::FinitePoint3::get),
            Ok(expected)
        );
        let differential = model_curve_differential_by_id(
            crate::eval::admission::EvaluationAdmission::Standard,
            &index,
            &subset,
            1.0,
        )
        .unwrap();
        assert_eq!(differential.point, expected);
        assert_eq!(
            differential.tangent.unwrap().get(),
            Vector3::new(if sense { 1.0 } else { -1.0 }, 0.0, 0.0)
        );
        assert_eq!(
            differential.acceleration.unwrap().get(),
            Vector3::new(0.0, 0.0, 0.0)
        );
        assert_eq!(
            model_curve_parameter_near_point_with_tolerance(
                &cadmpeg_test_support::service_decode_context(),
                &index,
                &subset,
                expected,
                1.0,
                NonNegativeLength::ZERO,
            )
            .expect("find the model curve parameter near the point")
            .map(crate::scalar::FiniteReal::get),
            Some(1.0),
        );
        assert_eq!(
            model_curve_point_by_id(
                crate::eval::admission::EvaluationAdmission::Standard,
                &index,
                &subset,
                4.0
            ),
            Err(crate::eval::EvaluationFailure::NoValue)
        );
    }
}

#[test]
fn subset_curve_over_wide_interval_maps_finite_local_parameter() {
    let source = CurveId::mint("test:model:curve#wide-source").unwrap();
    let subset = CurveId::mint("test:model:curve#wide-subset").unwrap();
    let mut ir = CadIr::empty();
    for id in [&source, &subset] {
        ir.model.curves.push(Curve {
            parameter_range: None,
            id: id.clone(),
            geometry: CurveGeometry::Solved(SolvedCurveGeometry::Line(
                crate::geometry::analytic::LineCurve::try_new(
                    Point3::new(0.0, 0.0, 0.0),
                    Vector3::new(1.0, 0.0, 0.0),
                )
                .unwrap(),
            )),
            source_object: None,
        });
    }
    ir.model
        .add_procedural_curve(
            &subset,
            ProceduralCurve::new(
                ProceduralCurveId::mint("test:model:procedural-curve#wide-subset").unwrap(),
                ProceduralCurveDefinition::Subset(
                    crate::geometry::curve_payloads::SubsetCurveConstruction::try_new(
                        source,
                        [-f64::MAX, f64::MAX],
                        true,
                        None,
                    )
                    .unwrap(),
                ),
            ),
        )
        .unwrap();
    let index = crate::index::ModelIndex::build(&ir);
    assert_eq!(
        model_curve_point_by_id(
            crate::eval::admission::EvaluationAdmission::Standard,
            &index,
            &subset,
            f64::MAX
        )
        .map(crate::features::FinitePoint3::get),
        Ok(Point3::new(0.0, 0.0, 0.0))
    );
}

#[test]
fn model_differential_refusal_stops_later_derivatives() {
    use crate::eval::EvaluationFailure;
    use crate::features::{FinitePoint3, FiniteVector3};
    use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
    for tangent_refuses in [false, true] {
        let mut policy = DecodePolicy::service();
        policy.limits.max_recursion_depth = 0;
        let arena = DecodeArena::new();
        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
        let first = ctx
            .enter_nested_limit("test model derivative")
            .expect_err("zero depth refuses");
        let acceleration_called = std::cell::Cell::new(false);
        let result = crate::eval::differential_at(
            Ok(FinitePoint3::ZERO),
            || {
                if tangent_refuses {
                    Err(EvaluationFailure::ResourceLimit(first))
                } else {
                    Ok(FiniteVector3::ZERO)
                }
            },
            || {
                acceleration_called.set(true);
                Err(EvaluationFailure::ResourceLimit(first))
            },
        );
        let Err(failure) = result else {
            panic!("a derivative refusal must leave the model evaluation");
        };
        assert_eq!(failure, EvaluationFailure::ResourceLimit(first));
        assert_eq!(acceleration_called.get(), !tangent_refuses);
        assert!(
            matches!(ctx.finish_session(), Err(cadmpeg_core::CodecError::ResourceLimit(sticky)) if sticky == first)
        );
    }
}