#![allow(clippy::unwrap_used)]
#![allow(clippy::default_trait_access)]
#![allow(unused_imports)]
use std::fmt::Write as _;
use std::io::Cursor;
use cadmpeg_core::decode::{DecodeMode, InspectOptions};
use cadmpeg_ir::codec::{Codec, CodecBackend, Confidence, DecodeOptions};
use cadmpeg_ir::eval::{
model_curve_point_by_id, model_surface_partials_by_id, model_surface_point_by_id, pcurve_uv,
};
use cadmpeg_ir::examples::unit_cube;
use cadmpeg_ir::geometry::{
Curve, CurveGeometry, NurbsCurve, NurbsSurface, PcurveGeometry, Surface, SurfaceGeometry,
};
use cadmpeg_ir::ids::{CurveId, ProceduralCurveId, SurfaceId};
use cadmpeg_ir::index::ModelIndex;
use cadmpeg_ir::math::{Point2, Point3, Vector3};
use cadmpeg_ir::transform::Transform;
use cadmpeg_ir::units::{LengthUnit, Units};
use cadmpeg_ir::CadIr;
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipWriter};
use crate::ids::StepIdentity;
use crate::loss::StepLossCode;
use crate::test_support::{decode_inline, export};
use crate::{
write_step, StepCodec, StepError, StepSchema, StepUnsupportedPolicy, StepWriteOptions,
};
#[test]
pub(crate) fn decode_transfers_ap242_one_based_tessellation_indices() {
let bytes = include_bytes!("../../../tests/fixtures/ap242_tessellation.p21");
let result = StepCodec::default()
.decode(&mut Cursor::new(bytes), &DecodeOptions::default())
.expect("decode AP242 tessellation");
assert_eq!(result.ir().model.tessellations.len(), 2);
assert_eq!(result.ir().model.bodies.len(), 1);
let mesh = &result.ir().model.tessellations[0];
assert_eq!(mesh.vertices.len(), 3);
assert_eq!(mesh.vertices[1].x, 10.0);
assert_eq!(mesh.triangles, [[0, 1, 2]]);
assert_eq!(mesh.normals.len(), 3);
assert_eq!(
mesh.body.as_ref().map(cadmpeg_ir::ids::BodyId::as_str),
Some("step:data:body#38")
);
let complex = result
.ir()
.model
.tessellations
.iter()
.find(|mesh| mesh.id.ends_with("#7"))
.unwrap();
assert_eq!(complex.triangles, [[0, 1, 2], [2, 1, 3], [0, 1, 3]]);
assert_eq!(complex.vertices[0], Point3::new(10.0, 10.0, 0.0));
assert_eq!(complex.normals.len(), 4);
assert_eq!(complex.normals[0].x, 1.0);
assert!(result
.ir()
.model
.appearance_bindings
.iter()
.any(|binding| matches!(
binding.target,
cadmpeg_ir::appearance::AppearanceTarget::Tessellation(_)
)));
assert!(result
.report()
.notes
.iter()
.any(|note| note
== "geometric validation surface area triangle sheet: expected 50, tessellation approximation 50"));
assert!(result.report().notes.iter().any(|note| note.starts_with(
"geometric validation centroid triangle centroid: expected (3.333333333333333,3.333333333333333,0), tessellation approximation distance"
)));
assert!(result.report().notes.iter().any(
|note| note == "geometric validation volume open sheet volume: expected 0, tessellation approximation 0"
));
assert!(!result.report().losses.iter().any(|loss| loss
.message
.contains("does not match transferred tessellation")));
let validation = cadmpeg_ir::validate_neutral(result.ir(), result.report().losses.clone());
assert!(validation.is_ok(), "{:#?}", validation.findings);
}
#[test]
fn complex_tessellated_face_retains_its_surface_carrier() {
let source = String::from_utf8(
include_bytes!("../../../tests/fixtures/ap242_tessellation.p21").to_vec(),
)
.expect("fixture is UTF-8")
.replace(
"#7=COMPLEX_TRIANGULATED_FACE('strip and fan',#6,4,((1.,0.,0.),(0.,1.,0.),(0.,0.,1.),(0.,0.,-1.)),$,(4,3,2,1),((1,2,3,4)),((1,2,4)));",
"#7=COMPLEX_TRIANGULATED_FACE('strip and fan',#6,4,((1.,0.,0.),(0.,1.,0.),(0.,0.,1.),(0.,0.,-1.)),#90,(4,3,2,1),((1,2,3,4)),((1,2,4)));",
)
.replace(
"ENDSEC;\nEND-ISO-10303-21;",
"#90=PLANE('',#34);\nENDSEC;\nEND-ISO-10303-21;",
);
let decoded = StepCodec::default()
.decode(&mut Cursor::new(source), &DecodeOptions::default())
.expect("decode tessellated face surface");
let surface = decoded
.ir()
.model
.surfaces
.iter()
.find(|surface| surface.id.as_str() == "step:data:surface#90")
.expect("tessellated face surface");
assert_eq!(
surface
.source_object
.as_ref()
.map(|source| source.object_id.as_str()),
Some("#7")
);
let validation = cadmpeg_ir::validate_neutral(decoded.ir(), decoded.report().losses.clone());
assert!(validation.is_ok(), "{:#?}", validation.findings);
}
#[test]
fn complex_tessellation_partials_transfer_coordinates_and_indices() {
let source = String::from_utf8(
include_bytes!("../../../tests/fixtures/ap242_tessellation.p21").to_vec(),
)
.expect("fixture is UTF-8")
.replace(
"#3=COORDINATES_LIST('triangle coordinates',3,((0.,0.,0.),(10.,0.,0.),(0.,10.,0.)));",
"#3=(COORDINATES_LIST(3,((0.,0.,0.),(10.,0.,0.),(0.,10.,0.))) GEOMETRIC_REPRESENTATION_ITEM() REPRESENTATION_ITEM('triangle coordinates') TESSELLATED_ITEM());",
)
.replace(
"#4=TRIANGULATED_FACE('triangle',#3,3,((0.,0.,1.)),$,(),((1,2,3)));",
"#4=(GEOMETRIC_REPRESENTATION_ITEM() REPRESENTATION_ITEM('triangle') TESSELLATED_FACE(#3,3,((0.,0.,1.)),$) TESSELLATED_ITEM() TESSELLATED_STRUCTURED_ITEM() TRIANGULATED_FACE((),((1,2,3))));",
);
let decoded = StepCodec::default()
.decode(&mut Cursor::new(source), &DecodeOptions::default())
.expect("decode complex tessellation partials");
let mesh = decoded
.ir()
.model
.tessellations
.iter()
.find(|mesh| mesh.id.ends_with("#4"))
.expect("complex tessellated face");
assert_eq!(mesh.vertices.len(), 3);
assert_eq!(mesh.vertices[1], Point3::new(10.0, 0.0, 0.0));
assert_eq!(mesh.triangles, [[0, 1, 2]]);
assert_eq!(mesh.normals.len(), 3);
assert_eq!(
mesh.body.as_ref().map(cadmpeg_ir::ids::BodyId::as_str),
Some("step:data:body#38")
);
let validation = cadmpeg_ir::validate_neutral(decoded.ir(), decoded.report().losses.clone());
assert!(validation.is_ok(), "{:#?}", validation.findings);
}
#[test]
fn tessellation_geometry_sets_transfer_flag_and_invalid_pnindex_is_rejected() {
let result = StepCodec::default()
.decode(
&mut Cursor::new(include_bytes!(
"../../../tests/fixtures/ap242_tessellation.p21"
)),
&DecodeOptions::default(),
)
.expect("decode tessellation fixture");
assert!(result.report().geometry_transferred);
assert!(result
.ir()
.model
.tessellations
.iter()
.any(|mesh| mesh.id == "step:tessellation:mesh#7" && mesh.body.is_none()));
assert!(result.report().losses.iter().any(|loss| {
loss.code == StepLossCode::TessellationItemUndeclared.kind()
&& loss.message.contains("mesh retained as detached")
}));
let malformed = decode_inline(
"#1=COORDINATES_LIST('',3,((0.,0.,0.),(1.,0.,0.),(0.,1.,0.)));
#2=TRIANGULATED_SURFACE_SET('',#1,3,$,('bad'),((1,2,3)));",
);
assert!(malformed.ir().model.tessellations.is_empty());
assert!(malformed
.report()
.losses
.iter()
.any(|loss| loss.message.contains("invalid pnindex")));
}
#[test]
fn shared_tessellation_item_is_not_assigned_to_an_arbitrary_body() {
let source = String::from_utf8(
include_bytes!("../../../tests/fixtures/ap242_tessellation.p21").to_vec(),
)
.expect("fixture is UTF-8")
.replace(
"ENDSEC;\nEND-ISO-10303-21;",
"#80=WIRE_SHELL('',(#32));\n#81=SHELL_BASED_WIREFRAME_MODEL('',(#80));\n#82=TESSELLATED_SHELL('shared mesh',(#4),#80);\nENDSEC;\nEND-ISO-10303-21;",
);
let decoded = StepCodec::default()
.decode(&mut Cursor::new(source), &DecodeOptions::default())
.expect("decode shared tessellation item");
let mesh = decoded
.ir()
.model
.tessellations
.iter()
.find(|mesh| mesh.id == "step:tessellation:mesh#4")
.expect("shared mesh");
assert!(mesh.body.is_none());
assert!(
decoded.report().losses.iter().any(|loss| {
loss.code == StepLossCode::TessellationItemBodyUnresolved.kind()
&& loss.message.contains("multiple candidate bodies")
}),
"{:#?}",
decoded.report().losses
);
}
#[test]
fn malformed_complex_strip_does_not_discard_valid_strips() {
let result = decode_inline(
"#1=COORDINATES_LIST('',4,((0.,0.,0.),(1.,0.,0.),(0.,1.,0.),(1.,1.,0.)));
#2=COMPLEX_TRIANGULATED_SURFACE_SET('',#1,4,$,$,((1,2),(1,2,3,4)),());",
);
assert_eq!(result.ir().model.tessellations.len(), 1);
assert_eq!(result.ir().model.tessellations[0].triangles.len(), 2);
}
#[test]
fn complex_triangle_strip_alternates_winding() {
let result = decode_inline(
"#1=COORDINATES_LIST('',4,((0.,0.,0.),(1.,0.,0.),(0.,1.,0.),(1.,1.,0.)));
#2=COMPLEX_TRIANGULATED_SURFACE_SET('',#1,4,$,$,((1,2,3,4)),());",
);
assert_eq!(result.ir().model.tessellations.len(), 1);
assert_eq!(
result.ir().model.tessellations[0].triangles,
[[0, 1, 2], [2, 1, 3]]
);
}
#[test]
fn non_finite_tessellation_coordinates_are_rejected() {
let result = decode_inline(
"#1=COORDINATES_LIST('',1,((1E400,0.,0.)));
#2=TRIANGULATED_SURFACE_SET('',#1,1,$,$,((1,1,1)));",
);
assert!(result.ir().model.tessellations.is_empty());
}
#[test]
fn complex_tessellated_face_keeps_exact_support_surface_reachable() {
let source = String::from_utf8(
include_bytes!("../../../tests/fixtures/ap242_tessellation.p21").to_vec(),
)
.expect("fixture is UTF-8")
.replace(
"#7=COMPLEX_TRIANGULATED_FACE('strip and fan',#6,4,((1.,0.,0.),(0.,1.,0.),(0.,0.,1.),(0.,0.,-1.)),$,(4,3,2,1),((1,2,3,4)),((1,2,4)));",
"#7=COMPLEX_TRIANGULATED_FACE('strip and fan',#6,4,((1.,0.,0.),(0.,1.,0.),(0.,0.,1.),(0.,0.,-1.)),#79,(4,3,2,1),((1,2,3,4)),((1,2,4)));",
)
.replace(
"ENDSEC;\nEND-ISO-10303-21;",
"#79=PLANE('exact support',#34);\nENDSEC;\nEND-ISO-10303-21;",
);
let result = StepCodec::default()
.decode(
&mut Cursor::new(source.as_bytes()),
&DecodeOptions::default(),
)
.expect("decode complex tessellated support");
let support = result
.ir()
.model
.surfaces
.iter()
.find(|surface| surface.id.0 == "step:data:surface#79")
.expect("exact support surface");
assert_eq!(
support
.source_object
.as_ref()
.map(|source| source.object_id.as_str()),
Some("#7")
);
let validation = cadmpeg_ir::validate_neutral(result.ir(), result.report().losses.clone());
assert!(!validation.findings.iter().any(|finding| {
finding.check == cadmpeg_ir::Check::CarrierReachability
&& finding.entity.as_deref() == Some("step:data:surface#79")
}));
}