1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum FunctionStatus {
10 NativePrimitive,
12 Scaffolded,
14 Implemented,
18 NotApplicable,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct FunctionSupport {
29 pub name: &'static str,
31 pub owner: &'static str,
33 pub status: FunctionStatus,
35}
36
37const NATIVE: FunctionStatus = FunctionStatus::NativePrimitive;
38#[allow(dead_code)]
43const SCAFFOLDED: FunctionStatus = FunctionStatus::Scaffolded;
44const NOT_APPLICABLE: FunctionStatus = FunctionStatus::NotApplicable;
45const IMPLEMENTED: FunctionStatus = FunctionStatus::Implemented;
46
47pub const FUNCTIONS: &[FunctionSupport] = &[
49 FunctionSupport {
50 name: "IfcAssociatedSurface",
51 owner: "surface::basis",
52 status: IMPLEMENTED,
53 },
54 FunctionSupport {
55 name: "IfcBaseAxis",
56 owner: "resource::axes",
57 status: IMPLEMENTED,
58 },
59 FunctionSupport {
60 name: "IfcBuild2Axes",
61 owner: "resource::functions",
62 status: IMPLEMENTED,
63 },
64 FunctionSupport {
65 name: "IfcBuildAxes",
66 owner: "transform",
67 status: IMPLEMENTED,
68 },
69 FunctionSupport {
70 name: "IfcConsecutiveSegments",
71 owner: "rules::express",
72 status: IMPLEMENTED,
73 },
74 FunctionSupport {
75 name: "IfcConstraintsParamBSpline",
76 owner: "rules::express",
77 status: IMPLEMENTED,
78 },
79 FunctionSupport {
80 name: "IfcCrossProduct",
81 owner: "axiolid_core::Vec3::cross",
82 status: NATIVE,
83 },
84 FunctionSupport {
85 name: "IfcCurveDim",
86 owner: "rules::dimension",
87 status: IMPLEMENTED,
88 },
89 FunctionSupport {
90 name: "IfcCurveWeightsPositive",
91 owner: "rules::bspline",
92 status: IMPLEMENTED,
93 },
94 FunctionSupport {
95 name: "IfcDotProduct",
96 owner: "axiolid_core::Vec3::dot",
97 status: NATIVE,
98 },
99 FunctionSupport {
100 name: "IfcFirstProjAxis",
101 owner: "transform",
102 status: IMPLEMENTED,
103 },
104 FunctionSupport {
105 name: "IfcGetBasisSurface",
106 owner: "surface::basis",
107 status: IMPLEMENTED,
108 },
109 FunctionSupport {
110 name: "IfcListToArray",
111 owner: "curve::bspline",
112 status: NOT_APPLICABLE,
113 },
114 FunctionSupport {
115 name: "IfcMakeArrayOfArray",
116 owner: "surface::bspline",
117 status: NOT_APPLICABLE,
118 },
119 FunctionSupport {
120 name: "IfcNormalise",
121 owner: "axiolid_core::Vec3::normalize",
122 status: NATIVE,
123 },
124 FunctionSupport {
125 name: "IfcOrthogonalComplement",
126 owner: "resource::functions",
127 status: IMPLEMENTED,
128 },
129 FunctionSupport {
130 name: "IfcSameAxis2Placement",
131 owner: "resource::functions",
132 status: IMPLEMENTED,
133 },
134 FunctionSupport {
135 name: "IfcSameCartesianPoint",
136 owner: "resource::functions",
137 status: IMPLEMENTED,
138 },
139 FunctionSupport {
140 name: "IfcSameDirection",
141 owner: "resource::functions",
142 status: IMPLEMENTED,
143 },
144 FunctionSupport {
145 name: "IfcSameValue",
146 owner: "resource::functions",
147 status: IMPLEMENTED,
148 },
149 FunctionSupport {
150 name: "IfcScalarTimesVector",
151 owner: "axiolid_core::Vec3::mul",
152 status: NATIVE,
153 },
154 FunctionSupport {
155 name: "IfcSecondProjAxis",
156 owner: "transform",
157 status: IMPLEMENTED,
158 },
159 FunctionSupport {
160 name: "IfcSurfaceWeightsPositive",
161 owner: "rules::bspline",
162 status: IMPLEMENTED,
163 },
164 FunctionSupport {
165 name: "IfcVectorDifference",
166 owner: "axiolid_core::Vec3::sub",
167 status: NATIVE,
168 },
169 FunctionSupport {
170 name: "IfcVectorSum",
171 owner: "axiolid_core::Vec3::add",
172 status: NATIVE,
173 },
174 FunctionSupport {
175 name: "IfcPointListDim",
176 owner: "resource::functions",
177 status: IMPLEMENTED,
178 },
179 FunctionSupport {
180 name: "IfcTaperedSweptAreaProfiles",
181 owner: "rules::surface",
182 status: IMPLEMENTED,
183 },
184 FunctionSupport {
185 name: "IfcCorrectLocalPlacement",
186 owner: "rules::placement",
187 status: IMPLEMENTED,
188 },
189];
190
191const DEFAULT_EPSILON: f64 = 0.000_001;
193
194#[must_use]
203pub fn same_value(a: f64, b: f64, epsilon: Option<f64>) -> bool {
204 let eps = epsilon.unwrap_or(DEFAULT_EPSILON);
205 a + eps > b && a < b + eps
206}
207
208#[must_use]
215pub fn same_components(a: &[f64], b: &[f64], epsilon: Option<f64>) -> bool {
216 let at = |v: &[f64], i: usize| v.get(i).copied().unwrap_or(0.0);
217 (0..3).all(|i| same_value(at(a, i), at(b, i), epsilon))
218}
219
220#[must_use]
225pub fn point_list_dim(type_name: &str) -> Option<usize> {
226 match type_name.to_ascii_uppercase().as_str() {
227 "IFCCARTESIANPOINTLIST2D" => Some(2),
228 "IFCCARTESIANPOINTLIST3D" => Some(3),
229 _ => None,
230 }
231}
232
233#[must_use]
237pub fn orthogonal_complement(v: &[f64]) -> Option<[f64; 2]> {
238 match v {
239 [x, y] => Some([-*y, *x]),
240 _ => None,
241 }
242}
243
244#[must_use]
249pub fn build_2_axes(ref_direction: Option<[f64; 2]>) -> ([f64; 2], [f64; 2]) {
250 let x = ref_direction
251 .and_then(|d| {
252 let len = d[0].hypot(d[1]);
253 (len.is_finite() && len > 0.0).then(|| [d[0] / len, d[1] / len])
254 })
255 .unwrap_or([1.0, 0.0]);
256 (x, [-x[1], x[0]])
257}
258
259#[must_use]
275pub fn same_axis2_placement(
276 a_axes: (&[f64], &[f64]),
277 a_location: &[f64],
278 b_axes: (&[f64], &[f64]),
279 b_location: &[f64],
280 epsilon: Option<f64>,
281) -> bool {
282 same_components(a_axes.0, b_axes.0, epsilon)
283 && same_components(a_axes.1, b_axes.1, epsilon)
284 && same_components(a_location, b_location, epsilon)
285}
286
287pub fn function_support(name: &str) -> Option<&'static FunctionSupport> {
289 FUNCTIONS
290 .iter()
291 .find(|item| item.name.eq_ignore_ascii_case(name))
292}
293
294#[cfg(test)]
295mod function_tests {
296 use super::*;
297
298 #[test]
302 fn same_value_boundary_is_exclusive() {
303 assert!(same_value(1.0, 1.0 + 0.5e-6, None));
304 assert!(!same_value(1.0, 1.0 + 1e-6, None), "exactly eps apart");
305 assert!(!same_value(1.0, 1.0 + 2e-6, None));
306 assert!(!same_value(1.0 + 1e-6, 1.0, None));
308 }
309
310 #[test]
312 fn same_value_default_epsilon_is_not_exact_equality() {
313 assert!(same_value(2.0, 2.0 + 1e-9, None));
314 assert!(!same_value(2.0, 2.0 + 1e-9, Some(1e-12)));
315 }
316
317 #[test]
320 fn missing_third_component_is_zero() {
321 assert!(same_components(&[1.0, 2.0], &[1.0, 2.0, 0.0], None));
322 assert!(!same_components(&[1.0, 2.0], &[1.0, 2.0, 5.0], None));
323 }
324
325 #[test]
326 fn point_list_dim_refuses_unknown_types() {
327 assert_eq!(point_list_dim("IfcCartesianPointList2D"), Some(2));
328 assert_eq!(point_list_dim("IFCCARTESIANPOINTLIST3D"), Some(3));
329 assert_eq!(point_list_dim("IFCCARTESIANPOINT"), None);
330 }
331
332 #[test]
335 fn orthogonal_complement_is_2d_only() {
336 assert_eq!(orthogonal_complement(&[1.0, 0.0]), Some([0.0, 1.0]));
337 assert_eq!(orthogonal_complement(&[0.0, 1.0]), Some([-1.0, 0.0]));
338 assert_eq!(orthogonal_complement(&[1.0, 0.0, 0.0]), None);
339 }
340
341 #[test]
343 fn build_2_axes_defaults_and_normalises() {
344 assert_eq!(build_2_axes(None), ([1.0, 0.0], [0.0, 1.0]));
345 assert_eq!(build_2_axes(Some([0.0, 0.0])), ([1.0, 0.0], [0.0, 1.0]));
346 let (x, y) = build_2_axes(Some([5.0, 0.0]));
347 assert!((x[0] - 1.0).abs() < 1e-12, "normalised: {x:?}");
348 assert!((y[1] - 1.0).abs() < 1e-12, "perpendicular: {y:?}");
349 }
350
351 #[test]
355 fn same_axis2_placement_actually_compares_locations() {
356 let x = [1.0, 0.0, 0.0];
357 let y = [0.0, 1.0, 0.0];
358 assert!(same_axis2_placement(
359 (&x, &y),
360 &[0.0, 0.0, 0.0],
361 (&x, &y),
362 &[0.0, 0.0, 0.0],
363 None
364 ));
365 assert!(!same_axis2_placement(
368 (&x, &y),
369 &[0.0, 0.0, 0.0],
370 (&x, &y),
371 &[5.0, 0.0, 0.0],
372 None
373 ));
374 assert!(!same_axis2_placement(
376 (&x, &y),
377 &[0.0, 0.0, 0.0],
378 (&y, &x),
379 &[0.0, 0.0, 0.0],
380 None
381 ));
382 }
383}