1use ifc_model::{Entity, EntityId, Transaction, Value};
31
32use crate::error::GeometryError;
33use crate::resource::topology::slot;
34
35use super::{invalid, refs};
36
37pub fn vertex_point(tx: &mut Transaction, geometry: EntityId) -> EntityId {
39 let mut attrs = vec![Value::Null; 1];
40 attrs[slot::VERTEX_GEOMETRY] = Value::Ref(geometry);
41 tx.create(Entity::new("IFCVERTEXPOINT", attrs))
42}
43
44pub fn edge(tx: &mut Transaction, start: EntityId, end: EntityId) -> EntityId {
46 let mut attrs = vec![Value::Null; 2];
47 attrs[slot::EDGE_START] = Value::Ref(start);
48 attrs[slot::EDGE_END] = Value::Ref(end);
49 tx.create(Entity::new("IFCEDGE", attrs))
50}
51
52pub fn edge_curve(
57 tx: &mut Transaction,
58 start: EntityId,
59 end: EntityId,
60 geometry: EntityId,
61 same_sense: bool,
62) -> EntityId {
63 let mut attrs = vec![Value::Null; 4];
64 attrs[slot::EDGE_START] = Value::Ref(start);
65 attrs[slot::EDGE_END] = Value::Ref(end);
66 attrs[slot::EDGE_GEOMETRY] = Value::Ref(geometry);
67 attrs[slot::EDGE_SAME_SENSE] = Value::Bool(same_sense);
68 tx.create(Entity::new("IFCEDGECURVE", attrs))
69}
70
71pub fn oriented_edge(tx: &mut Transaction, element: EntityId, orientation: bool) -> EntityId {
78 let mut attrs = vec![Value::Null; 4];
79 attrs[slot::EDGE_START] = Value::Derived;
80 attrs[slot::EDGE_END] = Value::Derived;
81 attrs[slot::EDGE_ELEMENT] = Value::Ref(element);
82 attrs[slot::EDGE_ORIENTATION] = Value::Bool(orientation);
83 tx.create(Entity::new("IFCORIENTEDEDGE", attrs))
84}
85
86pub fn subedge(tx: &mut Transaction, start: EntityId, end: EntityId, parent: EntityId) -> EntityId {
91 let mut attrs = vec![Value::Null; 3];
92 attrs[slot::EDGE_START] = Value::Ref(start);
93 attrs[slot::EDGE_END] = Value::Ref(end);
94 attrs[slot::PARENT_EDGE] = Value::Ref(parent);
95 tx.create(Entity::new("IFCSUBEDGE", attrs))
96}
97
98pub fn poly_loop(tx: &mut Transaction, polygon: &[EntityId]) -> Result<EntityId, GeometryError> {
107 const T: &str = "IFCPOLYLOOP";
108 if polygon.len() < 3 {
109 return Err(invalid(
110 T,
111 "Polygon",
112 format!("expected at least 3 points, got {}", polygon.len()),
113 ));
114 }
115 for (i, point) in polygon.iter().enumerate() {
119 if polygon[..i].contains(point) {
120 return Err(invalid(
121 T,
122 "Polygon",
123 "the point list is UNIQUE; a loop closes implicitly, so the \
124 first point must not be repeated at the end",
125 ));
126 }
127 }
128 let mut attrs = vec![Value::Null; 1];
129 attrs[slot::POLYGON] = refs(polygon);
130 Ok(tx.create(Entity::new(T, attrs)))
131}
132
133pub fn edge_loop(tx: &mut Transaction, edges: &[EntityId]) -> Result<EntityId, GeometryError> {
141 const T: &str = "IFCEDGELOOP";
142 if edges.is_empty() {
143 return Err(invalid(T, "EdgeList", "expected at least one edge"));
144 }
145 let mut attrs = vec![Value::Null; 1];
146 attrs[slot::EDGE_LIST] = refs(edges);
147 Ok(tx.create(Entity::new(T, attrs)))
148}
149
150pub fn vertex_loop(tx: &mut Transaction, vertex: EntityId) -> EntityId {
154 tx.create(Entity::new("IFCVERTEXLOOP", vec![Value::Ref(vertex)]))
155}
156
157pub fn face_bound(tx: &mut Transaction, bound: EntityId, orientation: bool) -> EntityId {
162 bound_entity(tx, "IFCFACEBOUND", bound, orientation)
163}
164
165pub fn face_outer_bound(tx: &mut Transaction, bound: EntityId, orientation: bool) -> EntityId {
170 bound_entity(tx, "IFCFACEOUTERBOUND", bound, orientation)
171}
172
173fn bound_entity(
175 tx: &mut Transaction,
176 type_name: &'static str,
177 bound: EntityId,
178 orientation: bool,
179) -> EntityId {
180 let mut attrs = vec![Value::Null; 2];
181 attrs[slot::BOUND] = Value::Ref(bound);
182 attrs[slot::ORIENTATION] = Value::Bool(orientation);
183 tx.create(Entity::new(type_name, attrs))
184}
185
186pub fn face(tx: &mut Transaction, bounds: &[EntityId]) -> Result<EntityId, GeometryError> {
192 const T: &str = "IFCFACE";
193 if bounds.is_empty() {
194 return Err(invalid(T, "Bounds", "expected at least one bound"));
195 }
196 let mut attrs = vec![Value::Null; 1];
197 attrs[slot::BOUNDS] = refs(bounds);
198 Ok(tx.create(Entity::new(T, attrs)))
199}
200
201pub fn face_surface(
211 tx: &mut Transaction,
212 bounds: &[EntityId],
213 surface: EntityId,
214 same_sense: bool,
215 advanced: bool,
216) -> Result<EntityId, GeometryError> {
217 let type_name = if advanced {
218 "IFCADVANCEDFACE"
219 } else {
220 "IFCFACESURFACE"
221 };
222 if bounds.is_empty() {
223 return Err(invalid(type_name, "Bounds", "expected at least one bound"));
224 }
225 let mut attrs = vec![Value::Null; 3];
226 attrs[slot::BOUNDS] = refs(bounds);
227 attrs[slot::FACE_SURFACE] = Value::Ref(surface);
228 attrs[slot::FACE_SAME_SENSE] = Value::Bool(same_sense);
229 Ok(tx.create(Entity::new(type_name, attrs)))
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub enum ShellKind {
235 Closed,
240 Open,
242 Connected,
244}
245
246impl ShellKind {
247 fn type_name(self) -> &'static str {
249 match self {
250 Self::Closed => "IFCCLOSEDSHELL",
251 Self::Open => "IFCOPENSHELL",
252 Self::Connected => "IFCCONNECTEDFACESET",
253 }
254 }
255}
256
257pub fn shell(
263 tx: &mut Transaction,
264 kind: ShellKind,
265 faces: &[EntityId],
266) -> Result<EntityId, GeometryError> {
267 let type_name = kind.type_name();
268 if faces.is_empty() {
269 return Err(invalid(type_name, "CfsFaces", "expected at least one face"));
270 }
271 let mut attrs = vec![Value::Null; 1];
272 attrs[slot::CFS_FACES] = refs(faces);
273 Ok(tx.create(Entity::new(type_name, attrs)))
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub enum BrepKind {
279 Faceted,
281 Advanced,
283}
284
285pub fn manifold_solid_brep(
293 tx: &mut Transaction,
294 kind: BrepKind,
295 outer: EntityId,
296 voids: &[EntityId],
297) -> EntityId {
298 let type_name = match (kind, voids.is_empty()) {
299 (BrepKind::Faceted, true) => "IFCFACETEDBREP",
300 (BrepKind::Faceted, false) => "IFCFACETEDBREPWITHVOIDS",
301 (BrepKind::Advanced, true) => "IFCADVANCEDBREP",
302 (BrepKind::Advanced, false) => "IFCADVANCEDBREPWITHVOIDS",
303 };
304 let mut attrs = vec![Value::Null; if voids.is_empty() { 1 } else { 2 }];
305 attrs[slot::OUTER] = Value::Ref(outer);
306 if !voids.is_empty() {
307 attrs[slot::VOIDS] = refs(voids);
308 }
309 tx.create(Entity::new(type_name, attrs))
310}
311
312pub fn shell_based_surface_model(
321 tx: &mut Transaction,
322 shells: &[EntityId],
323) -> Result<EntityId, GeometryError> {
324 const T: &str = "IFCSHELLBASEDSURFACEMODEL";
325 if shells.is_empty() {
326 return Err(invalid(T, "SbsmBoundary", "expected at least one shell"));
327 }
328 Ok(tx.create(Entity::new(T, vec![refs(shells)])))
329}
330
331pub fn face_based_surface_model(
337 tx: &mut Transaction,
338 face_sets: &[EntityId],
339) -> Result<EntityId, GeometryError> {
340 const T: &str = "IFCFACEBASEDSURFACEMODEL";
341 if face_sets.is_empty() {
342 return Err(invalid(T, "FbsmFaces", "expected at least one face set"));
343 }
344 Ok(tx.create(Entity::new(T, vec![refs(face_sets)])))
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357pub enum BareTopology {
358 Loop,
360 Vertex,
362}
363
364pub fn bare_topology(tx: &mut Transaction, kind: BareTopology) -> EntityId {
366 let entity = match kind {
367 BareTopology::Loop => "IFCLOOP",
368 BareTopology::Vertex => "IFCVERTEX",
369 };
370 tx.create(Entity::new(entity, Vec::new()))
371}