Skip to main content

ifc_geometry/authoring/
placement.rs

1//! Points, directions and axis placements.
2//!
3//! These are the leaves every other geometry entity references, so the
4//! validation here is the cheapest place to stop a malformed body.
5
6use ifc_model::{Entity, EntityId, Transaction, Value};
7
8use crate::error::GeometryError;
9use crate::resource::direction::slot as direction_slot;
10use crate::resource::placement::slot as placement_slot;
11use crate::resource::point::slot as point_slot;
12
13use super::{invalid, reals, require_finite};
14
15/// Stage an `IfcCartesianPoint`.
16///
17/// Accepts 1-3 coordinates: IFC implies a point's dimension from the list
18/// length rather than declaring it.
19///
20/// # Errors
21///
22/// Refuses a non-finite coordinate, or a count outside 1..=3. A NaN here
23/// would otherwise propagate into every placement referencing the point.
24pub fn cartesian_point(tx: &mut Transaction, coords: &[f64]) -> Result<EntityId, GeometryError> {
25    if coords.is_empty() || coords.len() > 3 {
26        return Err(invalid(
27            "IFCCARTESIANPOINT",
28            "Coordinates",
29            format!("expected 1..=3 coordinates, got {}", coords.len()),
30        ));
31    }
32    require_finite("IFCCARTESIANPOINT", "Coordinates", coords)?;
33    let mut attrs = vec![Value::Null];
34    attrs[point_slot::cartesian_point::COORDINATES] = reals(coords);
35    Ok(tx.create(Entity::new("IFCCARTESIANPOINT", attrs)))
36}
37
38/// Stage an `IfcDirection`.
39///
40/// Ratios are written exactly as given. IFC direction ratios are *not*
41/// required to be unit length, and the reader documents that it returns them
42/// unnormalized, so normalizing here would silently disagree with the read
43/// direction and discard a magnitude the file was entitled to express.
44///
45/// # Errors
46///
47/// Refuses a non-finite ratio, a count outside 2..=3, and an all-zero vector,
48/// which denotes no direction at all and cannot be normalized downstream.
49pub fn direction(tx: &mut Transaction, ratios: &[f64]) -> Result<EntityId, GeometryError> {
50    if ratios.len() < 2 || ratios.len() > 3 {
51        return Err(invalid(
52            "IFCDIRECTION",
53            "DirectionRatios",
54            format!("expected 2..=3 ratios, got {}", ratios.len()),
55        ));
56    }
57    require_finite("IFCDIRECTION", "DirectionRatios", ratios)?;
58    if ratios.iter().all(|r| *r == 0.0) {
59        return Err(invalid(
60            "IFCDIRECTION",
61            "DirectionRatios",
62            "all ratios are zero, which denotes no direction",
63        ));
64    }
65    let mut attrs = vec![Value::Null];
66    attrs[direction_slot::direction::DIRECTION_RATIOS] = reals(ratios);
67    Ok(tx.create(Entity::new("IFCDIRECTION", attrs)))
68}
69
70/// Stage an `IfcAxis2Placement3D`.
71///
72/// `axis` is the local Z, `ref_direction` the local X. Both are optional in
73/// IFC; omitting them means the implicit global axes, which is a meaningful
74/// default rather than missing data, so `None` is written as `$`.
75pub fn axis2_placement_3d(
76    tx: &mut Transaction,
77    location: EntityId,
78    axis: Option<EntityId>,
79    ref_direction: Option<EntityId>,
80) -> EntityId {
81    let mut attrs = vec![Value::Null, Value::Null, Value::Null];
82    attrs[placement_slot::LOCATION] = Value::Ref(location);
83    attrs[placement_slot::axis2_3d::AXIS] = axis.map_or(Value::Null, Value::Ref);
84    attrs[placement_slot::axis2_3d::REF_DIRECTION] = ref_direction.map_or(Value::Null, Value::Ref);
85    tx.create(Entity::new("IFCAXIS2PLACEMENT3D", attrs))
86}
87
88/// Stage an `IfcAxis2Placement2D`.
89///
90/// Carries no `Axis`: a 2D placement has only a location and a reference
91/// direction, so this is a shorter record than its 3D counterpart rather
92/// than the same one with a hole in it.
93pub fn axis2_placement_2d(
94    tx: &mut Transaction,
95    location: EntityId,
96    ref_direction: Option<EntityId>,
97) -> EntityId {
98    let mut attrs = vec![Value::Null, Value::Null];
99    attrs[placement_slot::LOCATION] = Value::Ref(location);
100    attrs[placement_slot::axis2_2d::REF_DIRECTION] = ref_direction.map_or(Value::Null, Value::Ref);
101    tx.create(Entity::new("IFCAXIS2PLACEMENT2D", attrs))
102}