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"));
}
#[test]
fn written_chart_reaches_the_point_of_a_signed_carrier() {
use super::WrittenChart;
use cadmpeg_ir::eval::admission::EvaluationAdmission;
use cadmpeg_ir::eval::decode::surface_point_solved;
use cadmpeg_ir::geometry::analytic::{ConeSurface, SphereSurface, TorusSurface};
use cadmpeg_ir::geometry::SolvedSurfaceGeometry;
const EPS_CHART_POINT: f64 = 1.0e-12;
const EPS_PARTIAL_STEP: f64 = 1.0e-6;
let center = Point3::new(1.0, -2.0, 3.0);
let axis = Vector3::new(0.0, 0.0, 1.0);
let reference = Vector3::new(1.0, 0.0, 0.0);
let cases = [
(
SolvedSurfaceGeometry::Torus(
TorusSurface::try_new(center, axis, reference, 6.0, -2.0).unwrap(),
),
SolvedSurfaceGeometry::Torus(
TorusSurface::try_new(center, axis, reference, 6.0, 2.0).unwrap(),
),
false,
),
(
SolvedSurfaceGeometry::Sphere(
SphereSurface::try_new(center, axis, reference, -5.0).unwrap(),
),
SolvedSurfaceGeometry::Sphere(
SphereSurface::try_new(center, axis, reference, 5.0).unwrap(),
),
true,
),
(
SolvedSurfaceGeometry::Cone(
ConeSurface::try_new(center, axis, reference, 4.0, 1.0, -0.5).unwrap(),
),
SolvedSurfaceGeometry::Cone(
ConeSurface::try_new(
center,
Vector3::new(0.0, 0.0, -1.0),
reference,
4.0,
1.0,
0.5,
)
.unwrap(),
),
false,
),
];
let point = |surface: &SolvedSurfaceGeometry, u: f64, v: f64| {
surface_point_solved(EvaluationAdmission::Standard, surface, u, v)
.unwrap()
.get()
};
let normal = |surface: &SolvedSurfaceGeometry, u: f64, v: f64| {
let along_u = point(surface, u + EPS_PARTIAL_STEP, v).vector_from(point(surface, u, v));
let along_v = point(surface, u, v + EPS_PARTIAL_STEP).vector_from(point(surface, u, v));
along_u.cross(along_v)
};
for (carrier, written, reverses) in cases {
let chart = WrittenChart::of(&carrier).expect("a signed carrier has a written chart");
assert_eq!(chart.reverses_normal(), reverses);
for (u, v) in [(0.0, 0.0), (0.3, 0.7), (-1.2, 1.1), (2.5, -0.4)] {
let ([mapped_u, _], _) = chart.range(0, [u, u]);
let ([mapped_v, _], _) = chart.range(1, [v, v]);
let distance = point(&carrier, u, v).distance(point(&written, mapped_u, mapped_v));
assert!(distance < EPS_CHART_POINT, "{distance} at ({u}, {v})");
let agreement = normal(&carrier, u, v).dot(normal(&written, mapped_u, mapped_v));
assert_eq!(agreement < 0.0, reverses, "({u}, {v})");
}
assert_eq!(WrittenChart::of(&written), None);
}
}