#![allow(clippy::unwrap_used)]
use crate::examples::unit_cube;
use crate::validate::validate_neutral;
#[test]
fn feature_operation_geometry_is_validated() {
use crate::features::{Feature, FeatureDefinition, FeatureId, FeatureOperation};
let definitions = vec![
FeatureDefinition::Operation(FeatureOperation::Form { cages: Vec::new() }),
FeatureDefinition::Operation(FeatureOperation::Form {
cages: vec![
crate::ids::SubdId::mint("synthetic:test:subd#missing").expect("valid identity")
],
}),
];
let expected = ["references missing Form control cage `synthetic:test:subd#missing`"];
let mut ir = unit_cube().expect("valid unit cube fixture");
for (ordinal, definition) in definitions.into_iter().enumerate() {
ir.model.features.push(Feature {
id: FeatureId::mint(format!("synthetic:test:feature#invalid-{ordinal}"))
.expect("identity grammar"),
ordinal: cadmpeg_core::decode::u64_from_index(ordinal),
name: None,
suppressed: Some(false),
dependencies: crate::features::DistinctMembers::default(),
source_properties: std::collections::BTreeMap::new(),
source_tag: None,
source_text: None,
source_content: crate::features::FeatureContent::default(),
evaluation: crate::features::FeatureEvaluation::from_definition(definition),
native_ref: None,
});
}
let findings = validate_neutral(&ir, Vec::new())
.expect("validate the neutral model")
.findings;
assert!(!findings
.iter()
.any(|finding| { finding.entity.as_deref() == Some("synthetic:test:feature#invalid-0") }));
for message in expected {
assert!(findings.iter().any(|finding| finding.message == message));
}
}
fn feature(
id: &str,
ordinal: usize,
definition: crate::features::FeatureDefinition,
) -> crate::features::Feature {
crate::features::Feature {
id: crate::features::FeatureId::mint(id).unwrap(),
ordinal: cadmpeg_core::decode::u64_from_index(ordinal),
name: None,
suppressed: Some(false),
dependencies: crate::features::DistinctMembers::default(),
source_properties: std::collections::BTreeMap::new(),
source_tag: None,
source_text: None,
source_content: crate::features::FeatureContent::default(),
evaluation: crate::features::FeatureEvaluation::from_definition(definition),
native_ref: None,
}
}
fn feature_reference_findings(ir: &crate::CadIr) -> Vec<crate::report::check::Finding> {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
let index = crate::index::ModelIndex::build(ir);
let arena = DecodeArena::new();
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
let mut findings = Vec::new();
super::super::check_feature_references(&ctx, ir, &index, &mut findings).unwrap();
ctx.finish_session().unwrap();
findings
}
#[test]
fn many_block_instances_resolve_their_definitions_in_linear_time() {
use crate::features::{FeatureDefinition, FeatureOperation};
const INSTANCES: usize = 1 << 16;
let block = "synthetic:test:feature#block";
let plane = "synthetic:test:feature#plane";
let mut ir = crate::CadIr::empty();
ir.model.features.push(feature(
block,
0,
FeatureDefinition::Operation(FeatureOperation::SketchBlockDefinition { sketch: None }),
));
ir.model.features.push(feature(
plane,
1,
FeatureDefinition::Operation(FeatureOperation::DatumPlane {
frame: crate::features::FeatureDatumPlaneFrame::new(
crate::math::Point3::new(0.0, 0.0, 0.0),
crate::math::Vector3::new(0.0, 0.0, 1.0),
crate::math::Vector3::new(1.0, 0.0, 0.0),
)
.unwrap(),
}),
));
for index in 0..INSTANCES {
let target = if index == 0 { plane } else { block };
let mut instance = feature(
&format!("synthetic:test:feature#instance-{index}"),
index + 2,
FeatureDefinition::Operation(FeatureOperation::SketchBlockInstance {
block: Some(crate::features::FeatureId::mint(target).unwrap()),
placement: Some(crate::transform::Transform::identity()),
}),
);
instance
.dependencies
.insert(crate::features::FeatureId::mint(target).unwrap());
ir.model.features.push(instance);
}
let findings = feature_reference_findings(&ir);
let not_definitions: Vec<_> = findings
.iter()
.filter(|finding| finding.message == "sketch block target is not a block definition")
.map(|finding| finding.entity.as_deref().unwrap())
.collect();
assert_eq!(not_definitions, ["synthetic:test:feature#instance-0"]);
}
#[test]
fn many_datum_plane_cycles_are_each_reported_once_in_linear_time() {
use crate::features::{DatumPlaneReference, FeatureDefinition, FeatureOperation};
const CYCLES: usize = 1 << 15;
let id = |cycle: usize, side: usize| format!("synthetic:test:feature#plane-{cycle}-{side}");
let mut ir = crate::CadIr::empty();
for cycle in 0..CYCLES {
for side in 0..2 {
ir.model.features.push(feature(
&id(cycle, side),
2 * cycle + side,
FeatureDefinition::Operation(FeatureOperation::DatumOffsetPlane {
reference: Some(DatumPlaneReference::Feature {
feature: crate::features::FeatureId::mint(id(cycle, 1 - side)).unwrap(),
}),
distance: crate::scalar::Length::new(1.0).unwrap(),
}),
));
}
}
let findings = feature_reference_findings(&ir);
let reported: Vec<_> = findings
.iter()
.filter(|finding| finding.message.contains("datum-plane reference cycle"))
.map(|finding| finding.entity.clone().unwrap())
.collect();
let expected: Vec<_> = (0..CYCLES).map(|cycle| id(cycle, 0)).collect();
assert_eq!(reported, expected);
}
#[test]
fn attached_plate_and_bend_validate_profiles_edges_and_history() {
use crate::features::{
EdgeSelection, FeatureDefinition, FeatureId, FeatureOperation, PlanarProfileRef,
SheetMetalRelief, SheetMetalReliefForm, SheetMetalThicknessSide, SheetMetalTransition,
SheetMetalTransitionForm,
};
use crate::scalar::{NonNegativeLength, PositiveLength};
let relief = SheetMetalRelief {
form: SheetMetalReliefForm::StyleDefault,
width: PositiveLength::new(1.0).unwrap(),
depth: PositiveLength::new(2.0).unwrap(),
minimum_remnant: NonNegativeLength::new(0.0).unwrap(),
seam_gap: NonNegativeLength::new(0.0).unwrap(),
};
let transition = SheetMetalTransition {
form: SheetMetalTransitionForm::StyleDefault,
radius: NonNegativeLength::new(0.0).unwrap(),
};
let plate_id = FeatureId::mint("test:model:feature#plate").unwrap();
let plate = FeatureOperation::SheetMetalAttachedPlate {
profile: PlanarProfileRef::Sketch(
crate::sketches::SketchId::mint("test:model:sketch#missing").unwrap(),
),
thickness: PositiveLength::new(1.0).unwrap(),
side: SheetMetalThicknessSide::Forward,
attachment: EdgeSelection::Edges(vec![
crate::ids::EdgeId::mint("test:model:edge#missing").unwrap()
]),
};
let bend = FeatureOperation::SheetMetalBend {
plate: plate_id.clone(),
attachment: EdgeSelection::All,
inside_radius: PositiveLength::new(1.0).unwrap(),
relief,
transition,
};
let mut ir = crate::CadIr::empty();
ir.model.features.push(feature(
plate_id.as_str(),
0,
FeatureDefinition::Operation(plate),
));
ir.model.features.push(feature(
"test:model:feature#bend",
1,
FeatureDefinition::Operation(bend),
));
let findings = validate_neutral(&ir, Vec::new()).unwrap().findings;
for missing in ["test:model:sketch#missing", "test:model:edge#missing"] {
assert!(findings.iter().any(|finding| finding.check
== crate::report::check::Check::ReferentialIntegrity
&& finding.entity.as_deref() == Some(plate_id.as_str())
&& finding.message.contains(missing)));
}
assert!(findings.iter().any(|finding| finding.entity.as_deref()
== Some("test:model:feature#bend")
&& finding.message.contains("dependencies")));
ir.model.features[1].dependencies = vec![plate_id.clone()].try_into().unwrap();
assert!(!feature_reference_findings(&ir)
.iter()
.any(|finding| finding.entity.as_deref() == Some("test:model:feature#bend")));
ir.model.features[0].evaluation.edit(|definition, _| {
*definition = FeatureDefinition::Operation(FeatureOperation::StoredGeometry {});
});
assert!(feature_reference_findings(&ir)
.iter()
.any(
|finding| finding.entity.as_deref() == Some("test:model:feature#bend")
&& finding.message.contains("attached plate")
));
ir.model.features.remove(0);
assert!(feature_reference_findings(&ir)
.iter()
.any(|finding| finding.message.contains("missing attached plate")));
}
#[test]
fn an_attached_plate_requires_one_bend() {
use crate::features::*;
use crate::scalar::{NonNegativeLength, PositiveLength};
let mut ir = crate::CadIr::empty();
let id = FeatureId::mint("test:model:feature#plate").unwrap();
let plate = FeatureOperation::SheetMetalAttachedPlate {
profile: PlanarProfileRef::Native("test:profile".into()),
thickness: PositiveLength::new(1.0).unwrap(),
side: SheetMetalThicknessSide::Forward,
attachment: EdgeSelection::All,
};
ir.model
.features
.push(feature(id.as_str(), 0, FeatureDefinition::Operation(plate)));
let missing_bend = |ir: &crate::CadIr| {
feature_reference_findings(ir)
.iter()
.any(|finding| finding.message == "attached plate requires exactly one bend")
};
assert!(missing_bend(&ir));
let bend = FeatureOperation::SheetMetalBend {
plate: id.clone(),
attachment: EdgeSelection::All,
inside_radius: PositiveLength::new(1.0).unwrap(),
relief: SheetMetalRelief {
form: SheetMetalReliefForm::StyleDefault,
width: PositiveLength::new(1.0).unwrap(),
depth: PositiveLength::new(1.0).unwrap(),
minimum_remnant: NonNegativeLength::new(0.0).unwrap(),
seam_gap: NonNegativeLength::new(0.0).unwrap(),
},
transition: SheetMetalTransition {
form: SheetMetalTransitionForm::StyleDefault,
radius: NonNegativeLength::new(0.0).unwrap(),
},
};
let mut bend = feature(
"test:model:feature#bend",
1,
FeatureDefinition::Operation(bend),
);
bend.dependencies.insert(id);
ir.model.features.push(bend.clone());
assert!(!missing_bend(&ir));
bend.id = FeatureId::mint("test:model:feature#second-bend").unwrap();
bend.ordinal = 2;
ir.model.features.push(bend);
assert!(missing_bend(&ir));
}
#[test]
fn corner_requires_a_preceding_bend_dependency() {
use crate::features::*;
let bend_id = FeatureId::mint("test:model:feature#bend").unwrap();
let mut ir = crate::CadIr::empty();
ir.model.features.push(feature(
"test:model:feature#corner",
1,
FeatureDefinition::Operation(FeatureOperation::SheetMetalCorner {
bend: bend_id.clone(),
effect: SheetMetalCornerEffect::Unchanged,
}),
));
assert!(feature_reference_findings(&ir)
.iter()
.any(|finding| finding.message.contains("missing corner bend")));
ir.model.features.push(feature(
bend_id.as_str(),
0,
FeatureDefinition::Operation(FeatureOperation::StoredGeometry {}),
));
assert!(feature_reference_findings(&ir)
.iter()
.any(|finding| finding.message.contains("corner bend")));
let bend: FeatureOperation = serde_json::from_value(serde_json::json!({
"definition": "sheet_metal_bend", "plate": "test:model:feature#plate",
"attachment": {"kind": "all"}, "inside_radius": 1.0,
"relief": {"form": "style_default", "width": 1.0, "depth": 1.0,
"minimum_remnant": 0.0, "seam_gap": 0.0},
"transition": {"form": "style_default", "radius": 0.0}
}))
.unwrap();
ir.model.features[1].evaluation.edit(|definition, _| {
*definition = FeatureDefinition::Operation(bend);
});
ir.model.features[0].dependencies.insert(bend_id);
let corner_findings = |ir: &crate::CadIr| {
feature_reference_findings(ir)
.into_iter()
.filter(|finding| finding.entity.as_deref() == Some("test:model:feature#corner"))
.collect::<Vec<_>>()
};
assert!(corner_findings(&ir).is_empty());
ir.model.features[1].ordinal = 2;
assert!(corner_findings(&ir)
.iter()
.any(|finding| finding.message.contains("precede")));
ir.model.features[1].ordinal = 0;
ir.model.features[0].dependencies = DistinctMembers::default();
assert!(corner_findings(&ir)
.iter()
.any(|finding| finding.message.contains("dependencies")));
}