1use ifc_model::{Entity, EntityId, Transaction, Value};
27
28use crate::error::GeometryError;
29use crate::surface::bounded::{plane_slot, surface_slot, trimmed_slot};
30use crate::surface::bspline::slot as bspline_slot;
31use crate::surface::elementary::slot as elementary_slot;
32
33use super::std_profile::positive;
34use super::{invalid, refs, require_finite};
35
36pub fn spherical_surface(
42 tx: &mut Transaction,
43 position: EntityId,
44 radius: f64,
45) -> Result<EntityId, GeometryError> {
46 const T: &str = "IFCSPHERICALSURFACE";
47 positive(T, "Radius", radius)?;
48 let mut attrs = vec![Value::Null; 2];
49 attrs[elementary_slot::POSITION] = Value::Ref(position);
50 attrs[elementary_slot::RADIUS] = Value::Real(radius);
51 Ok(tx.create(Entity::new(T, attrs)))
52}
53
54pub fn toroidal_surface(
66 tx: &mut Transaction,
67 position: EntityId,
68 major_radius: f64,
69 minor_radius: f64,
70) -> Result<EntityId, GeometryError> {
71 const T: &str = "IFCTOROIDALSURFACE";
72 positive(T, "MajorRadius", major_radius)?;
73 positive(T, "MinorRadius", minor_radius)?;
74 let mut attrs = vec![Value::Null; 3];
75 attrs[elementary_slot::POSITION] = Value::Ref(position);
76 attrs[elementary_slot::MAJOR_RADIUS] = Value::Real(major_radius);
77 attrs[elementary_slot::MINOR_RADIUS] = Value::Real(minor_radius);
78 Ok(tx.create(Entity::new(T, attrs)))
79}
80
81pub fn curve_bounded_plane(
86 tx: &mut Transaction,
87 basis: EntityId,
88 outer_boundary: EntityId,
89 inner_boundaries: &[EntityId],
90) -> EntityId {
91 let mut attrs = vec![Value::Null; 3];
92 attrs[plane_slot::BASIS_SURFACE] = Value::Ref(basis);
93 attrs[plane_slot::OUTER_BOUNDARY] = Value::Ref(outer_boundary);
94 attrs[plane_slot::INNER_BOUNDARIES] = refs(inner_boundaries);
97 tx.create(Entity::new("IFCCURVEBOUNDEDPLANE", attrs))
98}
99
100pub fn curve_bounded_surface(
109 tx: &mut Transaction,
110 basis: EntityId,
111 boundaries: &[EntityId],
112 implicit_outer: bool,
113) -> Result<EntityId, GeometryError> {
114 const T: &str = "IFCCURVEBOUNDEDSURFACE";
115 if boundaries.is_empty() {
116 return Err(invalid(T, "Boundaries", "expected at least one boundary"));
117 }
118 let mut attrs = vec![Value::Null; 3];
119 attrs[surface_slot::BASIS_SURFACE] = Value::Ref(basis);
120 attrs[surface_slot::BOUNDARIES] = refs(boundaries);
121 attrs[surface_slot::IMPLICIT_OUTER] = Value::Bool(implicit_outer);
122 Ok(tx.create(Entity::new(T, attrs)))
123}
124
125pub fn rectangular_trimmed_surface(
137 tx: &mut Transaction,
138 basis: EntityId,
139 u: (f64, f64),
140 v: (f64, f64),
141) -> Result<EntityId, GeometryError> {
142 const T: &str = "IFCRECTANGULARTRIMMEDSURFACE";
143 let (u1, u2) = u;
144 let (v1, v2) = v;
145 require_finite(T, "U1", &[u1, u2])?;
146 require_finite(T, "V1", &[v1, v2])?;
147 if u1 == u2 {
148 return Err(invalid(T, "U1", "U1 and U2 must differ"));
149 }
150 if v1 == v2 {
151 return Err(invalid(T, "V1", "V1 and V2 must differ"));
152 }
153
154 let mut attrs = vec![Value::Null; 7];
155 attrs[trimmed_slot::BASIS_SURFACE] = Value::Ref(basis);
156 attrs[trimmed_slot::U1] = parameter(u1);
157 attrs[trimmed_slot::V1] = parameter(v1);
158 attrs[trimmed_slot::U2] = parameter(u2);
159 attrs[trimmed_slot::V2] = parameter(v2);
160 attrs[trimmed_slot::USENSE] = Value::Bool(u2 > u1);
163 attrs[trimmed_slot::VSENSE] = Value::Bool(v2 > v1);
164 Ok(tx.create(Entity::new(T, attrs)))
165}
166
167fn parameter(value: f64) -> Value {
172 Value::Real(value)
173}
174
175#[derive(Debug, Clone, Copy)]
177pub struct SurfaceKnots<'a> {
178 pub multiplicities: &'a [i64],
180 pub knots: &'a [f64],
182}
183
184#[derive(Debug, Clone, Copy)]
189pub struct SurfaceBasis<'a> {
190 pub degree: (i64, i64),
192 pub u: SurfaceKnots<'a>,
194 pub v: SurfaceKnots<'a>,
196 pub form: &'a str,
198 pub knot_spec: &'a str,
200}
201
202pub fn bspline_surface_with_knots(
214 tx: &mut Transaction,
215 control_points: &[&[EntityId]],
216 basis: SurfaceBasis<'_>,
217) -> Result<EntityId, GeometryError> {
218 const T: &str = "IFCBSPLINESURFACEWITHKNOTS";
219 let attrs = surface_attrs(T, control_points, basis)?;
220 Ok(tx.create(Entity::new(T, attrs)))
221}
222
223pub fn rational_bspline_surface_with_knots(
230 tx: &mut Transaction,
231 control_points: &[&[EntityId]],
232 basis: SurfaceBasis<'_>,
233 weights: &[&[f64]],
234) -> Result<EntityId, GeometryError> {
235 const T: &str = "IFCRATIONALBSPLINESURFACEWITHKNOTS";
236 if weights.len() != control_points.len() {
237 return Err(invalid(
238 T,
239 "WeightsData",
240 format!(
241 "{} weight rows for {} control point rows",
242 weights.len(),
243 control_points.len()
244 ),
245 ));
246 }
247 for (index, (row, points)) in weights.iter().zip(control_points).enumerate() {
248 if row.len() != points.len() {
249 return Err(invalid(
250 T,
251 "WeightsData",
252 format!(
253 "weight row {index} has {} entries, its control row has {}",
254 row.len(),
255 points.len()
256 ),
257 ));
258 }
259 require_finite(T, "WeightsData", row)?;
260 }
261 let mut attrs = surface_attrs(T, control_points, basis)?;
262 attrs.push(Value::List(
263 weights
264 .iter()
265 .map(|row| Value::List(row.iter().copied().map(Value::Real).collect()))
266 .collect(),
267 ));
268 Ok(tx.create(Entity::new(T, attrs)))
269}
270
271fn surface_attrs(
273 type_name: &'static str,
274 control_points: &[&[EntityId]],
275 basis: SurfaceBasis<'_>,
276) -> Result<Vec<Value>, GeometryError> {
277 let SurfaceBasis {
278 degree: (u_degree, v_degree),
279 u,
280 v,
281 form,
282 knot_spec,
283 } = basis;
284 let rows = control_points.len();
285 if rows < 2 {
286 return Err(invalid(
287 type_name,
288 "ControlPointsList",
289 format!("expected at least 2 rows, got {rows}"),
290 ));
291 }
292 let columns = control_points[0].len();
293 if columns < 2 {
294 return Err(invalid(
295 type_name,
296 "ControlPointsList",
297 format!("expected at least 2 columns, got {columns}"),
298 ));
299 }
300 if let Some(bad) = control_points.iter().position(|row| row.len() != columns) {
303 return Err(invalid(
304 type_name,
305 "ControlPointsList",
306 format!(
307 "row {bad} has {} control points, row 0 has {columns}; the grid must be rectangular",
308 control_points[bad].len()
309 ),
310 ));
311 }
312
313 check_knots(type_name, "U", u_degree, rows, u)?;
314 check_knots(type_name, "V", v_degree, columns, v)?;
315
316 let mut attrs = vec![Value::Null; 12];
317 attrs[bspline_slot::U_DEGREE] = Value::Integer(u_degree);
318 attrs[bspline_slot::V_DEGREE] = Value::Integer(v_degree);
319 attrs[bspline_slot::CONTROL_POINTS] =
320 Value::List(control_points.iter().map(|row| refs(row)).collect());
321 attrs[bspline_slot::SURFACE_FORM] = Value::Enum(form.into());
322 attrs[bspline_slot::U_CLOSED] = Value::LogicalUnknown;
324 attrs[bspline_slot::V_CLOSED] = Value::LogicalUnknown;
325 attrs[bspline_slot::SELF_INTERSECT] = Value::LogicalUnknown;
326 attrs[bspline_slot::U_MULTIPLICITIES] = integers(u.multiplicities);
327 attrs[bspline_slot::V_MULTIPLICITIES] = integers(v.multiplicities);
328 attrs[bspline_slot::U_KNOTS] = reals(u.knots);
329 attrs[bspline_slot::V_KNOTS] = reals(v.knots);
330 attrs[bspline_slot::KNOT_SPEC] = Value::Enum(knot_spec.into());
331 Ok(attrs)
332}
333
334fn check_knots(
336 type_name: &'static str,
337 axis: &'static str,
338 degree: i64,
339 extent: usize,
340 knots: SurfaceKnots<'_>,
341) -> Result<(), GeometryError> {
342 if degree < 1 {
343 return Err(invalid(
344 type_name,
345 "Degree",
346 format!("{axis}Degree must be at least 1, got {degree}"),
347 ));
348 }
349 if knots.multiplicities.len() != knots.knots.len() {
350 return Err(invalid(
351 type_name,
352 "Multiplicities",
353 format!(
354 "{axis}: {} multiplicities for {} knots",
355 knots.multiplicities.len(),
356 knots.knots.len()
357 ),
358 ));
359 }
360 if knots.knots.len() < 2 {
361 return Err(invalid(
362 type_name,
363 "Knots",
364 format!("{axis}: expected at least 2 distinct knots"),
365 ));
366 }
367 require_finite(type_name, "Knots", knots.knots)?;
368 if knots.multiplicities.iter().any(|m| *m < 1) {
369 return Err(invalid(
370 type_name,
371 "Multiplicities",
372 format!("{axis}: a multiplicity is not positive"),
373 ));
374 }
375 if knots.knots.windows(2).any(|w| w[1] <= w[0]) {
376 return Err(invalid(
377 type_name,
378 "Knots",
379 format!("{axis}: knots must strictly increase"),
380 ));
381 }
382 let total: i64 = knots.multiplicities.iter().sum();
383 let expected = degree + extent as i64 + 1;
384 if total != expected {
385 return Err(invalid(
386 type_name,
387 "Multiplicities",
388 format!(
389 "{axis}: multiplicities sum to {total}, but degree {degree} over {extent} control points requires {expected}"
390 ),
391 ));
392 }
393 Ok(())
394}
395
396fn integers(values: &[i64]) -> Value {
398 Value::List(values.iter().copied().map(Value::Integer).collect())
399}
400
401fn reals(values: &[f64]) -> Value {
403 Value::List(values.iter().copied().map(Value::Real).collect())
404}