1use ifc_model::{Entity, EntityId, Model, Transaction, Value};
14
15use crate::PropertyResult;
16
17use super::authoring::optional_text;
18use super::owned::{stage_ifc4, stage_owned, Rooted};
19use super::predefined::{invalid, measure, require_guid, Measure};
20const BAR_SURFACE: &[&str] = &["PLAIN", "TEXTURED"];
21
22#[derive(Debug, Clone, Copy)]
27pub struct ReinforcementBarDraft<'a> {
28 pub total_cross_section_area: f64,
30 pub steel_grade: &'a str,
32 pub bar_surface: Option<&'a str>,
34 pub effective_depth: Option<f64>,
36 pub nominal_bar_diameter: Option<f64>,
38 pub bar_count: Option<i64>,
40}
41
42pub fn add_reinforcement_bar_properties(
50 tx: &mut Transaction,
51 draft: ReinforcementBarDraft<'_>,
52) -> PropertyResult<EntityId> {
53 const ENTITY: &str = "IFCREINFORCEMENTBARPROPERTIES";
54
55 if draft.steel_grade.trim().is_empty() {
56 return Err(invalid(ENTITY, "SteelGrade", "blank"));
57 }
58 if !draft.total_cross_section_area.is_finite() || draft.total_cross_section_area < 0.0 {
61 return Err(invalid(
62 ENTITY,
63 "TotalCrossSectionArea",
64 format!("{}", draft.total_cross_section_area),
65 ));
66 }
67 if let Some(surface) = draft.bar_surface {
68 if !BAR_SURFACE.contains(&surface) {
69 return Err(invalid(ENTITY, "BarSurface", surface));
70 }
71 }
72
73 let mut attributes = vec![Value::Null; 6];
74 attributes[0] = Value::Real(draft.total_cross_section_area);
75 attributes[1] = Value::Text(draft.steel_grade.into());
76 attributes[2] = draft
77 .bar_surface
78 .map_or(Value::Null, |s| Value::Enum(s.into()));
79 attributes[3] = measure(
80 ENTITY,
81 "EffectiveDepth",
82 draft.effective_depth,
83 Measure::Length,
84 )?;
85 attributes[4] = measure(
86 ENTITY,
87 "NominalBarDiameter",
88 draft.nominal_bar_diameter,
89 Measure::Positive,
90 )?;
91 attributes[5] = draft.bar_count.map_or(Value::Null, Value::Integer);
95
96 Ok(tx.create(Entity::new(ENTITY, attributes)))
97}
98
99const SECTION_TYPE: &[&str] = &["TAPERED", "UNIFORM"];
100
101pub fn add_section_properties(
111 tx: &mut Transaction,
112 section_type: &str,
113 start_profile: EntityId,
114 end_profile: Option<EntityId>,
115) -> PropertyResult<EntityId> {
116 const ENTITY: &str = "IFCSECTIONPROPERTIES";
117
118 if !SECTION_TYPE.contains(§ion_type) {
119 return Err(invalid(ENTITY, "SectionType", section_type));
120 }
121
122 let mut attributes = vec![Value::Null; 3];
123 attributes[0] = Value::Enum(section_type.into());
124 attributes[1] = Value::Ref(start_profile);
125 attributes[2] = end_profile.map_or(Value::Null, Value::Ref);
126
127 Ok(tx.create(Entity::new(ENTITY, attributes)))
128}
129
130const BAR_ROLE: &[&str] = &[
131 "ANCHORING",
132 "EDGE",
133 "LIGATURE",
134 "MAIN",
135 "PUNCHING",
136 "RING",
137 "SHEAR",
138 "STUD",
139 "USERDEFINED",
140 "NOTDEFINED",
141];
142
143#[derive(Debug, Clone, Copy)]
145pub struct SectionReinforcementDraft<'a> {
146 pub longitudinal_start_position: f64,
148 pub longitudinal_end_position: f64,
150 pub transverse_position: Option<f64>,
152 pub reinforcement_role: &'a str,
154 pub section_definition: EntityId,
156 pub cross_section_reinforcement_definitions: &'a [EntityId],
159}
160
161pub fn add_section_reinforcement_properties(
173 tx: &mut Transaction,
174 draft: SectionReinforcementDraft<'_>,
175) -> PropertyResult<EntityId> {
176 const ENTITY: &str = "IFCSECTIONREINFORCEMENTPROPERTIES";
177
178 if !BAR_ROLE.contains(&draft.reinforcement_role) {
179 return Err(invalid(
180 ENTITY,
181 "ReinforcementRole",
182 draft.reinforcement_role,
183 ));
184 }
185 if draft.cross_section_reinforcement_definitions.is_empty() {
186 return Err(invalid(
187 ENTITY,
188 "CrossSectionReinforcementDefinitions",
189 "empty",
190 ));
191 }
192
193 let mut attributes = vec![Value::Null; 6];
194 attributes[0] = measure(
195 ENTITY,
196 "LongitudinalStartPosition",
197 Some(draft.longitudinal_start_position),
198 Measure::Length,
199 )?;
200 attributes[1] = measure(
201 ENTITY,
202 "LongitudinalEndPosition",
203 Some(draft.longitudinal_end_position),
204 Measure::Length,
205 )?;
206 attributes[2] = measure(
207 ENTITY,
208 "TransversePosition",
209 draft.transverse_position,
210 Measure::Length,
211 )?;
212 attributes[3] = Value::Enum(draft.reinforcement_role.into());
213 attributes[4] = Value::Ref(draft.section_definition);
214 attributes[5] = Value::List(
215 draft
216 .cross_section_reinforcement_definitions
217 .iter()
218 .copied()
219 .map(Value::Ref)
220 .collect(),
221 );
222
223 Ok(tx.create(Entity::new(ENTITY, attributes)))
224}
225
226pub fn add_reinforcement_definition_properties(
243 tx: &mut Transaction,
244 global_id: &str,
245 name: Option<&str>,
246 description: Option<&str>,
247 definition_type: Option<&str>,
248 reinforcement_section_definitions: &[EntityId],
249) -> PropertyResult<EntityId> {
250 let rooted = reinforcement_definition(
251 global_id,
252 (name, description),
253 definition_type,
254 reinforcement_section_definitions,
255 )?;
256 stage_ifc4(tx, rooted)
257}
258
259#[allow(clippy::too_many_arguments)]
270pub fn add_reinforcement_definition_properties_with_owner_history(
271 tx: &mut Transaction,
272 model: &Model,
273 global_id: &str,
274 name: Option<&str>,
275 description: Option<&str>,
276 definition_type: Option<&str>,
277 reinforcement_section_definitions: &[EntityId],
278 owner_history: EntityId,
279) -> PropertyResult<EntityId> {
280 let rooted = reinforcement_definition(
281 global_id,
282 (name, description),
283 definition_type,
284 reinforcement_section_definitions,
285 )?;
286 stage_owned(tx, model, rooted, owner_history)
287}
288
289fn reinforcement_definition<'a>(
290 global_id: &'a str,
291 (name, description): (Option<&'a str>, Option<&'a str>),
292 definition_type: Option<&str>,
293 reinforcement_section_definitions: &[EntityId],
294) -> PropertyResult<Rooted<'a>> {
295 const ENTITY: &str = "IFCREINFORCEMENTDEFINITIONPROPERTIES";
296
297 if reinforcement_section_definitions.is_empty() {
298 return Err(invalid(ENTITY, "ReinforcementSectionDefinitions", "empty"));
299 }
300 require_guid(ENTITY, global_id)?;
301 let sections = reinforcement_section_definitions.iter().copied();
302 let values = vec![
303 ("DefinitionType", optional_text(definition_type)),
304 (
305 "ReinforcementSectionDefinitions",
306 Value::List(sections.map(Value::Ref).collect()),
307 ),
308 ];
309 Ok(Rooted {
310 entity: ENTITY,
311 global_id,
312 name,
313 description,
314 values,
315 })
316}
317
318pub fn add_profile_properties(
328 tx: &mut Transaction,
329 name: Option<&str>,
330 description: Option<&str>,
331 properties: &[EntityId],
332 profile_definition: EntityId,
333) -> PropertyResult<EntityId> {
334 const ENTITY: &str = "IFCPROFILEPROPERTIES";
335
336 if properties.is_empty() {
337 return Err(invalid(ENTITY, "Properties", "empty"));
338 }
339
340 let mut attributes = vec![Value::Null; 4];
341 attributes[0] = optional_text(name);
342 attributes[1] = optional_text(description);
343 attributes[2] = Value::List(properties.iter().copied().map(Value::Ref).collect());
344 attributes[3] = Value::Ref(profile_definition);
345
346 Ok(tx.create(Entity::new(ENTITY, attributes)))
347}