use super::*;
#[test]
fn eval_expression_evaluates_against_history_variables() {
assert_eq!(
eval_expression("width = 10;", &serde_json::Value::Null, "width/2").unwrap(),
5.0
);
let cfg = serde_json::json!({ "values": { "d": 8 } });
assert_eq!(eval_expression("", &cfg, "configurator.d + 2").unwrap(), 10.0);
assert_eq!(eval_expression("", &serde_json::Value::Null, "10 + 5").unwrap(), 15.0);
assert!(eval_expression("", &serde_json::Value::Null, "nope * 2").is_err());
}
#[test]
fn solid_handle_registry_lifecycle() {
let a = make_box_brep(Vec3::new(0.0, 0.0, 0.0), 1.0, 1.0, 1.0).unwrap();
let b = make_box_brep(Vec3::new(0.5, 0.5, 0.5), 1.0, 1.0, 1.0).unwrap();
let start = registered_solid_count();
let ha = register_solid_value(a);
let hb = register_solid_value(b);
assert_ne!(ha, hb);
assert_eq!(registered_solid_count(), start + 2);
let hc = boolean_handle(ha, hb, "union", 1e-6, false).unwrap();
assert_eq!(registered_solid_count(), start + 3);
let mesh = tessellate_handle(hc, 0.01).unwrap();
assert!(!mesh.positions.is_empty() && !mesh.indices.is_empty());
let props =
with_registered_solid(hc, |s| solid_mass_properties(s).map_err(javascript_error))
.unwrap();
assert!(
(props.volume - 1.875).abs() < 1e-6,
"union volume {} != 1.875",
props.volume
);
let identity_translate = [
1.0, 0.0, 0.0, 5.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
let ht = transform_handle(hc, &identity_translate, false).unwrap();
assert_ne!(ht, hc);
assert_eq!(registered_solid_count(), start + 4);
let tprops =
with_registered_solid(ht, |s| solid_mass_properties(s).map_err(javascript_error))
.unwrap();
assert!(
(tprops.volume - 1.875).abs() < 1e-6,
"translated volume {} != 1.875",
tprops.volume
);
let names_json = face_names_handle(hc).unwrap();
let names: Vec<(u64, Option<String>)> = serde_json::from_str(&names_json).unwrap();
assert!(!names.is_empty(), "face_names_handle returned empty map");
free_solid(ht);
free_solid(u32::MAX);
assert_eq!(registered_solid_count(), start + 3);
free_solid(ha);
free_solid(hb);
free_solid(hc);
assert_eq!(registered_solid_count(), start);
free_solid(hc);
assert_eq!(registered_solid_count(), start);
}
#[test]
fn box_is_valid_and_has_exact_volume() {
let mesh = make_box(2.0, 3.0, 4.0);
mesh.validate().unwrap();
assert!((mesh.signed_volume() - 24.0).abs() < 1e-12);
}
#[test]
fn mesh_volume_does_not_require_normals() {
let mut mesh = make_box(2.0, 3.0, 4.0);
mesh.normals.clear();
assert!(
mesh.validate().is_err(),
"full validate() must still require normals"
);
mesh.validate_geometry()
.expect("geometry validation must accept an empty normal buffer");
assert!(
(mesh.signed_volume() - 24.0).abs() < 1e-12,
"normal-less mesh volume must be exact"
);
}
#[test]
fn cylinder_converges_to_analytic_volume() {
let mesh = make_cylinder(2.0, 5.0, 256);
let expected = std::f64::consts::PI * 4.0 * 5.0;
assert!((mesh.signed_volume() - expected).abs() / expected < 2e-4);
}
#[test]
fn export_step_handles_round_trips_a_resident_box() {
let solid = make_box_brep(Vec3::new(0.0, 0.0, 0.0), 4.0, 3.0, 2.0).unwrap();
let handle = register_solid_value(solid);
let step = export_step_handles(&[handle], "Part", "MM", "").unwrap();
assert!(step.contains("ISO-10303-21"), "STEP header present");
let solids = import_step(&step).unwrap();
assert_eq!(solids.len(), 1, "round-trips to one solid");
let volume = solid_mass_properties(&solids[0]).unwrap().volume;
assert!((volume - 24.0).abs() < 1e-6, "imported volume {volume} != 24");
assert!(export_step_handles(&[], "Part", "MM", "").is_err());
free_solid(handle);
assert!(
export_step_handles(&[handle], "Part", "MM", "").is_err(),
"a freed handle must error, not emit stale STEP"
);
}
fn line_curve(a: Vec3, b: Vec3) -> NurbsCurve {
NurbsCurve::new(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![Vec4::from_point(a, 1.0), Vec4::from_point(b, 1.0)],
)
.unwrap()
}
#[test]
fn sketch_profile_display_payload_rectangle() {
let corners = [
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(10.0, 0.0, 0.0),
Vec3::new(10.0, 6.0, 0.0),
Vec3::new(0.0, 6.0, 0.0),
];
let mut curves = Vec::new();
let mut edge_names = Vec::new();
for i in 0..4 {
curves.push(line_curve(corners[i], corners[(i + 1) % 4]));
edge_names.push(Some(format!("Sk:G{}", 10 + i)));
}
let profile = SketchProfile {
origin: Vec3::new(0.0, 0.0, 0.0),
x_axis: Vec3::new(1.0, 0.0, 0.0),
y_axis: Vec3::new(0.0, 1.0, 0.0),
z_axis: Vec3::new(0.0, 0.0, 1.0),
regions: vec![vec![ProfileLoop {
curves,
edge_names,
loop_id: None,
}]],
};
let payload = sketch_profile_display_payload(&profile);
assert_eq!(payload.faces.len(), 1, "one sheet face");
assert_eq!(payload.faces[0].1.as_deref(), Some("Sk:FACE"));
assert_eq!(payload.edges.len(), 4, "four boundary edges");
assert_eq!(
payload.edges[0].1.as_deref(),
Some("Sk:G10"),
"edge carries its sketch geometry name"
);
assert_eq!(payload.vertices.len(), 4, "four corner vertices");
assert!(!payload.mesh.positions.is_empty(), "face mesh has vertices");
assert!(!payload.mesh.indices.is_empty(), "face mesh has triangles");
assert_eq!(payload.mesh.face_ids.len(), payload.mesh.indices.len() / 3);
assert!(payload.mesh.face_ids.iter().all(|&f| f == 0));
let area: f64 = payload
.mesh
.indices
.chunks_exact(3)
.map(|t| {
let p = |i: u32| {
let b = i as usize * 3;
Vec3::new(
payload.mesh.positions[b],
payload.mesh.positions[b + 1],
payload.mesh.positions[b + 2],
)
};
let (a, b, c) = (p(t[0]), p(t[1]), p(t[2]));
b.sub(a).cross(c.sub(a)).length() * 0.5
})
.sum();
assert!((area - 60.0).abs() < 1e-6, "sheet area {area} != 60");
}