ogeom_doc/attributes.rs
1//! The document's attribute layer: properties, materials, layers, and
2//! validation values.
3//!
4//! Everything here is *data about* shapes rather than geometry: the
5//! free-form key–value pairs an application pins to a face, the material a
6//! body is meant to be cut from, the layers a drawing organizes itself by,
7//! and the mass-property check values an exchange partner records so the
8//! receiver can verify a translation did not quietly lose a boss. The
9//! document stores and round-trips them; computing anything (a volume to
10//! compare against a validation record) stays with the code that owns the
11//! geometry.
12
13use ogeom_math::Point;
14
15use crate::structure::Colour;
16
17/// A user-defined property's value.
18#[derive(Debug, Clone, PartialEq)]
19pub enum PropertyValue {
20 /// Free text.
21 Text(String),
22 /// A number, meaning whatever the name says it means.
23 Number(f64),
24 /// A yes or a no.
25 Flag(bool),
26}
27
28/// One user-defined property: a name and a value.
29#[derive(Debug, Clone, PartialEq)]
30pub struct Property {
31 /// The key.
32 pub name: String,
33 /// The value.
34 pub value: PropertyValue,
35}
36
37/// A material in the document's own list.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub struct MaterialId(pub(crate) usize);
40
41impl MaterialId {
42 /// The position in the document's material list.
43 #[must_use]
44 pub fn index(&self) -> usize {
45 self.0
46 }
47}
48
49/// A material: what a body is meant to be made of.
50#[derive(Debug, Clone, PartialEq)]
51pub struct Material {
52 /// The name: "AISI 304", "PA12".
53 pub name: String,
54 /// Density in kilograms per cubic metre, where known.
55 pub density: Option<f64>,
56 /// A display colour, where one belongs to the material.
57 pub colour: Option<Colour>,
58}
59
60/// A layer in the document's own list.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub struct LayerId(pub(crate) usize);
63
64impl LayerId {
65 /// The position in the document's layer list.
66 #[must_use]
67 pub fn index(&self) -> usize {
68 self.0
69 }
70}
71
72/// A layer: a named grouping with a visibility flag.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct Layer {
75 /// The name.
76 pub name: String,
77 /// Whether the layer is shown.
78 pub visible: bool,
79}
80
81/// Mass-property check values, recorded so a receiver can verify a
82/// translation preserved the body they describe.
83#[derive(Debug, Clone, Copy, PartialEq)]
84pub struct ValidationProperties {
85 /// Enclosed volume, in the model's cubic length unit.
86 pub volume: f64,
87 /// Surface area, in the squared unit.
88 pub area: f64,
89 /// The centroid.
90 pub centroid: Point,
91}
92
93impl ValidationProperties {
94 /// Whether another set of values agrees within a relative tolerance,
95 /// the centroid compared against the body's own size, taken from the
96 /// cube root of its volume.
97 #[must_use]
98 pub fn agrees_with(&self, other: &Self, relative: f64) -> bool {
99 let close = |a: f64, b: f64, scale: f64| (a - b).abs() <= scale.abs().max(1.0) * relative;
100 let size = self.volume.abs().cbrt();
101 close(self.volume, other.volume, self.volume)
102 && close(self.area, other.area, self.area)
103 && close(self.centroid.x, other.centroid.x, size)
104 && close(self.centroid.y, other.centroid.y, size)
105 && close(self.centroid.z, other.centroid.z, size)
106 }
107}
108
109/// How a texture's image is laid onto a shape.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum TextureMapping {
112 /// By the surfaces' own parameters: `(u, v)` straight from the chart.
113 Parametric,
114 /// By a box around the shape, each face taking the projection of the
115 /// side it faces most.
116 Box,
117 /// By a cylinder about the shape's own longest axis.
118 Cylindrical,
119 /// By a sphere about the shape's centre.
120 Spherical,
121}
122
123/// A texture: an image and how it lands on the geometry.
124///
125/// The image itself is *named*, not carried. A kernel that read image files
126/// would have opinions about formats, colour spaces and decoding that
127/// belong to a renderer; what a document needs to persist and exchange is
128/// which image, laid on how, at what scale, which is what this is.
129#[derive(Debug, Clone, PartialEq)]
130pub struct Texture {
131 /// Where the image is: a path or a URI, as the file said it.
132 pub image: String,
133 /// How it is laid on.
134 pub mapping: TextureMapping,
135 /// Repeats across the mapping's own unit span.
136 pub repeat: (f64, f64),
137 /// Where the mapping starts, in the same units as `repeat`.
138 pub offset: (f64, f64),
139}
140
141impl Texture {
142 /// A texture laid on by the surfaces' own parameters, once across.
143 #[must_use]
144 pub fn image(image: impl Into<String>) -> Self {
145 Self {
146 image: image.into(),
147 mapping: TextureMapping::Parametric,
148 repeat: (1.0, 1.0),
149 offset: (0.0, 0.0),
150 }
151 }
152}
153
154/// A texture's place in the document.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub struct TextureId(pub(crate) usize);
157
158impl TextureId {
159 /// Its position in the document's texture list.
160 #[must_use]
161 pub const fn index(&self) -> usize {
162 self.0
163 }
164}