#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ids::{BodyId, EdgeId, FaceId, OccurrenceId, PmiId, ProductDefinitionId, VertexId};
use crate::transform::Transform;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PmiTarget {
Body {
body: BodyId,
},
Face {
face: FaceId,
},
Edge {
edge: EdgeId,
},
Vertex {
vertex: VertexId,
},
Product {
product: ProductDefinitionId,
},
Occurrence {
occurrence: OccurrenceId,
},
ShapeAspect {
source_id: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PmiValue {
pub value: f64,
pub quantity: PmiQuantity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum PmiQuantity {
Length,
Angle,
Ratio,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum DimensionKind {
Size,
Location,
Angular,
Diameter,
Radius,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum GeometricToleranceKind {
Straightness,
Flatness,
Roundness,
Cylindricity,
Coaxiality,
LineProfile,
SurfaceProfile,
Angularity,
Perpendicularity,
Parallelism,
Position,
Concentricity,
Symmetry,
CircularRunout,
TotalRunout,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct DatumReference {
pub datum: PmiId,
pub precedence: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub common_group: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modifiers: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct LimitsAndFits {
pub form_variance: String,
pub zone_variance: String,
pub grade: String,
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PmiDefinition {
Datum {
identification: String,
},
DatumSystem {
references: Vec<DatumReference>,
},
GeometricTolerance {
tolerance: GeometricToleranceKind,
magnitude: PmiValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
defined_unit: Option<PmiValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
defined_area_unit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
defined_area_second_unit: Option<PmiValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
datum_system: Option<PmiId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
modifiers: Vec<String>,
},
Dimension {
dimension: DimensionKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
nominal: Option<PmiValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
lower_deviation: Option<PmiValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
upper_deviation: Option<PmiValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
limits_and_fits: Option<LimitsAndFits>,
},
Presentation {
#[serde(default, skip_serializing_if = "Option::is_none")]
text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
placement: Option<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
semantics: Vec<PmiId>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct PmiAnnotation {
pub id: PmiId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub targets: Vec<PmiTarget>,
pub definition: PmiDefinition,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::document::CadIr;
use crate::report::Check;
use crate::units::Units;
use crate::validate::validate_neutral;
#[test]
fn datum_system_references_resolve_with_precedence() {
let datum_id = PmiId("test:model:pmi#datum-a".into());
let mut ir = CadIr::empty(Units::default());
ir.model.pmi.push(PmiAnnotation {
id: datum_id.clone(),
name: Some("datum A".into()),
targets: vec![PmiTarget::ShapeAspect {
source_id: "#10".into(),
}],
definition: PmiDefinition::Datum {
identification: "A".into(),
},
});
ir.model.pmi.push(PmiAnnotation {
id: PmiId("test:model:pmi#system".into()),
name: None,
targets: Vec::new(),
definition: PmiDefinition::DatumSystem {
references: vec![DatumReference {
datum: datum_id,
precedence: 1,
common_group: None,
modifiers: Vec::new(),
}],
},
});
ir.finalize();
assert!(validate_neutral(&ir, Vec::new()).is_ok());
}
#[test]
fn unresolved_semantic_reference_is_invalid() {
let mut ir = CadIr::empty(Units::default());
ir.model.pmi.push(PmiAnnotation {
id: PmiId("test:model:pmi#graphic".into()),
name: None,
targets: Vec::new(),
definition: PmiDefinition::Presentation {
text: None,
placement: None,
semantics: vec![PmiId("test:model:pmi#missing".into())],
},
});
let report = validate_neutral(&ir, Vec::new());
assert!(report
.findings
.iter()
.any(|finding| finding.check == Check::Pmi));
}
#[test]
fn datum_references_are_type_checked_and_common_groups_are_explicit() {
let mut ir = CadIr::empty(Units::default());
let dimension_id = PmiId("test:model:pmi#dimension".into());
ir.model.pmi.push(PmiAnnotation {
id: dimension_id.clone(),
name: None,
targets: Vec::new(),
definition: PmiDefinition::Dimension {
dimension: DimensionKind::Size,
nominal: None,
lower_deviation: None,
upper_deviation: None,
limits_and_fits: None,
},
});
ir.model.pmi.push(PmiAnnotation {
id: PmiId("test:model:pmi#system".into()),
name: None,
targets: Vec::new(),
definition: PmiDefinition::DatumSystem {
references: vec![DatumReference {
datum: dimension_id.clone(),
precedence: 1,
common_group: None,
modifiers: Vec::new(),
}],
},
});
ir.model.pmi.push(PmiAnnotation {
id: PmiId("test:model:pmi#tolerance".into()),
name: None,
targets: Vec::new(),
definition: PmiDefinition::GeometricTolerance {
tolerance: GeometricToleranceKind::Position,
magnitude: PmiValue {
value: 0.1,
quantity: PmiQuantity::Length,
},
defined_unit: None,
defined_area_unit: None,
defined_area_second_unit: None,
datum_system: Some(dimension_id),
modifiers: Vec::new(),
},
});
let findings = validate_neutral(&ir, Vec::new()).findings;
assert!(
findings
.iter()
.filter(|finding| finding.check == Check::Pmi)
.count()
>= 2
);
}
#[test]
fn non_finite_presentation_placement_is_invalid() {
let mut ir = CadIr::empty(Units::default());
let mut placement = Transform::identity();
placement.rows[0][3] = f64::INFINITY;
ir.model.pmi.push(PmiAnnotation {
id: PmiId("test:model:pmi#graphic".into()),
name: None,
targets: Vec::new(),
definition: PmiDefinition::Presentation {
text: None,
placement: Some(placement),
semantics: Vec::new(),
},
});
assert!(validate_neutral(&ir, Vec::new())
.findings
.iter()
.any(|finding| finding.check == Check::Pmi && finding.message.contains("non-finite")));
}
}