ifc_geometry/constraint/
grid.rs1use crate::error::GeometryResult;
17use crate::slots::Slots;
18use ifc_model::{Entity, EntityId};
19
20pub(crate) mod axis_slot {
22 pub const AXIS_TAG: usize = 0;
24 pub const AXIS_CURVE: usize = 1;
26 pub const SAME_SENSE: usize = 2;
28}
29
30pub(crate) mod intersection_slot {
32 pub const INTERSECTING_AXES: usize = 0;
34 pub const OFFSET_DISTANCES: usize = 1;
36}
37
38pub(crate) mod placement_slot {
40 pub const PLACEMENT_LOCATION: usize = 0;
42 pub const PLACEMENT_REF_DIRECTION: usize = 1;
44}
45
46#[derive(Debug, Clone, Copy)]
48pub struct GridAxis<'m> {
49 slots: Slots<'m>,
50}
51
52impl<'m> GridAxis<'m> {
53 pub fn new(id: EntityId, entity: &'m Entity) -> Self {
55 Self {
56 slots: Slots::new(id, entity),
57 }
58 }
59
60 pub fn id(&self) -> EntityId {
62 self.slots.id()
63 }
64
65 pub fn tag(&self) -> Option<&'m str> {
67 self.slots.opt(axis_slot::AXIS_TAG)?.as_text()
68 }
69
70 pub fn curve(&self) -> GeometryResult<EntityId> {
72 self.slots.req_ref(axis_slot::AXIS_CURVE, "AxisCurve")
73 }
74
75 pub fn same_sense(&self) -> bool {
80 self.slots.opt_bool(axis_slot::SAME_SENSE).unwrap_or(true)
81 }
82}
83
84#[derive(Debug, Clone, Copy)]
89pub struct VirtualGridIntersection<'m> {
90 slots: Slots<'m>,
91}
92
93impl<'m> VirtualGridIntersection<'m> {
94 pub fn new(id: EntityId, entity: &'m Entity) -> Self {
96 Self {
97 slots: Slots::new(id, entity),
98 }
99 }
100
101 pub fn id(&self) -> EntityId {
103 self.slots.id()
104 }
105
106 pub fn axes(&self) -> GeometryResult<Vec<EntityId>> {
112 self.slots
113 .req_ref_list(intersection_slot::INTERSECTING_AXES, "IntersectingAxes")
114 }
115
116 pub fn offsets(&self) -> GeometryResult<Vec<f64>> {
121 self.slots
122 .req_f64_list(intersection_slot::OFFSET_DISTANCES, "OffsetDistances")
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
128pub struct GridPlacement<'m> {
129 slots: Slots<'m>,
130}
131
132impl<'m> GridPlacement<'m> {
133 pub fn new(id: EntityId, entity: &'m Entity) -> Self {
135 Self {
136 slots: Slots::new(id, entity),
137 }
138 }
139
140 pub fn id(&self) -> EntityId {
142 self.slots.id()
143 }
144
145 pub fn location(&self) -> GeometryResult<EntityId> {
147 self.slots
148 .req_ref(placement_slot::PLACEMENT_LOCATION, "PlacementLocation")
149 }
150
151 pub fn ref_direction(&self) -> Option<EntityId> {
157 self.slots.opt_ref(placement_slot::PLACEMENT_REF_DIRECTION)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use ifc_model::Value;
165
166 #[test]
167 fn reads_axis_tag_and_curve() {
168 let e = Entity::new(
169 "IFCGRIDAXIS",
170 vec![
171 Value::Text("C".into()),
172 Value::Ref(EntityId(5)),
173 Value::Bool(true),
174 ],
175 );
176 let axis = GridAxis::new(EntityId(1), &e);
177 assert_eq!(axis.tag(), Some("C"));
178 assert_eq!(axis.curve().unwrap(), EntityId(5));
179 assert!(axis.same_sense());
180 }
181
182 #[test]
184 fn absent_same_sense_defaults_to_true() {
185 let e = Entity::new(
186 "IFCGRIDAXIS",
187 vec![
188 Value::Text("4".into()),
189 Value::Ref(EntityId(5)),
190 Value::Null,
191 ],
192 );
193 assert!(GridAxis::new(EntityId(1), &e).same_sense());
194 }
195
196 #[test]
197 fn reads_intersection_axes_and_offsets() {
198 let e = Entity::new(
199 "IFCVIRTUALGRIDINTERSECTION",
200 vec![
201 Value::List(vec![Value::Ref(EntityId(1)), Value::Ref(EntityId(2))]),
202 Value::List(vec![Value::Real(0.5), Value::Real(-0.25)]),
203 ],
204 );
205 let x = VirtualGridIntersection::new(EntityId(3), &e);
206 assert_eq!(x.axes().unwrap().len(), 2);
207 assert_eq!(x.offsets().unwrap(), vec![0.5, -0.25]);
208 }
209
210 #[test]
211 fn ref_direction_is_optional() {
212 let e = Entity::new(
213 "IFCGRIDPLACEMENT",
214 vec![Value::Ref(EntityId(9)), Value::Null],
215 );
216 let p = GridPlacement::new(EntityId(1), &e);
217 assert_eq!(p.location().unwrap(), EntityId(9));
218 assert_eq!(p.ref_direction(), None);
219 }
220}