runmat_geometry_core/model/
regions.rs1use serde::{Deserialize, Serialize};
2
3use crate::selection::EntityKind;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct Region {
7 pub region_id: String,
8 pub name: String,
9 pub tag: Option<String>,
10 #[serde(default, skip_serializing_if = "Option::is_none")]
11 pub cad_ownership: Option<CadRegionOwnership>,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum CadSemanticKind {
17 Assembly,
18 Component,
19 Reference,
20 Body,
21 Compound,
22 Face,
23 Subshape,
24 Layer,
25 Color,
26 Material,
27 Shape,
28 Unknown,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct CadLabelRef {
34 pub label_entry: String,
35 pub name: String,
36 pub kind: CadSemanticKind,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct CadColorEvidence {
42 pub source: String,
43 pub color_type: String,
44 pub hex_rgba: String,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct CadPhysicalMaterialEvidence {
50 pub label_entry: String,
51 pub name: String,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub description: Option<String>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub density: Option<String>,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub density_name: Option<String>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub density_value_type: Option<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct CadRegionOwnership {
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub face_id: Option<u64>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub label: Option<CadLabelRef>,
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
70 pub owner_path: Vec<CadLabelRef>,
71 #[serde(default, skip_serializing_if = "Vec::is_empty")]
72 pub layers: Vec<String>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub color: Option<CadColorEvidence>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub material: Option<CadPhysicalMaterialEvidence>,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct EntityIdRange {
82 pub start: u64,
83 pub count: u64,
84}
85
86impl EntityIdRange {
87 pub fn new(start: u64, count: u64) -> Self {
88 Self { start, count }
89 }
90
91 pub fn end_exclusive(&self) -> Option<u64> {
92 self.start.checked_add(self.count)
93 }
94
95 pub fn contains(&self, entity_id: u64) -> bool {
96 self.end_exclusive()
97 .is_some_and(|end| entity_id >= self.start && entity_id < end)
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct RegionEntityMapping {
104 pub region_id: String,
105 pub mesh_id: String,
106 pub entity_kind: EntityKind,
107 pub ranges: Vec<EntityIdRange>,
108}
109
110impl RegionEntityMapping {
111 pub fn new(
112 region_id: impl Into<String>,
113 mesh_id: impl Into<String>,
114 entity_kind: EntityKind,
115 ranges: Vec<EntityIdRange>,
116 ) -> Self {
117 Self {
118 region_id: region_id.into(),
119 mesh_id: mesh_id.into(),
120 entity_kind,
121 ranges,
122 }
123 }
124
125 pub fn all_faces(
126 region_id: impl Into<String>,
127 mesh_id: impl Into<String>,
128 face_count: u64,
129 ) -> Self {
130 Self::new(
131 region_id,
132 mesh_id,
133 EntityKind::Face,
134 if face_count == 0 {
135 Vec::new()
136 } else {
137 vec![EntityIdRange::new(0, face_count)]
138 },
139 )
140 }
141
142 pub fn entity_count(&self) -> u64 {
143 self.ranges.iter().map(|range| range.count).sum()
144 }
145
146 pub fn contains_entity(&self, entity_id: u64) -> bool {
147 self.ranges.iter().any(|range| range.contains(entity_id))
148 }
149}