Skip to main content

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}