Skip to main content

jagua_rs/io/
ext_repr.rs

1use crate::geometry::DTransformation;
2use serde::{Deserialize, Serialize};
3
4/// External representation of an [`Item`](crate::entities::Item).
5#[derive(Serialize, Clone)]
6pub struct ExtItem {
7    /// Unique external identifier of the item.
8    pub id: u64,
9    /// Required orientation permissions in original item coordinates.
10    pub orientation: ExtOrientation,
11    /// Shape of the item. Polygons with holes and multipolygons are not supported.
12    pub shape: ExtShape,
13    /// Minimum required quality in `0..N_QUALITIES`, validated at import.
14    /// Missing or null requires full quality. Holes always block placement.
15    pub min_quality: Option<usize>,
16}
17
18/// Orientation permissions in degrees. Both this object and its rotation field are required.
19#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
20#[serde(deny_unknown_fields)]
21pub struct ExtOrientation {
22    /// Permitted rotations, applied after optional reflection.
23    pub rotation: ExtRotation,
24    /// Optional reflection axes through the original item's local origin,
25    /// in degrees counterclockwise from its positive x-axis, before rotation.
26    /// 0° is the horizontal x-axis: `(x, y)` becomes `(x, -y)`.
27    /// 90° is the vertical y-axis: `(x, y)` becomes `(-x, y)`.
28    /// Missing or empty disables reflection.
29    /// Choose no reflection or one axis; equivalent axes are normalized modulo 180°.
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub reflection_axes: Vec<f32>,
32}
33
34/// Explicit rotation modes, validated at import.
35/// Lists and stepped expansions are limited to 65,536 angles.
36#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
37#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
38pub enum ExtRotation {
39    /// A nonempty list of finite angles in degrees. Zero is not implicit.
40    /// Equivalent angles are normalized modulo 360 and deduplicated.
41    Discrete { angles: Vec<f32> },
42    /// Zero-based increments covering one turn, excluding the duplicate endpoint.
43    /// The finite step must be in (0, 360]. In f64 arithmetic, round `360 / step`
44    /// to a count and require `abs(count * step - 360) <= 360 * f32::EPSILON`.
45    /// Expand by index as `i * 360 / count`, avoiding accumulated rounding error.
46    Stepped { step: f32 },
47    /// Any rotation angle. No null or empty-list sentinel is used.
48    Continuous {},
49}
50
51impl<'de> Deserialize<'de> for ExtItem {
52    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
53        // Reject obsolete permissions even in flattened demand items, while allowing
54        // unrelated metadata such as source filenames.
55        #[derive(Deserialize)]
56        struct Input {
57            id: u64,
58            orientation: ExtOrientation,
59            shape: ExtShape,
60            min_quality: Option<usize>,
61            #[serde(
62                default,
63                rename = "allowed_orientations",
64                alias = "allowed_rotations",
65                alias = "allowed_reflection_axes",
66                deserialize_with = "reject_legacy"
67            )]
68            legacy: (),
69        }
70        fn reject_legacy<'de, D: serde::Deserializer<'de>>(_: D) -> Result<(), D::Error> {
71            Err(serde::de::Error::custom(
72                "legacy orientation fields are not supported; use orientation.rotation",
73            ))
74        }
75        let Input {
76            id,
77            orientation,
78            shape,
79            min_quality,
80            legacy: (),
81        } = Input::deserialize(deserializer)?;
82        Ok(Self {
83            id,
84            orientation,
85            shape,
86            min_quality,
87        })
88    }
89}
90
91/// External representation of a [`Container`](crate::entities::Container).
92/// Items can be placed inside containers.
93#[derive(Serialize, Deserialize, Clone)]
94pub struct ExtContainer {
95    /// Unique identifier of the container
96    pub id: u64,
97    /// Shape of the container
98    pub shape: ExtShape,
99    /// Zones within the container with varying quality. Holes in the container shape are treated as zones with quality 0.
100    #[serde(skip_serializing_if = "Vec::is_empty", default)]
101    pub zones: Vec<ExtQualityZone>,
102}
103
104/// Various ways to represent a shape
105#[derive(Serialize, Deserialize, Clone)]
106#[serde(tag = "type", content = "data")]
107#[serde(rename_all = "snake_case")]
108pub enum ExtShape {
109    /// Axis-aligned rectangle. With its left bottom corner at (`x_min`, `y_min`), a width and height
110    Rectangle {
111        x_min: f32,
112        y_min: f32,
113        width: f32,
114        height: f32,
115    },
116    /// Polygon with a single outer boundary
117    SimplePolygon(ExtSPolygon),
118    /// Polygon with a single outer boundary and a set of holes
119    Polygon(ExtPolygon),
120    /// Multiple disjoint polygons
121    MultiPolygon(Vec<ExtPolygon>),
122}
123
124/// A polygon represented as an outer boundary and a list of holes
125#[derive(Serialize, Deserialize, Clone)]
126pub struct ExtPolygon {
127    /// The outer boundary of the polygon
128    pub outer: ExtSPolygon,
129    /// A list of holes in the polygon
130    #[serde(default)]
131    pub inner: Vec<ExtSPolygon>,
132}
133
134/// External representation of a [`SPolygon`](crate::geometry::primitives::SPolygon).
135/// A polygon with no holes and no self-intersections.
136#[derive(Serialize, Deserialize, Clone)]
137pub struct ExtSPolygon(pub Vec<(f32, f32)>);
138
139/// A zone with a specific quality level
140#[derive(Serialize, Deserialize, Clone)]
141pub struct ExtQualityZone {
142    /// The quality level of this zone
143    pub quality: usize,
144    /// The polygon shape of this zone
145    pub shape: ExtShape,
146}
147
148/// External representation of a [`Layout`](crate::entities::Layout).
149/// A layout consists of a container with items placed in a specific configuration.
150#[derive(Serialize, Deserialize, Clone)]
151pub struct ExtLayout {
152    /// The container that was used
153    pub container_id: u64,
154    /// The items placed in the container and where they were placed
155    pub placed_items: Vec<ExtPlacedItem>,
156    /// Some statistics about the layout
157    pub density: f32,
158}
159
160/// External representation of a [`PlacedItem`](crate::entities::PlacedItem).
161/// An item placed in a container with a specific transformation.
162#[derive(Serialize, Deserialize, Clone)]
163pub struct ExtPlacedItem {
164    /// The id of the item in the instance
165    pub item_id: u64,
166    /// The transformation applied to the item to place it in the container
167    pub transformation: ExtTransformation,
168}
169
170/// Optional x-axis reflection, followed by rotation and translation.
171/// The canonical x-axis reflection is independent of the item's permitted input axes:
172/// reflecting across axis `a` then rotating by `r` is exported with rotation `r + 2*a`.
173#[derive(Serialize, Deserialize, Clone)]
174pub struct ExtTransformation {
175    /// Negate the original local y coordinate before rotation. Defaults to false.
176    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
177    pub reflected: bool,
178    /// The rotation angle in degrees
179    pub rotation: f32,
180    /// The translation vector (x, y)
181    pub translation: (f32, f32),
182}
183
184impl From<DTransformation> for ExtTransformation {
185    fn from(dt: DTransformation) -> Self {
186        ExtTransformation {
187            reflected: dt.reflected,
188            rotation: dt.rotation().to_degrees(),
189            translation: dt.translation(),
190        }
191    }
192}
193
194impl From<ExtTransformation> for DTransformation {
195    fn from(ext_dt: ExtTransformation) -> Self {
196        DTransformation::new(ext_dt.rotation.to_radians(), ext_dt.translation)
197            .with_reflection(ext_dt.reflected)
198    }
199}