Skip to main content

ogeom_doc/
pmi.rs

1//! PMI: dimensions, geometric tolerances, datums, their targets, and the
2//! drawing of them.
3//!
4//! The machine-readable annotations AP242 calls *semantic* PMI: the values,
5//! not the leader lines. A dimension carries what it measures and its
6//! plus/minus bounds; a geometric tolerance carries its kind, magnitude and
7//! the datums it references; a datum is a letter on a feature, and its
8//! *targets* are the pads a fixture actually contacts it at. Everything
9//! anchors to topology nodes, the same way colours and names do, so an
10//! annotation survives every placement of the shape it describes.
11//!
12//! And the *presentation* kind, which is the leader lines: where each
13//! annotation is drawn, in which plane, as which polylines. It is kept
14//! separate because it is separate: a drawing places its callouts where a
15//! draughtsman put them, and neither half derives from the other. What ties
16//! them is [`Callout::annotates`], and a document may carry either alone.
17
18use ogeom_topo::TShapeId;
19
20/// What a dimension's value measures.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum MeasureKind {
23    /// A length, in the document's length unit.
24    Length,
25    /// An angle, in radians.
26    Angle,
27}
28
29/// A dimensional characteristic: a size or a location, with its values.
30#[derive(Debug, Clone)]
31pub struct Dimension {
32    /// What the file calls it: `diameter`, `linear distance`, …
33    pub name: String,
34    /// The stated values: one for a plain dimension, several when the file
35    /// states a value with explicit bounds.
36    pub values: Vec<f64>,
37    /// Length or angle.
38    pub kind: MeasureKind,
39    /// The upper allowance, when a plus/minus tolerance applies.
40    pub plus: Option<f64>,
41    /// The lower allowance (typically negative) when one applies.
42    pub minus: Option<f64>,
43    /// The topology the dimension measures, one group per feature: a size
44    /// has one group, a location has one per end.
45    pub features: Vec<Vec<TShapeId>>,
46    /// Whether the dimension runs *between* two features (a location)
47    /// rather than sizing one.
48    pub location: bool,
49}
50
51impl Dimension {
52    /// Every measured node, features flattened.
53    pub fn items(&self) -> impl Iterator<Item = TShapeId> + '_ {
54        self.features.iter().flatten().copied()
55    }
56}
57
58/// A geometric tolerance: flatness, position, profile, and their kin.
59#[derive(Debug, Clone)]
60pub struct GeometricTolerance {
61    /// The kind, as a lower-case word: `flatness`, `position`,
62    /// `surface_profile`, `perpendicularity`, …
63    pub kind: String,
64    /// The annotation's own name.
65    pub name: String,
66    /// The tolerance zone's magnitude.
67    pub magnitude: f64,
68    /// Zone and material-condition modifiers, as lower-case words:
69    /// `maximum_material_requirement`, `unequally_disposed`, …: the
70    /// exchange vocabulary for Ⓜ, Ⓤ and their kin.
71    pub modifiers: Vec<String>,
72    /// The datum letters the tolerance references, in precedence order. A
73    /// composite reference (two datums acting as one, ISO's `A-B`) is a
74    /// single entry with its labels hyphen-joined.
75    pub datums: Vec<String>,
76    /// The topology the tolerance controls.
77    pub items: Vec<TShapeId>,
78}
79
80/// A datum: a letter naming a feature other annotations reference.
81#[derive(Debug, Clone)]
82pub struct Datum {
83    /// The letter: `A`, `B`, …
84    pub label: String,
85    /// The feature's topology.
86    pub items: Vec<TShapeId>,
87}
88
89/// A document's PMI, in file order: the semantic annotations, the targets a
90/// datum is actually contacted at, and the drawn presentation.
91#[derive(Debug, Clone, Default)]
92pub struct Pmi {
93    /// Dimensional characteristics.
94    pub dimensions: Vec<Dimension>,
95    /// Geometric tolerances.
96    pub tolerances: Vec<GeometricTolerance>,
97    /// Datums.
98    pub datums: Vec<Datum>,
99    /// The targets datums are established at.
100    pub targets: Vec<DatumTarget>,
101    /// The drawn annotations.
102    pub callouts: Vec<Callout>,
103}
104
105impl Pmi {
106    /// No annotations.
107    #[must_use]
108    pub fn new() -> Self {
109        Self::default()
110    }
111
112    /// Whether anything is annotated.
113    #[must_use]
114    pub fn is_empty(&self) -> bool {
115        self.dimensions.is_empty()
116            && self.tolerances.is_empty()
117            && self.datums.is_empty()
118            && self.targets.is_empty()
119            && self.callouts.is_empty()
120    }
121
122    /// The targets belonging to one datum letter, in the order the file gave.
123    pub fn targets_of<'a>(&'a self, datum: &'a str) -> impl Iterator<Item = &'a DatumTarget> + 'a {
124        self.targets.iter().filter(move |t| t.datum == datum)
125    }
126}
127
128/// Where a datum is actually contacted: the target a fixture touches it at.
129///
130/// A datum plane on a casting is not contacted over its whole face (it rests
131/// on three pads), and the drawing says so with targets: `A1`, `A2`, `A3`,
132/// each a point, a line or an area of stated size. The distinction matters to
133/// anything that inspects the part, because the datum it should establish is
134/// the one the targets define and not the nominal surface.
135#[derive(Debug, Clone, Copy, PartialEq)]
136pub enum DatumTargetKind {
137    /// A point contact.
138    Point,
139    /// A line contact, of the stated length.
140    Line {
141        /// How long the contact runs.
142        length: f64,
143    },
144    /// A rectangular area.
145    Rectangle {
146        /// Along the target's own reference direction.
147        length: f64,
148        /// Across it.
149        width: f64,
150    },
151    /// A circular area.
152    Circle {
153        /// The contact patch's diameter.
154        diameter: f64,
155    },
156}
157
158/// One datum target: which datum, which target of it, and where.
159#[derive(Debug, Clone)]
160pub struct DatumTarget {
161    /// The datum's letter: `A` for `A1`.
162    pub datum: String,
163    /// The target's own number: `1` for `A1`.
164    pub index: u32,
165    /// Point, line, or area, with its size.
166    pub kind: DatumTargetKind,
167    /// Where the target sits, in the part's own coordinates.
168    pub at: ogeom_math::Point,
169    /// The target's own frame: its normal, and the direction its length runs
170    /// in. Absent where the file placed the target by point alone, which a
171    /// point target does not need.
172    pub frame: Option<ogeom_math::Frame>,
173    /// The topology the target is placed on.
174    pub items: Vec<TShapeId>,
175}
176
177impl DatumTarget {
178    /// The identifier a drawing shows: the letter and the number, `A1`.
179    #[must_use]
180    pub fn identifier(&self) -> String {
181        format!("{}{}", self.datum, self.index)
182    }
183}
184
185/// One drawn annotation: the geometry a viewer puts on the screen.
186///
187/// *Presentation* PMI, as against the semantic kind above. The two are
188/// separate on purpose and in the file: the semantic annotation says a
189/// tolerance is `0.1` and controls this face, and the presentation says where
190/// its frame and leader are drawn. Neither derives from the other (a drawing
191/// places its callouts where a draughtsman put them), so a document that
192/// wants both carries both, and [`Callout::annotates`] is the link between.
193#[derive(Debug, Clone)]
194pub struct Callout {
195    /// The name the file gave it, which is how a drawing names its own
196    /// annotations: `Flatness.1`, `Linear Size.3`.
197    pub name: String,
198    /// The plane the annotation is drawn in, where the file stated one.
199    pub plane: Option<ogeom_math::Frame>,
200    /// The drawn geometry: polylines, in the part's own coordinates.
201    pub polylines: Vec<Vec<ogeom_math::Point>>,
202    /// Which semantic annotation this draws, where the file said.
203    pub annotates: Option<Annotated>,
204}
205
206/// Which semantic annotation a callout draws.
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208pub enum Annotated {
209    /// The dimension at this index of [`Pmi::dimensions`].
210    Dimension(usize),
211    /// The tolerance at this index of [`Pmi::tolerances`].
212    Tolerance(usize),
213    /// The datum at this index of [`Pmi::datums`].
214    Datum(usize),
215}