1use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
10#[serde(transparent)]
11pub struct PresentationId(pub String);
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
15pub struct CameraState {
16 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub position: Option<[f64; 3]>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub orientation: Option<[f64; 4]>,
22 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
24 pub properties: BTreeMap<String, String>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
29pub struct PresentationState {
30 pub kind: String,
32 pub order: u32,
34 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
36 pub attributes: BTreeMap<String, String>,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
39 pub assets: Vec<String>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
44pub struct PresentationDocument {
45 pub id: PresentationId,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub schema_version: Option<u32>,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub active_view: Option<String>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub camera: Option<CameraState>,
56 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 pub states: Vec<PresentationState>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub native_ref: Option<String>,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
66pub struct ViewPresentation {
67 pub id: PresentationId,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub object: Option<String>,
72 pub order: u32,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub expanded: Option<bool>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub visible: Option<bool>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub display_mode: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub selection_style: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub line_width: Option<f64>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub point_size: Option<f64>,
92 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
94 pub properties: BTreeMap<String, String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub native_ref: Option<String>,
98}
99
100use crate::ids::{
103 BodyId, CurveId, EdgeId, FaceId, LayerId, OccurrenceId, PmiId, PointId, ProductId, SurfaceId,
104 VertexId,
105};
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
109#[serde(tag = "kind", rename_all = "snake_case")]
110pub enum PresentationItem {
111 Body {
113 body: BodyId,
115 },
116 Face {
118 face: FaceId,
120 },
121 Edge {
123 edge: EdgeId,
125 },
126 Vertex {
128 vertex: VertexId,
130 },
131 Point {
133 point: PointId,
135 },
136 Curve {
138 curve: CurveId,
140 },
141 Surface {
143 surface: SurfaceId,
145 },
146 Product {
148 product: ProductId,
150 },
151 Occurrence {
153 occurrence: OccurrenceId,
155 },
156 Pmi {
158 annotation: PmiId,
160 },
161 Tessellation {
163 tessellation: String,
165 },
166 Source {
168 source_id: String,
170 },
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
175pub struct PresentationLayer {
176 pub id: LayerId,
178 pub name: String,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub description: Option<String>,
183 #[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}