use super::{curve, curve_is_supported};
use cadmpeg_ir::geometry::{CurveGeometry, SolvedCurveGeometry};
use cadmpeg_ir::math::{Point3, Vector3};
use cadmpeg_ir::transform::Transform;
#[test]
fn rejects_transform_that_step_operator_cannot_represent() {
let anisotropic = Transform::affine([
[2.0, 0.0, 0.0, 0.0],
[0.0, 3.0, 0.0, 0.0],
[0.0, 0.0, 2.0, 0.0],
])
.expect("affine transform");
let curve = CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
cadmpeg_ir::geometry::PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Line(
cadmpeg_ir::geometry::analytic::LineCurve::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
)
.unwrap(),
)),
anisotropic,
)
.expect("placed curve"),
));
assert!(!curve_is_supported(&curve));
}
#[test]
fn a_transformed_composite_curve_is_an_omitted_carrier() {
let composite = CurveGeometry::Solved(SolvedCurveGeometry::Composite {
segments: vec![cadmpeg_ir::geometry::CompositeCurveSegment {
curve: cadmpeg_ir::ids::CurveId::mint("step:data:curve#1").expect("curve id"),
same_sense: true,
transition: cadmpeg_ir::geometry::CompositeCurveTransition::Continuous,
}]
.try_into()
.expect("nonempty composite segments"),
self_intersect: None,
});
let transformed = CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
cadmpeg_ir::geometry::PlacedCurve::try_new(
Box::new(composite.solved().expect("solved carrier").clone()),
Transform::identity(),
)
.expect("placed curve"),
));
let mut emitter = crate::writer::Emitter::new();
assert!(curve(&mut emitter, transformed.solved().expect("solved carrier")).is_none());
assert!(!curve_is_supported(&transformed));
}
#[test]
fn numerical_audit_pcurve_keeps_large_finite_direction_and_magnitude() {
use cadmpeg_ir::geometry::pcurve::{LinePcurve, PcurveGeometry};
use cadmpeg_ir::math::Point2;
let mut emitter = crate::writer::Emitter::new();
let line = LinePcurve::try_new(Point2::new(0.0, 0.0), Point2::new(1.0e200, 0.0)).unwrap();
assert!(super::pcurve(&mut emitter, &PcurveGeometry::Line(line)).is_some());
let lines = emitter.into_lines().expect("finite reals");
assert!(lines
.iter()
.any(|line| line.contains("DIRECTION('',(1.,0.))")));
assert!(lines.iter().any(|line| line.contains(&format!(
"VECTOR('',#2,{})",
crate::writer::Emitter::new().real(1.0e200)
))));
let mut emitter = crate::writer::Emitter::new();
let line = LinePcurve::try_new(Point2::new(0.0, 0.0), Point2::new(f64::MAX, f64::MAX)).unwrap();
assert!(super::pcurve(&mut emitter, &PcurveGeometry::Line(line)).is_none());
}
#[test]
fn conical_surface_emits_a_positive_half_angle_and_reversed_axis() {
use super::surface;
use cadmpeg_ir::geometry::SolvedSurfaceGeometry;
let cone = cadmpeg_ir::geometry::analytic::ConeSurface::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
5.0,
1.0,
-0.715_584_993_317_674_8,
)
.unwrap();
let mut emitter = crate::writer::Emitter::new();
assert!(surface(&mut emitter, &SolvedSurfaceGeometry::Cone(cone)).is_some());
let lines = emitter.into_lines().expect("finite reals");
let emitted = lines
.iter()
.find(|line| line.contains("CONICAL_SURFACE("))
.expect("the cone emits a CONICAL_SURFACE carrier");
assert!(emitted.ends_with(&format!(
",{},{});",
crate::writer::Emitter::new().real(5.0),
crate::writer::Emitter::new().real(0.715_584_993_317_674_8)
)));
assert!(lines
.iter()
.any(|line| line.contains("DIRECTION('',(-0.,-0.,-1.))")));
}
const ACCEPTED_PLACEMENTS: usize = cadmpeg_ir::geometry::MAX_GEOMETRY_NESTING;
fn placed_plane(
placements: usize,
) -> Result<cadmpeg_ir::geometry::SolvedSurfaceGeometry, &'static str> {
use cadmpeg_ir::geometry::{PlacedSurface, SolvedSurfaceGeometry};
let mut geometry = SolvedSurfaceGeometry::Plane(
cadmpeg_ir::geometry::analytic::PlaneSurface::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
)
.expect("a unit-axis plane"),
);
for _ in 0..placements {
geometry = SolvedSurfaceGeometry::Transformed(PlacedSurface::try_new(
Box::new(geometry),
Transform::identity(),
)?);
}
Ok(geometry)
}
#[test]
fn a_surface_placement_chain_past_the_bound_is_unwritable() {
use super::{emitted_basis, surface, surface_is_supported};
let accepted = placed_plane(ACCEPTED_PLACEMENTS).expect("admitted nesting");
assert!(surface_is_supported(&accepted));
assert!(matches!(
emitted_basis(&accepted),
cadmpeg_ir::geometry::SolvedSurfaceGeometry::Plane(_)
));
let mut emitter = crate::writer::Emitter::new();
assert!(surface(&mut emitter, &accepted).is_some());
assert_eq!(
placed_plane(ACCEPTED_PLACEMENTS + 1),
Err("PlacedSurface.basis nests past the admitted inline basis depth")
);
}
fn placed_line(placements: usize) -> Result<CurveGeometry, &'static str> {
let mut geometry = SolvedCurveGeometry::Line(
cadmpeg_ir::geometry::analytic::LineCurve::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
)
.expect("a unit-direction line"),
);
for _ in 0..placements {
geometry = SolvedCurveGeometry::Transformed(cadmpeg_ir::geometry::PlacedCurve::try_new(
Box::new(geometry),
Transform::identity(),
)?);
}
Ok(CurveGeometry::Solved(geometry))
}
#[test]
fn a_curve_placement_chain_past_the_bound_is_unwritable() {
let accepted = placed_line(ACCEPTED_PLACEMENTS).expect("admitted nesting");
assert!(curve_is_supported(&accepted));
let mut emitter = crate::writer::Emitter::new();
assert!(curve(&mut emitter, accepted.solved().expect("solved carrier")).is_some());
assert_eq!(
placed_line(ACCEPTED_PLACEMENTS + 1),
Err("PlacedCurve.basis nests past the admitted inline basis depth")
);
}
fn placed_pcurve(
placements: usize,
) -> Result<cadmpeg_ir::geometry::pcurve::PcurveGeometry, &'static str> {
use cadmpeg_ir::geometry::pcurve::{LinePcurve, PcurveGeometry, PlacedPcurve};
use cadmpeg_ir::math::Point2;
use cadmpeg_ir::transform::Transform2;
let mut geometry = PcurveGeometry::Line(
LinePcurve::try_new(Point2::new(0.0, 0.0), Point2::new(1.0, 0.0))
.expect("a unit-direction parameter-space line"),
);
for _ in 0..placements {
geometry = PcurveGeometry::Transformed(PlacedPcurve::try_new(
Box::new(geometry),
Transform2::identity(),
)?);
}
Ok(geometry)
}
#[test]
fn a_pcurve_placement_chain_past_the_bound_is_unwritable() {
use super::pcurve;
let accepted = placed_pcurve(ACCEPTED_PLACEMENTS).expect("admitted nesting");
let mut emitter = crate::writer::Emitter::new();
assert!(pcurve(&mut emitter, &accepted).is_some());
assert_eq!(
placed_pcurve(ACCEPTED_PLACEMENTS + 1),
Err("PlacedPcurve.basis nests past the admitted inline basis depth")
);
}
#[test]
fn small_shears_are_not_similarities() {
use super::{Transform, Transform2};
for a in [1e-10, 1., 1e200] {
let shear = Transform::affine([
[a, 0.5 * a, 0., 0.],
[0., 0.75_f64.sqrt() * a, 0., 0.],
[0., 0., a, 0.],
])
.unwrap();
let shear2 = Transform2::affine([[a, 0.5 * a, 0.], [0., 0.75_f64.sqrt() * a, 0.]]).unwrap();
assert!(super::similarity_transform(&shear).is_none());
assert!(!super::similarity_transform_2d(&shear2));
let uniform =
Transform::affine([[a, 0., 0., 0.], [0., a, 0., 0.], [0., 0., a, 0.]]).unwrap();
assert!(super::similarity_transform(&uniform).is_some());
}
}
#[test]
fn a_direction_whose_length_overflows_keeps_its_orientation() {
const EPS_UNIT_COMPONENT: f64 = 1.0e-15;
let mut emitter = crate::writer::Emitter::new();
let direction =
cadmpeg_ir::units::DirectionAboveEpsilon::new(Vector3::new(1.3e308, 1.3e308, 0.0))
.expect("finite nonzero direction");
super::direction(&mut emitter, direction);
let lines = emitter.into_lines().expect("finite reals");
let [line] = lines.as_slice() else {
panic!("one DIRECTION record: {lines:?}");
};
let components = line
.split_once("DIRECTION('',(")
.and_then(|(_, rest)| rest.split_once("))"))
.map(|(components, _)| {
components
.split(',')
.map(|value| value.parse::<f64>().expect("a Part 21 real"))
.collect::<Vec<_>>()
})
.expect("a DIRECTION record");
assert_eq!(components.len(), 3, "{line}");
assert!((components[0] - std::f64::consts::FRAC_1_SQRT_2).abs() < EPS_UNIT_COMPONENT);
assert!((components[1] - std::f64::consts::FRAC_1_SQRT_2).abs() < EPS_UNIT_COMPONENT);
assert_eq!(components[2], 0.0);
}
#[test]
fn a_zero_vector_cannot_be_emitted_as_a_direction() {
assert!(cadmpeg_ir::units::DirectionAboveEpsilon::new(Vector3::new(0.0, 0.0, 0.0)).is_none());
}
fn operator_section(rows: [[f64; 4]; 3]) -> Result<Vec<String>, cadmpeg_core::CodecError> {
let transform = Transform::affine(rows).expect("affine transform");
let mut emitter = crate::writer::Emitter::new();
super::transformation_operator(&mut emitter, transform);
emitter.into_lines()
}
fn assert_operator_is_refused(rows: [[f64; 4]; 3]) {
let written = operator_section(rows);
let Err(error) = written else {
panic!("{written:?}");
};
assert!(
matches!(error, cadmpeg_core::CodecError::NotImplemented(_)),
"{error}"
);
}
#[test]
fn a_transformation_operator_states_a_similarity() {
let lines = operator_section([
[0.0, -2.0, 0.0, 10.0],
[2.0, 0.0, 0.0, 20.0],
[0.0, 0.0, 2.0, 30.0],
])
.expect("a similarity is written");
assert!(
lines
.iter()
.any(|line| line.contains("CARTESIAN_TRANSFORMATION_OPERATOR_3D")),
"{lines:?}"
);
}
#[test]
fn a_transformation_operator_refuses_a_shear() {
assert_operator_is_refused([
[1.0, 0.5, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
]);
}
#[test]
fn a_transformation_operator_refuses_a_non_uniform_scale() {
assert_operator_is_refused([
[2.0, 0.0, 0.0, 0.0],
[0.0, 3.0, 0.0, 0.0],
[0.0, 0.0, 2.0, 0.0],
]);
}
#[test]
fn a_transformation_operator_refuses_a_zero_column() {
assert_operator_is_refused([
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
]);
}
#[test]
fn rigid_placements_are_written_into_their_carriers() {
use cadmpeg_ir::geometry::pcurve::{CirclePcurve, LinePcurve, PcurveGeometry, PlacedPcurve};
use cadmpeg_ir::geometry::SolvedSurfaceGeometry;
use cadmpeg_ir::math::Point2;
use cadmpeg_ir::transform::Transform2;
let emit = |write: &dyn Fn(&mut crate::writer::Emitter) -> Option<crate::writer::Ref>| {
let mut e = crate::writer::Emitter::new();
write(&mut e).expect("carrier is writable");
e.into_lines().expect("finite reals").join("\n")
};
let turn = Transform::affine([
[0.0, -1.0, 0.0, 10.0],
[1.0, 0.0, 0.0, 20.0],
[0.0, 0.0, 1.0, 30.0],
])
.expect("rigid transform");
let circle = SolvedCurveGeometry::Transformed(
cadmpeg_ir::geometry::PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Circle(
cadmpeg_ir::geometry::analytic::CircleCurve::try_new(
Point3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
2.0,
)
.unwrap(),
)),
turn,
)
.expect("placed circle"),
);
let text = emit(&|e| curve(e, &circle));
assert!(!text.contains("CURVE_REPLICA"));
assert!(text.contains("CARTESIAN_POINT('',(10.,21.,30.))"));
assert!(text.contains("DIRECTION('',(0.,1.,0.))"));
assert!(text.contains("CIRCLE("));
let cylinder = SolvedSurfaceGeometry::Transformed(
cadmpeg_ir::geometry::PlacedSurface::try_new(
Box::new(SolvedSurfaceGeometry::Cylinder(
cadmpeg_ir::geometry::analytic::CylinderSurface::try_new(
Point3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
2.0,
)
.unwrap(),
)),
turn,
)
.expect("placed cylinder"),
);
let text = emit(&|e| super::surface(e, &cylinder));
assert!(!text.contains("SURFACE_REPLICA"));
assert!(text.contains("CARTESIAN_POINT('',(10.,21.,30.))"));
assert!(text.contains("CYLINDRICAL_SURFACE("));
let flip = Transform2::affine([[1.0, 0.0, 0.0], [0.0, -1.0, 0.0]]).expect("reflection");
let placed =
|basis| PcurveGeometry::Transformed(PlacedPcurve::try_new(Box::new(basis), flip).unwrap());
let line = placed(PcurveGeometry::Line(
LinePcurve::try_new(Point2::new(1.0, 2.0), Point2::new(0.0, 1.0)).unwrap(),
));
let line = emit(&|e| super::pcurve(e, &line));
assert!(!line.contains("CURVE_REPLICA"));
assert!(line.contains("CARTESIAN_POINT('',(1.,-2.))"));
let circle = placed(PcurveGeometry::Circle(
CirclePcurve::try_new(
Point2::new(1.0, 2.0),
Point2::new(1.0, 0.0),
Point2::new(0.0, 1.0),
3.0,
)
.unwrap(),
));
let circle = emit(&|e| super::pcurve(e, &circle));
assert!(circle.contains("CURVE_REPLICA"));
}