#![allow(clippy::unwrap_used)]
use cadmpeg_test_support::edit;
use crate::examples::unit_cube;
use crate::geometry::SurfaceGeometry;
use crate::ids::UnknownId;
use crate::test_support::make_first_face_surface_unknown;
use crate::unknown::NativeUnknownRecord;
mod decode_copy;
#[test]
fn admitted_nurbs_geometry_copy_refuses_each_nested_vector() {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
use cadmpeg_core::CodecError;
let curve = crate::geometry::CurveGeometry::Solved(
crate::geometry::SolvedCurveGeometry::Nurbs(crate::test_support::nurbs::curve()),
);
let surface = SurfaceGeometry::Solved(crate::geometry::SolvedSurfaceGeometry::Nurbs(
crate::test_support::nurbs::surface(),
));
let arena = DecodeArena::new();
for limit in [0, 4] {
let mut policy = DecodePolicy::service();
policy.limits.max_collection_items = limit;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(matches!(curve.try_clone_for_decode(&ctx, "curve copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "curve copy"));
}
for limit in [0, 4, 8, 10, 12] {
let mut policy = DecodePolicy::service();
policy.limits.max_collection_items = limit;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(matches!(surface.try_clone_for_decode(&ctx, "surface copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "surface copy"));
}
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert_eq!(
curve.try_clone_for_decode(&ctx, "curve copy").unwrap(),
curve
);
assert_eq!(
surface.try_clone_for_decode(&ctx, "surface copy").unwrap(),
surface
);
}
#[test]
fn admitted_sampled_geometry_copy_refuses_both_polygon_lanes_and_polyline_rows() {
use crate::geometry::sampled::{PolygonalSurface, PolylineCurve, PolylineSamples};
use crate::math::Point3;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
use cadmpeg_core::CodecError;
let points = vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
];
let polygon = SurfaceGeometry::Solved(crate::geometry::SolvedSurfaceGeometry::Polygonal(
PolygonalSurface::new(
points.clone(),
vec![[0, 1, 2]],
0.0,
&cadmpeg_test_support::service_decode_context(),
)
.expect("polygonal construction admission")
.unwrap(),
));
let polyline =
crate::geometry::CurveGeometry::Solved(crate::geometry::SolvedCurveGeometry::Polyline(
PolylineCurve::new(
PolylineSamples::Unparameterized {
points: points.try_into().unwrap(),
},
0.0,
&cadmpeg_test_support::service_decode_context(),
)
.expect("polyline construction admission")
.unwrap(),
));
let arena = DecodeArena::new();
for limit in [0, 3] {
let mut policy = DecodePolicy::service();
policy.limits.max_collection_items = limit;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(matches!(polygon.try_clone_for_decode(&ctx, "polygon copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "polygon copy"));
}
let mut policy = DecodePolicy::service();
policy.limits.max_collection_items = 0;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(
matches!(polyline.try_clone_for_decode(&ctx, "polyline copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "polyline copy")
);
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert_eq!(
polygon.try_clone_for_decode(&ctx, "polygon copy").unwrap(),
polygon
);
assert_eq!(
polyline
.try_clone_for_decode(&ctx, "polyline copy")
.unwrap(),
polyline
);
}
#[test]
fn admitted_inline_geometry_copy_refuses_node_and_record_text() {
use crate::geometry::{
CompositeCurveSegment, CompositeCurveSegments, CompositeCurveTransition, CurveGeometry,
PlacedCurve, SolvedCurveGeometry,
};
use crate::ids::CurveId;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
use cadmpeg_core::CodecError;
let record = UnknownId::mint("test:geometry:unknown#1").unwrap();
let unknown = CurveGeometry::Solved(SolvedCurveGeometry::Unknown {
record: Some(record),
});
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_retained_bytes = 0;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(matches!(unknown.try_clone_for_decode(&ctx, "unknown copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "unknown copy"));
let composite = CurveGeometry::Solved(SolvedCurveGeometry::Composite {
segments: CompositeCurveSegments::try_from(vec![CompositeCurveSegment {
curve: CurveId::mint("test:geometry:curve#1").unwrap(),
same_sense: true,
transition: CompositeCurveTransition::Continuous,
}])
.unwrap(),
self_intersect: None,
});
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(
matches!(composite.try_clone_for_decode(&ctx, "composite copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "composite copy")
);
let transformed = CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Nurbs(
crate::test_support::nurbs::curve(),
)),
crate::transform::Transform::identity(),
)
.unwrap(),
));
let mut policy = DecodePolicy::service();
policy.limits.max_collection_items = 6;
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert!(
matches!(transformed.try_clone_for_decode(&ctx, "inline copy"),
Err(CodecError::ResourceLimit(resource)) if resource.operation == "inline copy")
);
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
assert_eq!(
unknown.try_clone_for_decode(&ctx, "unknown copy").unwrap(),
unknown
);
assert_eq!(
composite
.try_clone_for_decode(&ctx, "composite copy")
.unwrap(),
composite
);
assert_eq!(
transformed
.try_clone_for_decode(&ctx, "inline copy")
.unwrap(),
transformed
);
}
#[test]
fn numerical_audit_large_finite_axis_keeps_an_orthogonal_reference() {
let axis = crate::math::Vector3::new(f64::MAX, f64::MAX, 0.0);
let reference = crate::geometry::derive_reference_direction(axis);
assert_eq!(reference, crate::math::Vector3::new(0.0, 0.0, 1.0));
assert_eq!(axis.dot(reference), 0.0);
}
#[test]
fn unknown_surface_json_round_trips() {
let mut ir = unit_cube().expect("valid unit cube fixture");
let rec = UnknownId::mint("synthetic:cube:unknown#0").expect("valid identity");
ir.set_native_unknowns(
&cadmpeg_test_support::service_decode_context(),
"synthetic",
&[NativeUnknownRecord {
id: rec.clone(),
links: Vec::new(),
}],
)
.unwrap();
make_first_face_surface_unknown(&mut ir, Some(rec));
let json = ir.to_canonical_json().unwrap();
let parsed = crate::CadIr::from_json(&json).unwrap();
assert_eq!(parsed, ir, "round-trip must preserve the unknown surface");
}
#[test]
fn ordered_pcurve_uses_round_trip_with_isoparametric_state() {
let uses = vec![
crate::topology::PcurveUse {
pcurve: crate::ids::PcurveId::mint("test:model:pcurve#first").expect("valid identity"),
isoparametric: Some(true),
parameter_range: None,
},
crate::topology::PcurveUse {
pcurve: crate::ids::PcurveId::mint("test:model:pcurve#second").expect("valid identity"),
isoparametric: Some(false),
parameter_range: Some(
crate::geometry::DirectedParameterRange::new([0.0, 1.0]).unwrap(),
),
},
];
let json = serde_json::to_string(&uses).unwrap();
assert_eq!(
serde_json::from_str::<Vec<crate::topology::PcurveUse>>(&json).unwrap(),
uses
);
}
#[test]
fn support_side_rejects_orphan_legacy_parameter_range() {
let wire = serde_json::json!({
"surface": null,
"pcurve": null,
"pcurve_parameter_range": [0.0, 1.0],
});
assert!(serde_json::from_value::<crate::geometry::IntcurveSupportSide>(wire).is_err());
}
#[test]
fn the_g2_full_support_is_one_nested_key_or_absent() {
let shape = crate::geometry::G2BlendFirstShape::Full {
support: Some(crate::geometry::G2BlendFullSupport {
surface: crate::ids::SurfaceId::mint("test:model:surface#support")
.expect("valid identity"),
tolerance: crate::geometry::FitTolerance::try_new(0.02).unwrap(),
}),
};
let value = serde_json::to_value(&shape).unwrap();
assert_eq!(
value,
serde_json::json!({
"kind": "full",
"support": {
"surface": "test:model:surface#support",
"tolerance": 0.02
}
})
);
assert_eq!(
serde_json::from_value::<crate::geometry::G2BlendFirstShape>(value.clone()).unwrap(),
shape
);
let absent = serde_json::from_value::<crate::geometry::G2BlendFirstShape>(
serde_json::json!({"kind": "full"}),
)
.unwrap();
assert_eq!(
absent,
crate::geometry::G2BlendFirstShape::Full { support: None }
);
let mut half = value.clone();
half["support"]
.as_object_mut()
.expect("a support object")
.remove("tolerance");
let error = serde_json::from_value::<crate::geometry::G2BlendFirstShape>(half)
.unwrap_err()
.to_string();
assert!(error.contains("tolerance"), "{error}");
let mut bogus = value;
bogus["support"]["zz_bogus"] = serde_json::json!(1);
let error = serde_json::from_value::<crate::geometry::G2BlendFirstShape>(bogus)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct RevisionCompoundLoftDirectionWireTest {
direction: crate::geometry::CompoundLoftDirection,
}
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct VariableBlendShapeWireTest {
radii: crate::geometry::VariableBlendRadii,
u_range: [f64; 2],
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_v_lower"
)]
v_lower: Option<f64>,
}
fn variable_blend_value(discriminator: i64) -> crate::geometry::VariableBlendValue {
crate::geometry::VariableBlendValue {
modern_flag: false,
calibrated: 0,
payload: crate::geometry::VariableBlendValuePayload::TwoEnds {
discriminator,
parameters: [0.0, 1.0],
radii: [1.0, 2.0],
},
}
}
#[test]
fn the_variable_blend_radii_are_one_nested_tagged_object() {
let value = VariableBlendShapeWireTest {
radii: crate::geometry::VariableBlendRadii::Two {
first: variable_blend_value(0),
second: variable_blend_value(1),
},
u_range: [-1.0, 2.0],
v_lower: Some(-0.5),
};
let wire = serde_json::to_value(&value).unwrap();
assert_eq!(wire["radii"]["kind"], "two");
assert_eq!(wire["radii"]["first"]["payload"]["kind"], "two_ends");
assert_eq!(wire["radii"]["first"]["payload"]["discriminator"], 0);
assert_eq!(wire["radii"]["second"]["payload"]["kind"], "two_ends");
assert_eq!(wire["radii"]["second"]["payload"]["discriminator"], 1);
assert_eq!(wire["u_range"], serde_json::json!([-1.0, 2.0]));
assert_eq!(wire["v_lower"], -0.5);
assert_eq!(
serde_json::from_value::<VariableBlendShapeWireTest>(wire).unwrap(),
value
);
}
#[test]
fn a_single_variable_blend_radius_has_no_second_law_key() {
let single = VariableBlendShapeWireTest {
radii: crate::geometry::VariableBlendRadii::Single {
value: variable_blend_value(0),
},
u_range: [-1.0, 2.0],
v_lower: None,
};
let base = serde_json::to_value(&single).unwrap();
assert_eq!(base["radii"]["kind"], "single");
assert!(base["radii"].get("second").is_none());
let mut second_beside_single = base.clone();
second_beside_single["radii"]["second"] =
serde_json::to_value(variable_blend_value(1)).unwrap();
let error = serde_json::from_value::<VariableBlendShapeWireTest>(second_beside_single)
.unwrap_err()
.to_string();
assert!(error.contains("second"), "{error}");
let mut bogus = base.clone();
bogus["radii"]["zz_bogus"] = serde_json::json!(1);
let error = serde_json::from_value::<VariableBlendShapeWireTest>(bogus)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
assert!(base.get("v_lower").is_none());
let mut wire = base;
wire["u_range"] = serde_json::json!([-1.0, null]);
assert!(serde_json::from_value::<VariableBlendShapeWireTest>(wire).is_err());
}
#[test]
fn an_absent_blend_tangent_is_spelled_null_and_no_magic_value() {
use crate::geometry::VariableBlendInterpolationPoint;
use crate::math::{Point3, Vector3};
let point = VariableBlendInterpolationPoint {
parameter: 0.25,
radius: 2.0,
tangents: [None, Some(1.0e37)],
location: Point3::new(0.0, 0.0, 0.0),
normal: Vector3::new(0.0, 0.0, 1.0),
};
let wire = serde_json::to_value(&point).unwrap();
assert_eq!(wire["tangents"], serde_json::json!([null, 1.0e37]));
assert_eq!(
serde_json::from_value::<VariableBlendInterpolationPoint>(wire).unwrap(),
point
);
}
fn empty_loft_subdata() -> crate::geometry::LoftSubdata {
crate::geometry::LoftSubdata::type_211([1, 0], [0.0, 1.0])
}
#[test]
fn loft_subdata_derives_counts_and_refuses_a_ragged_table() {
let table = crate::geometry::LoftSubdata::table(
7,
vec![
crate::geometry::LoftSubdataRow {
parameters: [0.0, 1.0],
columns: vec![[2.0, 3.0]],
extra: None,
},
crate::geometry::LoftSubdataRow {
parameters: [4.0, 5.0],
columns: vec![[6.0, 7.0]],
extra: Some([8.0, 9.0]),
},
],
)
.unwrap();
assert_eq!(table.type_code(), 7);
assert_eq!(table.row_count(), 2);
assert_eq!(table.column_count(), 1);
assert!(crate::geometry::LoftSubdata::table(
9,
vec![
crate::geometry::LoftSubdataRow {
parameters: [0.0, 1.0],
columns: Vec::new(),
extra: None,
},
crate::geometry::LoftSubdataRow {
parameters: [2.0, 3.0],
columns: vec![[4.0, 5.0]],
extra: None,
},
],
)
.is_none());
}
#[test]
fn loft_subdata_type_211_preserves_headers_independent_of_payload_size() {
let table = crate::geometry::LoftSubdata::type_211([4, 0], [2.0, 3.0]);
assert_eq!(table.row_count(), 4);
assert_eq!(table.column_count(), 0);
assert_eq!(
serde_json::from_value::<crate::geometry::LoftSubdata>(
serde_json::to_value(&table).unwrap()
)
.unwrap(),
table
);
}
#[test]
fn a_loft_member_form_states_its_kind_and_carries_only_its_own_keys() {
let support = crate::geometry::LoftProfileMember {
profile: crate::geometry::LoftPathCurve {
id: crate::ids::CurveId::mint("test:model:curve#loft").expect("valid identity"),
endpoints: Some([Some(0.0), Some(1.0)]),
},
form: crate::geometry::LoftMemberForm::Support {
type_code: 3,
surface: Some(
crate::ids::SurfaceId::mint("test:model:surface#loft").expect("valid identity"),
),
support_bounds: [Some(-1.0), Some(1.0), None, None],
pcurve: None,
first_flag: true,
asm_extension: Some(-1),
subdata: empty_loft_subdata(),
direction: None,
},
};
let wire = serde_json::to_value(&support).unwrap();
assert!(wire.get("type_code").is_none());
assert_eq!(wire["form"]["kind"], "support");
assert_eq!(wire["form"]["type_code"], 3);
assert_eq!(wire["form"]["surface"], "test:model:surface#loft");
assert_eq!(wire["form"]["first_flag"], true);
assert!(wire["form"].get("secondary_pcurve").is_none());
assert_eq!(
serde_json::from_value::<crate::geometry::LoftProfileMember>(wire.clone()).unwrap(),
support
);
let pair = crate::geometry::LoftProfileMember {
profile: crate::geometry::LoftPathCurve {
id: crate::ids::CurveId::mint("test:model:curve#loft").expect("valid identity"),
endpoints: Some([None, None]),
},
form: crate::geometry::LoftMemberForm::PcurvePair {
pcurve: None,
secondary_pcurve: None,
asm_extension: None,
subdata: empty_loft_subdata(),
direction: None,
},
};
let mut pair_wire = serde_json::to_value(&pair).unwrap();
assert_eq!(pair_wire["form"]["kind"], "pcurve_pair");
assert!(pair_wire["form"].get("type_code").is_none());
assert!(pair_wire["form"].get("support_bounds").is_none());
assert_eq!(
serde_json::from_value::<crate::geometry::LoftProfileMember>(pair_wire.clone()).unwrap(),
pair
);
pair_wire["form"]["surface"] = serde_json::json!("test:model:surface#conflict");
let error = serde_json::from_value::<crate::geometry::LoftProfileMember>(pair_wire)
.unwrap_err()
.to_string();
assert!(error.contains("surface"), "{error}");
let mut support_wire = wire.clone();
support_wire["form"]["secondary_pcurve"] = serde_json::json!({
"kind": "line",
"origin": {"u": 0.0, "v": 0.0},
"direction": {"u": 1.0, "v": 0.0},
});
let error = serde_json::from_value::<crate::geometry::LoftProfileMember>(support_wire)
.unwrap_err()
.to_string();
assert!(error.contains("secondary_pcurve"), "{error}");
let mut without_flag = wire;
without_flag["form"]
.as_object_mut()
.unwrap()
.remove("first_flag");
let error = serde_json::from_value::<crate::geometry::LoftProfileMember>(without_flag)
.unwrap_err()
.to_string();
assert!(error.contains("first_flag"), "{error}");
}
#[test]
fn law_edge_keeps_its_flat_curve_and_endpoints_wire_shape() {
let expression = crate::geometry::LawExpression::Edge {
curve: crate::geometry::LoftPathCurve {
id: crate::ids::CurveId::mint("test:model:curve#law").expect("valid identity"),
endpoints: Some([None, Some(2.0)]),
},
parameters: [-1.0, 3.0],
};
let wire = serde_json::to_value(&expression).unwrap();
assert_eq!(wire["kind"], "edge");
assert_eq!(wire["curve"], "test:model:curve#law");
assert_eq!(wire["endpoints"], serde_json::json!([null, 2.0]));
assert_eq!(
serde_json::from_value::<crate::geometry::LawExpression>(wire).unwrap(),
expression
);
}
#[test]
fn a_law_formula_names_its_variant_with_a_tag() {
let null = crate::geometry::LawFormula::Null {};
let null_wire = serde_json::to_value(&null).unwrap();
assert_eq!(null_wire, serde_json::json!({ "kind": "null" }));
assert_eq!(
serde_json::from_value::<crate::geometry::LawFormula>(null_wire).unwrap(),
null
);
let named = crate::geometry::LawFormula::Named {
name: cadmpeg_core::nonblank_literal!("distance-law"),
variables: vec![crate::geometry::LawExpression::Double { value: 2.0 }],
};
let named_wire = serde_json::to_value(&named).unwrap();
assert_eq!(
named_wire,
serde_json::json!({
"kind": "named",
"name": "distance-law",
"variables": [{ "kind": "double", "value": 2.0 }]
})
);
assert_eq!(
serde_json::from_value::<crate::geometry::LawFormula>(named_wire).unwrap(),
named
);
let error = serde_json::from_value::<crate::geometry::LawFormula>(serde_json::json!({
"kind": "null",
"variables": []
}))
.unwrap_err()
.to_string();
assert!(error.contains("variables"), "{error}");
let named = serde_json::from_value::<crate::geometry::LawFormula>(serde_json::json!({
"kind": "named",
"name": "a_named_law",
"variables": []
}))
.unwrap();
assert!(named.variables().is_empty());
}
#[test]
fn a_law_formula_name_refuses_a_blank_string() {
for blank in ["", " ", "\t"] {
let error = serde_json::from_value::<crate::geometry::LawFormula>(serde_json::json!({
"kind": "named",
"name": blank,
"variables": []
}))
.unwrap_err()
.to_string();
assert!(error.contains("blank"), "{error}");
}
}
fn ranged_spring_definition() -> crate::geometry::ProceduralCurveDefinition {
crate::geometry::ProceduralCurveDefinition::Spring(
crate::geometry::curve_payloads::SpringCurvePayload::try_new(
crate::geometry::SpringLayout::ContextFirst {
supports: [
crate::geometry::SpringSupport::Ranges([[0.0, 1.0], [2.0, 3.0]]),
crate::geometry::SpringSupport::Ranges([[4.0, 5.0], [6.0, 7.0]]),
],
first_pcurve: Box::new(crate::geometry::SpringPcurve::Range([8.0, 9.0])),
second_pcurve: None,
parameter_range: [-1.0, 2.0],
discontinuities: [Vec::new(), Vec::new(), Vec::new()],
discontinuity_flag: true,
cache: None,
},
4,
)
.unwrap(),
)
}
#[test]
fn the_spring_layout_is_one_nested_tagged_object() {
let definition = ranged_spring_definition();
let wire = serde_json::to_value(&definition).unwrap();
assert_eq!(wire["kind"], "spring");
assert!(wire.get("context").is_none());
assert!(wire.get("surface_parameter_ranges").is_none());
assert_eq!(wire["layout"]["kind"], "context_first");
assert_eq!(
wire["layout"]["supports"][0],
serde_json::json!({"kind": "ranges", "value": [[0.0, 1.0], [2.0, 3.0]]})
);
assert_eq!(
wire["layout"]["first_pcurve"],
serde_json::json!({"kind": "range", "value": [8.0, 9.0]})
);
assert!(wire["layout"].get("form").is_none());
assert_eq!(
serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(wire).unwrap(),
definition
);
}
#[test]
fn a_spring_support_side_states_one_carrier_and_no_other_key() {
let mut split = serde_json::to_value(ranged_spring_definition()).unwrap();
split["layout"]["supports"][0]["kind"] = serde_json::json!("surface");
let error =
serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(split).unwrap_err();
assert!(error.to_string().contains("string"), "{error}");
let mut cache_keys = serde_json::to_value(ranged_spring_definition()).unwrap();
cache_keys["layout"]["form"] = serde_json::json!({});
let error = serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(cache_keys)
.unwrap_err();
assert!(error.to_string().contains("form"), "{error}");
let mut bogus = serde_json::to_value(ranged_spring_definition()).unwrap();
bogus["layout"]["zz_bogus"] = serde_json::json!(1);
let error =
serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(bogus).unwrap_err();
assert!(error.to_string().contains("zz_bogus"), "{error}");
}
#[test]
fn projection_role_keeps_the_native_string_wire_shape() {
let tail = crate::geometry::ProjectionTail::Ranged {
flag: true,
parameter_range: [-1.0, 2.0],
role: crate::geometry::ProjectionRole::Surf2,
};
let wire = serde_json::to_value(&tail).unwrap();
assert_eq!(wire["role"], "surf2");
assert_eq!(
serde_json::from_value::<crate::geometry::ProjectionTail>(wire).unwrap(),
tail
);
let error = serde_json::from_value::<crate::geometry::ProjectionTail>(serde_json::json!({
"kind": "ranged",
"flag": true,
"parameter_range": [-1.0, 2.0],
"role": "other"
}))
.unwrap_err();
assert!(error.to_string().contains("projection role field"));
}
#[test]
fn vector_offset_roles_are_two_named_keys_of_one_nested_object() {
let definition = crate::geometry::ProceduralCurveDefinition::VectorOffset(
crate::geometry::curve_payloads::VectorOffsetCurveConstruction::try_new(
crate::ids::CurveId::mint("test:model:curve#source").expect("valid identity"),
[-1.0, 2.0],
crate::math::Vector3::new(3.0, 4.0, 5.0),
crate::geometry::VectorOffsetRoles {
source: 7,
offset: 9,
},
None,
)
.unwrap(),
);
let wire = serde_json::to_value(&definition).unwrap();
assert_eq!(wire["roles"], serde_json::json!({"source": 7, "offset": 9}));
assert!(wire.get("labels").is_none());
assert!(wire.get("codes").is_none());
assert_eq!(
serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(wire.clone()).unwrap(),
definition
);
let mut labelled = wire.clone();
labelled["roles"]["labels"] = serde_json::json!(["source", "offset"]);
let error = serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(labelled)
.unwrap_err()
.to_string();
assert!(error.contains("labels"), "{error}");
let mut stray = wire;
stray["roles"]["zz_bogus"] = serde_json::json!(1);
let error = serde_json::from_value::<crate::geometry::ProceduralCurveDefinition>(stray)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
#[test]
fn procedural_carrier_serialization_preserves_checked_solved_cache() {
use crate::geometry::{CurveGeometry, SolvedCurveGeometry, SolvedSurfaceGeometry};
let curve = CurveGeometry::Procedural {
construction: "test:model:procedural_curve#0"
.try_into()
.expect("valid identity"),
cache: Some(SolvedCurveGeometry::Degenerate(
crate::geometry::analytic::DegenerateCurve::try_new(crate::math::Point3::new(
1.0, 2.0, 3.0,
))
.unwrap(),
)),
};
let surface = SurfaceGeometry::Procedural {
construction: "test:model:procedural_surface#0"
.try_into()
.expect("valid identity"),
cache: Some(SolvedSurfaceGeometry::Unknown { record: None }),
};
let curve_wire = serde_json::to_value(&curve).unwrap();
let surface_wire = serde_json::to_value(&surface).unwrap();
assert_eq!(
serde_json::from_value::<CurveGeometry>(curve_wire.clone()).unwrap(),
curve
);
assert_eq!(
serde_json::from_value::<SurfaceGeometry>(surface_wire.clone()).unwrap(),
surface
);
assert!(serde_json::from_value::<SolvedCurveGeometry>(curve_wire).is_err());
assert!(serde_json::from_value::<SolvedSurfaceGeometry>(surface_wire).is_err());
}
#[test]
fn a_solved_cache_refuses_a_procedural_carrier_as_an_unknown_variant() {
use crate::geometry::{CurveGeometry, SolvedCurveGeometry, SolvedSurfaceGeometry};
let mut curve = serde_json::json!({
"kind": "procedural",
"construction": "test:model:procedural_curve#0"
});
let mut surface = serde_json::json!({
"kind": "procedural",
"construction": "test:model:procedural_surface#0"
});
for _ in 0..3 {
let error = serde_json::from_value::<SolvedCurveGeometry>(curve.clone())
.unwrap_err()
.to_string();
assert!(error.contains("unknown variant `procedural`"), "{error}");
let error = serde_json::from_value::<SolvedSurfaceGeometry>(surface.clone())
.unwrap_err()
.to_string();
assert!(error.contains("unknown variant `procedural`"), "{error}");
curve = serde_json::json!({
"kind": "transformed",
"basis": curve,
"transform": serde_json::to_value(crate::transform::Transform::default()).unwrap()
});
surface = serde_json::json!({
"kind": "transformed",
"basis": surface,
"transform": serde_json::to_value(crate::transform::Transform::default()).unwrap()
});
}
let solved = CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
crate::geometry::PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Unknown { record: None }),
crate::transform::Transform::default(),
)
.expect("placed curve"),
));
assert_eq!(
serde_json::from_value::<CurveGeometry>(serde_json::to_value(&solved).unwrap()).unwrap(),
solved
);
let solved = SurfaceGeometry::Solved(SolvedSurfaceGeometry::Transformed(
crate::geometry::PlacedSurface::try_new(
Box::new(SolvedSurfaceGeometry::Unknown { record: None }),
crate::transform::Transform::default(),
)
.expect("placed surface"),
));
assert_eq!(
serde_json::from_value::<SurfaceGeometry>(serde_json::to_value(&solved).unwrap()).unwrap(),
solved
);
}
mod compound_components;
mod compound_loft;
mod fit_tolerance;
mod tspline_subtransform;
mod vertex_blend_twists;
mod revision_cache_form;
mod variable_blend_cache;
mod revision_compound_loft;
mod revision_compound_loft_tail;
mod revision_g2_blend;
mod loft_path;
mod loft_profile_member;
mod loft_subdata;
mod surface_curve_family;
mod rolling_ball_jet;
mod rolling_ball_side;
mod variable_blend_secondary_curve;
mod variable_blend_value;
#[test]
fn support_context_admission_preserves_mapping_and_numeric_invariants() {
use {
super::{
DirectedParameterRange, IntcurveSupportContext, IntcurveSupportSide, SupportPcurve,
},
crate::geometry::pcurve::{LinePcurve, PcurveGeometry},
};
let sides = [
IntcurveSupportSide {
surface: None,
pcurve: Some(SupportPcurve::new(
PcurveGeometry::Line(
LinePcurve::try_new(
crate::math::Point2::new(0.0, 0.0),
crate::math::Point2::new(1.0, 0.0),
)
.unwrap(),
),
Some(DirectedParameterRange::new([5.0, 2.0]).unwrap()),
)),
},
IntcurveSupportSide {
surface: None,
pcurve: None,
},
];
let empty = || std::array::from_fn(|_| Vec::new());
let mut context = IntcurveSupportContext::try_new(sides.clone(), [0.0, 1.0], empty()).unwrap();
let original = context.clone();
assert!(IntcurveSupportContext::try_new(sides.clone(), [1.0, 1.0], empty()).is_err());
assert!(IntcurveSupportContext::try_new(sides.clone(), [1.0, 0.0], empty()).is_err());
assert!(IntcurveSupportContext::try_new(sides.clone(), [0.0, f64::INFINITY], empty()).is_err());
assert!(
IntcurveSupportContext::try_new(sides, [0.0, 1.0], [vec![f64::NAN], vec![], vec![]])
.is_err()
);
assert!(edit::replace(&mut context, |previous| {
let sides = previous.sides().clone();
let mut range = previous.parameter_range().endpoints();
let discontinuities = crate::scalar::FiniteReal::raw_lanes(previous.discontinuities());
{
let range: &mut [f64; 2] = &mut range;
*range = [1.0, 1.0];
};
crate::geometry::IntcurveSupportContext::try_new(sides, range, discontinuities)
})
.is_err());
assert_eq!(context, original);
assert!(edit::replace(&mut context, |previous| {
let sides = previous.sides().clone();
let range = previous.parameter_range().endpoints();
let mut discontinuities = crate::scalar::FiniteReal::raw_lanes(previous.discontinuities());
{
let discontinuities: &mut [Vec<f64>; 3] = &mut discontinuities;
discontinuities[1].push(f64::INFINITY);
};
crate::geometry::IntcurveSupportContext::try_new(sides, range, discontinuities)
})
.is_err());
assert_eq!(context, original);
let mut wire = serde_json::to_value(&context).unwrap();
assert_eq!(
serde_json::from_value::<IntcurveSupportContext>(wire.clone()).unwrap(),
context
);
wire["parameter_range"] = serde_json::json!([1.0, 1.0]);
assert!(serde_json::from_value::<IntcurveSupportContext>(wire).is_err());
edit::replace(&mut context, |previous| {
let mut sides = previous.sides().clone();
let mut range = previous.parameter_range().endpoints();
let discontinuities = crate::scalar::FiniteReal::raw_lanes(previous.discontinuities());
{
let sides: &mut [crate::geometry::IntcurveSupportSide; 2] = &mut sides;
let range: &mut [f64; 2] = &mut range;
sides[0].pcurve.as_mut().unwrap().parameter_range = None;
*range = [1.0, 1.0];
};
crate::geometry::IntcurveSupportContext::try_new(sides, range, discontinuities)
})
.unwrap();
let unchanged = context.clone();
assert!(edit::replace(&mut context, |previous| {
let mut sides = previous.sides().clone();
let range = previous.parameter_range().endpoints();
let discontinuities = crate::scalar::FiniteReal::raw_lanes(previous.discontinuities());
{
let sides: &mut [crate::geometry::IntcurveSupportSide; 2] = &mut sides;
sides[0].pcurve.as_mut().unwrap().parameter_range =
Some(DirectedParameterRange::new([5.0, 2.0]).unwrap());
};
crate::geometry::IntcurveSupportContext::try_new(sides, range, discontinuities)
})
.is_err());
assert_eq!(context, unchanged);
}
#[test]
fn composite_curve_requires_a_segment_on_construction_and_serde() {
use super::{
CompositeCurveSegment, CompositeCurveSegments, CompositeCurveTransition, CurveGeometry,
SolvedCurveGeometry,
};
assert!(CompositeCurveSegments::try_from(Vec::new()).is_err());
let segment = CompositeCurveSegment {
curve: crate::ids::CurveId::mint("synthetic:test:curve#child").unwrap(),
same_sense: true,
transition: CompositeCurveTransition::Continuous,
};
let mut segments = CompositeCurveSegments::try_from(vec![segment]).unwrap();
segments[0].same_sense = false;
let curve = CurveGeometry::Solved(SolvedCurveGeometry::Composite {
segments,
self_intersect: None,
});
let wire = serde_json::json!({
"kind": "composite",
"segments": [{"curve": "synthetic:test:curve#child", "same_sense": false, "transition": "continuous"}],
"self_intersect": null,
});
assert_eq!(serde_json::to_value(&curve).unwrap(), wire);
assert_eq!(
serde_json::from_value::<CurveGeometry>(wire.clone()).unwrap(),
curve
);
let mut empty = wire;
empty["segments"] = serde_json::json!([]);
assert!(serde_json::from_value::<CurveGeometry>(empty).is_err());
}
mod offset_coordinate;
mod tolerant_intersection;
mod loft_scale_prefix;
mod helix_payloads;
mod admitted_records;
mod procedural_surface_payloads;
mod procedural_curve_payloads;
mod record_bounds;
mod revolution_payloads;
#[test]
fn the_nested_construction_enums_reject_an_unknown_key_by_name() {
let cases: [(&str, serde_json::Value); 4] = [
(
"G2BlendFirstShape",
serde_json::json!({"kind": "full", "zz_bogus": 1}),
),
(
"DeformableSurfaceData",
serde_json::json!({"kind": "full", "zz_bogus": 1}),
),
(
"LawSurfaceTail",
serde_json::json!({"kind": "full", "zz_bogus": 1}),
),
(
"ProjectionTail",
serde_json::json!({"kind": "ranged", "zz_bogus": 1}),
),
];
for (name, wire) in cases {
let error = match name {
"G2BlendFirstShape" => {
serde_json::from_value::<crate::geometry::G2BlendFirstShape>(wire).unwrap_err()
}
"DeformableSurfaceData" => {
serde_json::from_value::<crate::geometry::DeformableSurfaceData>(wire).unwrap_err()
}
"LawSurfaceTail" => {
serde_json::from_value::<crate::geometry::LawSurfaceTail>(wire).unwrap_err()
}
_ => serde_json::from_value::<crate::geometry::ProjectionTail>(wire).unwrap_err(),
}
.to_string();
assert!(error.contains("zz_bogus"), "{name}: {error}");
}
}
#[test]
fn a_law_surface_full_tail_states_its_solved_cache_contract() {
let tail = crate::geometry::LawSurfaceTail::Full {
cache: crate::geometry::LegacyCache::try_new(0.25).expect("fit tolerance"),
};
let wire = serde_json::to_value(tail.clone()).unwrap();
assert_eq!(
wire,
serde_json::json!({"kind": "full", "cache": {"fit_tolerance": 0.25}})
);
assert_eq!(
serde_json::from_value::<crate::geometry::LawSurfaceTail>(wire).unwrap(),
tail
);
}
#[test]
fn every_payload_free_law_surface_tail_refuses_an_unknown_key() {
for kind in ["full", "historical", "optimal"] {
let wire = serde_json::json!({"kind": kind, "zz_bogus": 1});
let error = serde_json::from_value::<crate::geometry::LawSurfaceTail>(wire)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{kind}: {error}");
}
for tail in [
crate::geometry::LawSurfaceTail::Historical {},
crate::geometry::LawSurfaceTail::Optimal {},
] {
let wire = serde_json::to_value(tail.clone()).unwrap();
assert_eq!(
serde_json::from_value::<crate::geometry::LawSurfaceTail>(wire).unwrap(),
tail
);
}
assert_eq!(
serde_json::to_value(crate::geometry::LawSurfaceTail::<f64>::Historical {}).unwrap(),
serde_json::json!({"kind": "historical"})
);
assert_eq!(
serde_json::to_value(crate::geometry::LawSurfaceTail::<f64>::Optimal {}).unwrap(),
serde_json::json!({"kind": "optimal"})
);
}
#[test]
fn the_skin_inner_count_lives_only_on_the_compact_layout_that_owns_it() {
use crate::geometry::{SkinSurfaceLayout, SkinSurfaceProfile};
let compact = SkinSurfaceLayout::Compact {
inner_count: 3,
curve: "test:model:curve#0".try_into().expect("valid identity"),
subdata: crate::geometry::LoftSubdata::type_211([1, 1], [0.0, 1.0]),
first_tail: 1,
secondary_curve: "test:model:curve#1".try_into().expect("valid identity"),
second_tail: 2,
};
let wire = serde_json::to_value(&compact).unwrap();
assert_eq!(wire["inner_count"], serde_json::json!(3));
assert_eq!(
serde_json::from_value::<SkinSurfaceLayout>(wire).unwrap(),
compact
);
let profiles = SkinSurfaceLayout::Profiles {
profiles: Vec::<SkinSurfaceProfile>::new(),
path: "test:model:curve#2".try_into().expect("valid identity"),
tail: [0, 0],
};
let mut wire = serde_json::to_value(&profiles).unwrap();
assert!(wire.get("inner_count").is_none());
assert_eq!(
serde_json::from_value::<SkinSurfaceLayout>(wire.clone()).unwrap(),
profiles
);
wire["inner_count"] = serde_json::json!(0);
let error = serde_json::from_value::<SkinSurfaceLayout>(wire)
.unwrap_err()
.to_string();
assert!(error.contains("inner_count"), "{error}");
}
#[test]
fn the_shared_math_carriers_reject_an_unknown_key_by_name() {
let vector = serde_json::json!({"x": 1.0, "y": 2.0, "z": 3.0, "zz_bogus": 1});
let error = serde_json::from_value::<crate::math::Vector3>(vector)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
let point = serde_json::json!({"u": 1.0, "v": 2.0, "zz_bogus": 1});
let error = serde_json::from_value::<crate::math::Point2>(point)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
#[test]
fn a_law_expression_states_its_kind_and_carries_only_its_own_keys() {
let null = crate::geometry::LawExpression::Null {};
assert_eq!(
serde_json::to_value(&null).unwrap(),
serde_json::json!({"kind": "null"})
);
assert_eq!(
serde_json::from_value::<crate::geometry::LawExpression>(
serde_json::json!({"kind": "null"})
)
.unwrap(),
null
);
for wire in [
serde_json::json!({"kind": "null", "zz_bogus": 1}),
serde_json::json!({"kind": "text", "value": "x", "zz_bogus": 1}),
] {
let error = serde_json::from_value::<crate::geometry::LawExpression>(wire)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
let formula = serde_json::json!({
"kind": "named",
"name": "law",
"variables": [],
"zz_bogus": 1,
});
let error = serde_json::from_value::<crate::geometry::LawFormula>(formula)
.unwrap_err()
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
#[test]
fn the_ir_scalar_mints_name_no_native_sentinel() {
use crate::geometry::{LoftSubdata, LoftSubdataRow};
use crate::scalar::PositiveI64;
assert!(PositiveI64::new(-1).is_none());
assert!(PositiveI64::new(0).is_none());
assert_eq!(PositiveI64::new(3).as_ref().map(PositiveI64::get), Some(3));
let rows = vec![LoftSubdataRow {
parameters: [0.0, 1.0],
columns: Vec::new(),
extra: None,
}];
assert_eq!(
LoftSubdata::table(211, rows).map(|table| table.type_code()),
Some(211)
);
}
cadmpeg_core::named_optional_field!(deserialize_v_lower, f64, "v_lower");
mod charged_curves;
mod nesting_bound;
mod support_mapping;
mod carrier_interval;