1use ifc_model::{Entity, EntityId, Transaction, Value};
7
8use crate::error::GeometryError;
9use crate::slots::section_slot as slot;
10
11use super::super::require_finite;
12use super::{
13 non_negative, parameterized, positive, put_opt, rule, AsymmetricIDims, CShapeDims, FlangedDims,
14 IShapeDims, ProfileHeader,
15};
16
17#[derive(Debug, Clone, Copy, Default)]
24pub struct IShapeExtras {
25 pub fillet_radius: Option<f64>,
27 pub flange_edge_radius: Option<f64>,
29 pub flange_slope: Option<f64>,
31}
32
33pub fn i_shape(
43 tx: &mut Transaction,
44 header: ProfileHeader<'_>,
45 dims: IShapeDims,
46 extras: IShapeExtras,
47) -> Result<EntityId, GeometryError> {
48 const T: &str = "IFCISHAPEPROFILEDEF";
49 let IShapeDims {
50 width,
51 depth,
52 web_thickness,
53 flange_thickness,
54 } = dims;
55 positive(T, "OverallWidth", width)?;
56 positive(T, "OverallDepth", depth)?;
57 positive(T, "WebThickness", web_thickness)?;
58 positive(T, "FlangeThickness", flange_thickness)?;
59 non_negative(T, "FilletRadius", extras.fillet_radius)?;
60 non_negative(T, "FlangeEdgeRadius", extras.flange_edge_radius)?;
61 if let Some(slope) = extras.flange_slope {
62 require_finite(T, "FlangeSlope", &[slope])?;
63 }
64 if 2.0 * flange_thickness >= depth {
65 return Err(rule(
66 T,
67 "FlangeThickness",
68 format!("2 * {flange_thickness} is not below OverallDepth {depth}"),
69 ));
70 }
71 if web_thickness >= width {
72 return Err(rule(
73 T,
74 "WebThickness",
75 format!("{web_thickness} is not below OverallWidth {width}"),
76 ));
77 }
78 if let Some(fillet) = extras.fillet_radius {
79 let web_room = (width - web_thickness) / 2.0;
80 let flange_room = (depth - 2.0 * flange_thickness) / 2.0;
81 if fillet > web_room || fillet > flange_room {
82 return Err(rule(
83 T,
84 "FilletRadius",
85 format!("{fillet} exceeds the room left by the web ({web_room}) or flanges ({flange_room})"),
86 ));
87 }
88 }
89 let mut attrs = parameterized(T, 10, header);
90 attrs[slot::I_WIDTH] = Value::Real(width);
91 attrs[slot::I_DEPTH] = Value::Real(depth);
92 attrs[slot::I_WEB] = Value::Real(web_thickness);
93 attrs[slot::I_FLANGE] = Value::Real(flange_thickness);
94 put_opt(&mut attrs, slot::I_FILLET, extras.fillet_radius);
95 put_opt(&mut attrs, slot::I_EDGE, extras.flange_edge_radius);
96 put_opt(&mut attrs, slot::I_SLOPE, extras.flange_slope);
97 Ok(tx.create(Entity::new(T, attrs)))
98}
99#[derive(Debug, Clone, Copy, Default)]
101pub struct LShapeExtras {
102 pub width: Option<f64>,
104 pub fillet_radius: Option<f64>,
106 pub edge_radius: Option<f64>,
108 pub leg_slope: Option<f64>,
110}
111
112pub fn l_shape(
121 tx: &mut Transaction,
122 header: ProfileHeader<'_>,
123 depth: f64,
124 thickness: f64,
125 extras: LShapeExtras,
126) -> Result<EntityId, GeometryError> {
127 const T: &str = "IFCLSHAPEPROFILEDEF";
128 positive(T, "Depth", depth)?;
129 positive(T, "Thickness", thickness)?;
130 if let Some(width) = extras.width {
131 positive(T, "Width", width)?;
132 }
133 non_negative(T, "FilletRadius", extras.fillet_radius)?;
134 non_negative(T, "EdgeRadius", extras.edge_radius)?;
135 if let Some(slope) = extras.leg_slope {
136 require_finite(T, "LegSlope", &[slope])?;
137 }
138 if thickness >= depth {
139 return Err(rule(
140 T,
141 "Thickness",
142 format!("{thickness} is not below Depth {depth}"),
143 ));
144 }
145 if let Some(width) = extras.width {
146 if thickness >= width {
147 return Err(rule(
148 T,
149 "Thickness",
150 format!("{thickness} is not below Width {width}"),
151 ));
152 }
153 }
154 let mut attrs = parameterized(T, 9, header);
155 attrs[slot::L_DEPTH] = Value::Real(depth);
156 attrs[slot::L_THICKNESS] = Value::Real(thickness);
157 put_opt(&mut attrs, slot::L_WIDTH, extras.width);
158 put_opt(&mut attrs, slot::L_FILLET, extras.fillet_radius);
159 put_opt(&mut attrs, slot::L_EDGE, extras.edge_radius);
160 put_opt(&mut attrs, slot::L_SLOPE, extras.leg_slope);
161 Ok(tx.create(Entity::new(T, attrs)))
162}
163#[derive(Debug, Clone, Copy, Default)]
165pub struct TShapeExtras {
166 pub fillet_radius: Option<f64>,
168 pub flange_edge_radius: Option<f64>,
170 pub web_edge_radius: Option<f64>,
172 pub web_slope: Option<f64>,
174 pub flange_slope: Option<f64>,
176}
177
178pub fn t_shape(
186 tx: &mut Transaction,
187 header: ProfileHeader<'_>,
188 dims: FlangedDims,
189 extras: TShapeExtras,
190) -> Result<EntityId, GeometryError> {
191 const T: &str = "IFCTSHAPEPROFILEDEF";
192 let FlangedDims {
193 depth,
194 flange_width,
195 web_thickness,
196 flange_thickness,
197 } = dims;
198 positive(T, "Depth", depth)?;
199 positive(T, "FlangeWidth", flange_width)?;
200 positive(T, "WebThickness", web_thickness)?;
201 positive(T, "FlangeThickness", flange_thickness)?;
202 non_negative(T, "FilletRadius", extras.fillet_radius)?;
203 non_negative(T, "FlangeEdgeRadius", extras.flange_edge_radius)?;
204 non_negative(T, "WebEdgeRadius", extras.web_edge_radius)?;
205 for (attribute, slope) in [
206 ("WebSlope", extras.web_slope),
207 ("FlangeSlope", extras.flange_slope),
208 ] {
209 if let Some(slope) = slope {
210 require_finite(T, attribute, &[slope])?;
211 }
212 }
213 if flange_thickness >= depth {
214 return Err(rule(
215 T,
216 "FlangeThickness",
217 format!("{flange_thickness} is not below Depth {depth}"),
218 ));
219 }
220 if web_thickness >= flange_width {
221 return Err(rule(
222 T,
223 "WebThickness",
224 format!("{web_thickness} is not below FlangeWidth {flange_width}"),
225 ));
226 }
227 let mut attrs = parameterized(T, 12, header);
228 attrs[slot::T_DEPTH] = Value::Real(depth);
229 attrs[slot::T_WIDTH] = Value::Real(flange_width);
230 attrs[slot::T_WEB] = Value::Real(web_thickness);
231 attrs[slot::T_FLANGE] = Value::Real(flange_thickness);
232 put_opt(&mut attrs, slot::T_FILLET, extras.fillet_radius);
233 put_opt(&mut attrs, slot::T_FLANGE_EDGE, extras.flange_edge_radius);
234 put_opt(&mut attrs, slot::T_WEB_EDGE, extras.web_edge_radius);
235 put_opt(&mut attrs, slot::T_WEB_SLOPE, extras.web_slope);
236 put_opt(&mut attrs, slot::T_FLANGE_SLOPE, extras.flange_slope);
237 Ok(tx.create(Entity::new(T, attrs)))
238}
239#[derive(Debug, Clone, Copy, Default)]
241pub struct UShapeExtras {
242 pub fillet_radius: Option<f64>,
244 pub edge_radius: Option<f64>,
246 pub flange_slope: Option<f64>,
248}
249
250pub fn u_shape(
259 tx: &mut Transaction,
260 header: ProfileHeader<'_>,
261 dims: FlangedDims,
262 extras: UShapeExtras,
263) -> Result<EntityId, GeometryError> {
264 const T: &str = "IFCUSHAPEPROFILEDEF";
265 let FlangedDims {
266 depth,
267 flange_width,
268 web_thickness,
269 flange_thickness,
270 } = dims;
271 positive(T, "Depth", depth)?;
272 positive(T, "FlangeWidth", flange_width)?;
273 positive(T, "WebThickness", web_thickness)?;
274 positive(T, "FlangeThickness", flange_thickness)?;
275 non_negative(T, "FilletRadius", extras.fillet_radius)?;
276 non_negative(T, "EdgeRadius", extras.edge_radius)?;
277 if let Some(slope) = extras.flange_slope {
278 require_finite(T, "FlangeSlope", &[slope])?;
279 }
280 if flange_thickness >= depth / 2.0 {
281 return Err(rule(
282 T,
283 "FlangeThickness",
284 format!(
285 "{flange_thickness} is not below half the Depth ({})",
286 depth / 2.0
287 ),
288 ));
289 }
290 if web_thickness >= flange_width {
291 return Err(rule(
292 T,
293 "WebThickness",
294 format!("{web_thickness} is not below FlangeWidth {flange_width}"),
295 ));
296 }
297 let mut attrs = parameterized(T, 10, header);
298 attrs[slot::U_DEPTH] = Value::Real(depth);
299 attrs[slot::U_WIDTH] = Value::Real(flange_width);
300 attrs[slot::U_WEB] = Value::Real(web_thickness);
301 attrs[slot::U_FLANGE] = Value::Real(flange_thickness);
302 put_opt(&mut attrs, slot::U_FILLET, extras.fillet_radius);
303 put_opt(&mut attrs, slot::U_EDGE, extras.edge_radius);
304 put_opt(&mut attrs, slot::U_SLOPE, extras.flange_slope);
305 Ok(tx.create(Entity::new(T, attrs)))
306}
307
308#[derive(Debug, Clone, Copy, Default)]
310pub struct ZShapeExtras {
311 pub fillet_radius: Option<f64>,
313 pub edge_radius: Option<f64>,
315}
316
317pub fn z_shape(
325 tx: &mut Transaction,
326 header: ProfileHeader<'_>,
327 dims: FlangedDims,
328 extras: ZShapeExtras,
329) -> Result<EntityId, GeometryError> {
330 const T: &str = "IFCZSHAPEPROFILEDEF";
331 let FlangedDims {
332 depth,
333 flange_width,
334 web_thickness,
335 flange_thickness,
336 } = dims;
337 positive(T, "Depth", depth)?;
338 positive(T, "FlangeWidth", flange_width)?;
339 positive(T, "WebThickness", web_thickness)?;
340 positive(T, "FlangeThickness", flange_thickness)?;
341 non_negative(T, "FilletRadius", extras.fillet_radius)?;
342 non_negative(T, "EdgeRadius", extras.edge_radius)?;
343 if flange_thickness >= depth / 2.0 {
344 return Err(rule(
345 T,
346 "FlangeThickness",
347 format!(
348 "{flange_thickness} is not below half the Depth ({})",
349 depth / 2.0
350 ),
351 ));
352 }
353 let mut attrs = parameterized(T, 9, header);
354 attrs[slot::Z_DEPTH] = Value::Real(depth);
355 attrs[slot::Z_FLANGE_WIDTH] = Value::Real(flange_width);
356 attrs[slot::Z_WEB] = Value::Real(web_thickness);
357 attrs[slot::Z_FLANGE] = Value::Real(flange_thickness);
358 put_opt(&mut attrs, slot::Z_FILLET, extras.fillet_radius);
359 put_opt(&mut attrs, slot::Z_EDGE, extras.edge_radius);
360 Ok(tx.create(Entity::new(T, attrs)))
361}
362pub fn c_shape(
371 tx: &mut Transaction,
372 header: ProfileHeader<'_>,
373 dims: CShapeDims,
374 internal_fillet_radius: Option<f64>,
375) -> Result<EntityId, GeometryError> {
376 const T: &str = "IFCCSHAPEPROFILEDEF";
377 let CShapeDims {
378 depth,
379 width,
380 wall_thickness,
381 girth,
382 } = dims;
383 positive(T, "Depth", depth)?;
384 positive(T, "Width", width)?;
385 positive(T, "WallThickness", wall_thickness)?;
386 positive(T, "Girth", girth)?;
387 non_negative(T, "InternalFilletRadius", internal_fillet_radius)?;
388 if girth >= depth / 2.0 {
389 return Err(rule(
390 T,
391 "Girth",
392 format!("{girth} is not below half the Depth ({})", depth / 2.0),
393 ));
394 }
395 if wall_thickness >= width / 2.0 || wall_thickness >= depth / 2.0 {
396 return Err(rule(
397 T,
398 "WallThickness",
399 format!(
400 "{wall_thickness} is not below half of both Width ({}) and Depth ({})",
401 width / 2.0,
402 depth / 2.0
403 ),
404 ));
405 }
406 if let Some(fillet) = internal_fillet_radius {
407 let across_width = width / 2.0 - wall_thickness;
408 let across_depth = depth / 2.0 - wall_thickness;
409 if fillet > across_width || fillet > across_depth {
410 return Err(rule(
411 T,
412 "InternalFilletRadius",
413 format!(
414 "{fillet} exceeds the room the wall leaves ({across_width} by {across_depth})"
415 ),
416 ));
417 }
418 }
419 let mut attrs = parameterized(T, 8, header);
420 attrs[slot::C_DEPTH] = Value::Real(depth);
421 attrs[slot::C_WIDTH] = Value::Real(width);
422 attrs[slot::C_WALL] = Value::Real(wall_thickness);
423 attrs[slot::C_GIRTH] = Value::Real(girth);
424 put_opt(&mut attrs, slot::C_FILLET, internal_fillet_radius);
425 Ok(tx.create(Entity::new(T, attrs)))
426}
427#[derive(Debug, Clone, Copy, Default)]
433pub struct AsymmetricIExtras {
434 pub top_flange_thickness: Option<f64>,
436 pub bottom_flange_fillet_radius: Option<f64>,
438 pub top_flange_fillet_radius: Option<f64>,
440 pub bottom_flange_edge_radius: Option<f64>,
442 pub top_flange_edge_radius: Option<f64>,
444 pub bottom_flange_slope: Option<f64>,
446 pub top_flange_slope: Option<f64>,
448}
449
450pub fn asymmetric_i_shape(
460 tx: &mut Transaction,
461 header: ProfileHeader<'_>,
462 dims: AsymmetricIDims,
463 extras: AsymmetricIExtras,
464) -> Result<EntityId, GeometryError> {
465 const T: &str = "IFCASYMMETRICISHAPEPROFILEDEF";
466 let AsymmetricIDims {
467 bottom_flange_width,
468 overall_depth,
469 web_thickness,
470 bottom_flange_thickness,
471 top_flange_width,
472 } = dims;
473 positive(T, "BottomFlangeWidth", bottom_flange_width)?;
474 positive(T, "OverallDepth", overall_depth)?;
475 positive(T, "WebThickness", web_thickness)?;
476 positive(T, "BottomFlangeThickness", bottom_flange_thickness)?;
477 positive(T, "TopFlangeWidth", top_flange_width)?;
478 if let Some(top) = extras.top_flange_thickness {
479 positive(T, "TopFlangeThickness", top)?;
480 }
481 non_negative(
482 T,
483 "BottomFlangeFilletRadius",
484 extras.bottom_flange_fillet_radius,
485 )?;
486 non_negative(T, "TopFlangeFilletRadius", extras.top_flange_fillet_radius)?;
487 non_negative(
488 T,
489 "BottomFlangeEdgeRadius",
490 extras.bottom_flange_edge_radius,
491 )?;
492 non_negative(T, "TopFlangeEdgeRadius", extras.top_flange_edge_radius)?;
493 for (attribute, slope) in [
494 ("BottomFlangeSlope", extras.bottom_flange_slope),
495 ("TopFlangeSlope", extras.top_flange_slope),
496 ] {
497 if let Some(slope) = slope {
498 require_finite(T, attribute, &[slope])?;
499 }
500 }
501 if web_thickness >= bottom_flange_width || web_thickness >= top_flange_width {
502 return Err(rule(
503 T,
504 "WebThickness",
505 format!(
506 "{web_thickness} is not below both flange widths ({bottom_flange_width}, {top_flange_width})"
507 ),
508 ));
509 }
510 if let Some(top) = extras.top_flange_thickness {
511 if bottom_flange_thickness + top >= overall_depth {
512 return Err(rule(
513 T,
514 "TopFlangeThickness",
515 format!(
516 "{bottom_flange_thickness} + {top} is not below OverallDepth {overall_depth}"
517 ),
518 ));
519 }
520 }
521 for (attribute, fillet, width) in [
522 (
523 "BottomFlangeFilletRadius",
524 extras.bottom_flange_fillet_radius,
525 bottom_flange_width,
526 ),
527 (
528 "TopFlangeFilletRadius",
529 extras.top_flange_fillet_radius,
530 top_flange_width,
531 ),
532 ] {
533 if let Some(fillet) = fillet {
534 let room = (width - web_thickness) / 2.0;
535 if fillet > room {
536 return Err(rule(
537 T,
538 attribute,
539 format!("{fillet} exceeds the flange clearance ({room})"),
540 ));
541 }
542 }
543 }
544 let mut attrs = parameterized(T, 15, header);
545 attrs[slot::AI_BOTTOM_WIDTH] = Value::Real(bottom_flange_width);
546 attrs[slot::AI_DEPTH] = Value::Real(overall_depth);
547 attrs[slot::AI_WEB] = Value::Real(web_thickness);
548 attrs[slot::AI_BOTTOM_FLANGE] = Value::Real(bottom_flange_thickness);
549 attrs[slot::AI_TOP_WIDTH] = Value::Real(top_flange_width);
550 put_opt(&mut attrs, slot::AI_TOP_FLANGE, extras.top_flange_thickness);
551 put_opt(
552 &mut attrs,
553 slot::AI_BOTTOM_FILLET,
554 extras.bottom_flange_fillet_radius,
555 );
556 put_opt(
557 &mut attrs,
558 slot::AI_TOP_FILLET,
559 extras.top_flange_fillet_radius,
560 );
561 put_opt(
562 &mut attrs,
563 slot::AI_BOTTOM_EDGE,
564 extras.bottom_flange_edge_radius,
565 );
566 put_opt(&mut attrs, slot::AI_TOP_EDGE, extras.top_flange_edge_radius);
567 put_opt(
568 &mut attrs,
569 slot::AI_BOTTOM_SLOPE,
570 extras.bottom_flange_slope,
571 );
572 put_opt(&mut attrs, slot::AI_TOP_SLOPE, extras.top_flange_slope);
573 Ok(tx.create(Entity::new(T, attrs)))
574}