Skip to main content

cadmpeg_ir/
presentation.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Neutral persisted document and view presentation state.
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7
8/// Stable presentation-document identity.
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
10#[serde(transparent)]
11pub struct PresentationId(pub String);
12
13/// Persisted camera pose.
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
15pub struct CameraState {
16    /// Camera position in document coordinates.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub position: Option<[f64; 3]>,
19    /// Persisted orientation quaternion in source component order.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub orientation: Option<[f64; 4]>,
22    /// Other camera fields retained by exact source name.
23    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
24    pub properties: BTreeMap<String, String>,
25}
26
27/// Ordered non-provider GUI state such as clipping or section state.
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
29pub struct PresentationState {
30    /// Persisted state element name.
31    pub kind: String,
32    /// Source order among document GUI state elements.
33    pub order: u32,
34    /// Exact root attributes.
35    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
36    pub attributes: BTreeMap<String, String>,
37    /// Referenced display assets as global native entry ids.
38    #[serde(default, skip_serializing_if = "Vec::is_empty")]
39    pub assets: Vec<String>,
40}
41
42/// Document-wide persisted GUI state.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
44pub struct PresentationDocument {
45    /// Globally unique presentation identity.
46    pub id: PresentationId,
47    /// Persisted GUI schema version.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub schema_version: Option<u32>,
50    /// Active view name or identity.
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub active_view: Option<String>,
53    /// Persisted active camera.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub camera: Option<CameraState>,
56    /// Ordered document-level GUI states.
57    #[serde(default, skip_serializing_if = "Vec::is_empty")]
58    pub states: Vec<PresentationState>,
59    /// Native GUI document record supplying this state.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub native_ref: Option<String>,
62}
63
64/// Presentation state owned by one persisted view provider.
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
66pub struct ViewPresentation {
67    /// Globally unique view-provider identity.
68    pub id: PresentationId,
69    /// Owning application object identity, if resolved.
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub object: Option<String>,
72    /// Source order in the provider table.
73    pub order: u32,
74    /// Persisted tree expansion state.
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub expanded: Option<bool>,
77    /// Persisted object visibility.
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub visible: Option<bool>,
80    /// Display mode name or numeric code.
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub display_mode: Option<String>,
83    /// Selection rendering mode.
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub selection_style: Option<String>,
86    /// Line width in persisted display units.
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub line_width: Option<f64>,
89    /// Point size in persisted display units.
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub point_size: Option<f64>,
92    /// Remaining view properties by exact source property name.
93    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
94    pub properties: BTreeMap<String, String>,
95    /// Native view-provider record supplying this state.
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub native_ref: Option<String>,
98}
99
100// Named presentation layers and their model-item membership.
101
102use crate::ids::{
103    BodyId, CurveId, EdgeId, FaceId, LayerId, OccurrenceId, PmiId, PointId, ProductId, SurfaceId,
104    VertexId,
105};
106
107/// A model or presentation object assigned to a layer.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
109#[serde(tag = "kind", rename_all = "snake_case")]
110pub enum PresentationItem {
111    /// Shape body.
112    Body {
113        /// Assigned body.
114        body: BodyId,
115    },
116    /// Topological face.
117    Face {
118        /// Assigned face.
119        face: FaceId,
120    },
121    /// Topological edge.
122    Edge {
123        /// Assigned edge.
124        edge: EdgeId,
125    },
126    /// Topological vertex.
127    Vertex {
128        /// Assigned vertex.
129        vertex: VertexId,
130    },
131    /// Point carrier.
132    Point {
133        /// Assigned point.
134        point: PointId,
135    },
136    /// Curve carrier.
137    Curve {
138        /// Assigned curve.
139        curve: CurveId,
140    },
141    /// Surface carrier.
142    Surface {
143        /// Assigned surface.
144        surface: SurfaceId,
145    },
146    /// Product prototype.
147    Product {
148        /// Assigned product.
149        product: ProductId,
150    },
151    /// Product occurrence.
152    Occurrence {
153        /// Assigned occurrence.
154        occurrence: OccurrenceId,
155    },
156    /// PMI annotation.
157    Pmi {
158        /// Assigned PMI annotation.
159        annotation: PmiId,
160    },
161    /// Tessellation identity.
162    Tessellation {
163        /// Assigned tessellation identity.
164        tessellation: String,
165    },
166    /// Source item whose neutral target type is not modeled.
167    Source {
168        /// Stable source item identity.
169        source_id: String,
170    },
171}
172
173/// One named presentation layer.
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
175pub struct PresentationLayer {
176    /// Stable layer identity.
177    pub id: LayerId,
178    /// Layer name.
179    pub name: String,
180    /// Optional layer description.
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub description: Option<String>,
183    /// Assigned items in source order.
184    #[serde(default, skip_serializing_if = "Vec::is_empty")]
185    pub items: Vec<PresentationItem>,
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::document::CadIr;
192    use crate::report::Check;
193    use crate::units::Units;
194    use crate::validate;
195
196    #[test]
197    fn source_layer_items_validate_without_fabricated_geometry() {
198        let mut ir = CadIr::empty(Units::default());
199        ir.model.presentation_layers.push(PresentationLayer {
200            id: LayerId("test:presentation:layer#construction".into()),
201            name: "construction".into(),
202            description: None,
203            items: vec![PresentationItem::Source {
204                source_id: "#42".into(),
205            }],
206        });
207
208        assert!(validate(&ir, Vec::new()).is_ok());
209    }
210
211    #[test]
212    fn missing_typed_layer_item_is_invalid() {
213        let mut ir = CadIr::empty(Units::default());
214        ir.model.presentation_layers.push(PresentationLayer {
215            id: LayerId("test:presentation:layer#missing".into()),
216            name: "missing".into(),
217            description: None,
218            items: vec![PresentationItem::Face {
219                face: FaceId("test:model:face#missing".into()),
220            }],
221        });
222
223        assert!(validate(&ir, Vec::new())
224            .findings
225            .iter()
226            .any(|finding| finding.check == Check::Presentation));
227    }
228}