ifc_lite_geometry/processors/
extrusion_tapered.rs1use crate::{
13 extrusion::{apply_transform, extrude_profile, extrude_profile_lofted},
14 profiles::ProfileProcessor,
15 Error, Mesh, Result, TessellationQuality, Vector3,
16};
17use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
18use nalgebra::Matrix4;
19
20use super::helpers::parse_axis2_placement_3d;
21use crate::router::GeometryProcessor;
22
23pub struct ExtrudedAreaSolidTaperedProcessor {
24 profile_processor: ProfileProcessor,
25}
26
27impl ExtrudedAreaSolidTaperedProcessor {
28 pub fn new(schema: IfcSchema) -> Self {
29 Self {
30 profile_processor: ProfileProcessor::new(schema),
31 }
32 }
33}
34
35impl GeometryProcessor for ExtrudedAreaSolidTaperedProcessor {
36 fn process(
37 &self,
38 entity: &DecodedEntity,
39 decoder: &mut EntityDecoder,
40 _schema: &IfcSchema,
41 quality: TessellationQuality,
42 ) -> Result<Mesh> {
43 let start_attr = entity.get(0).ok_or_else(|| {
51 Error::geometry("ExtrudedAreaSolidTapered missing SweptArea".to_string())
52 })?;
53 let start_entity = decoder
54 .resolve_ref(start_attr)?
55 .ok_or_else(|| Error::geometry("Failed to resolve SweptArea".to_string()))?;
56 let start_profile = self
57 .profile_processor
58 .process(&start_entity, decoder, quality)?;
59 if start_profile.outer.is_empty() {
60 return Ok(Mesh::new());
61 }
62
63 let direction_attr = entity.get(2).ok_or_else(|| {
64 Error::geometry("ExtrudedAreaSolidTapered missing ExtrudedDirection".to_string())
65 })?;
66 let direction_entity = decoder
67 .resolve_ref(direction_attr)?
68 .ok_or_else(|| Error::geometry("Failed to resolve ExtrudedDirection".to_string()))?;
69 if direction_entity.ifc_type != IfcType::IfcDirection {
70 return Err(Error::geometry(format!(
71 "Expected IfcDirection, got {}",
72 direction_entity.ifc_type
73 )));
74 }
75
76 use ifc_lite_core::AttributeValue;
77 let ratios_attr = direction_entity
78 .get(0)
79 .ok_or_else(|| Error::geometry("IfcDirection missing ratios".to_string()))?;
80 let ratios = ratios_attr
81 .as_list()
82 .ok_or_else(|| Error::geometry("Expected ratio list".to_string()))?;
83 let dir_x = ratios
84 .first()
85 .and_then(|v: &AttributeValue| v.as_float())
86 .unwrap_or(0.0);
87 let dir_y = ratios
88 .get(1)
89 .and_then(|v: &AttributeValue| v.as_float())
90 .unwrap_or(0.0);
91 let dir_z = ratios
92 .get(2)
93 .and_then(|v: &AttributeValue| v.as_float())
94 .unwrap_or(1.0);
95 let direction = Vector3::new(dir_x, dir_y, dir_z);
96 if direction.norm_squared() <= f64::EPSILON {
97 return Err(Error::geometry(
98 "ExtrudedAreaSolidTapered has zero-length ExtrudedDirection".to_string(),
99 ));
100 }
101 let local_direction = direction.normalize();
102
103 let depth = entity.get_float(3).ok_or_else(|| {
104 Error::geometry("ExtrudedAreaSolidTapered missing Depth".to_string())
105 })?;
106
107 let pos_transform = if let Some(pos_attr) = entity.get(1) {
108 if !pos_attr.is_null() {
109 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
110 if pos_entity.ifc_type == IfcType::IfcAxis2Placement3D {
111 Some(parse_axis2_placement_3d(&pos_entity, decoder)?)
112 } else {
113 None
114 }
115 } else {
116 None
117 }
118 } else {
119 None
120 }
121 } else {
122 None
123 };
124
125 let is_local_z_aligned =
128 local_direction.x.abs() < 0.001 && local_direction.y.abs() < 0.001;
129 let transform = if is_local_z_aligned {
130 if local_direction.z < 0.0 {
131 Some(Matrix4::new_translation(&Vector3::new(0.0, 0.0, -depth)))
132 } else {
133 None
134 }
135 } else {
136 let mut shear_mat = Matrix4::identity();
137 shear_mat[(0, 2)] = local_direction.x;
138 shear_mat[(1, 2)] = local_direction.y;
139 shear_mat[(2, 2)] = local_direction.z;
140 Some(shear_mat)
141 };
142
143 let end_profile_opt = match entity.get(4) {
147 Some(attr) if !attr.is_null() => match decoder.resolve_ref(attr)? {
148 Some(end_entity) => {
149 match self.profile_processor.process(&end_entity, decoder, quality) {
150 Ok(p) if !p.outer.is_empty() => Some(p),
151 Ok(_) => None,
152 Err(_) => None,
153 }
154 }
155 None => None,
156 },
157 _ => None,
158 };
159
160 let mut mesh = match end_profile_opt {
161 Some(end_profile) => {
162 extrude_profile_lofted(&start_profile, &end_profile, depth, transform)?
163 }
164 None => extrude_profile(&start_profile, depth, transform)?,
165 };
166
167 if let Some(pos) = pos_transform {
168 apply_transform(&mut mesh, &pos);
169 }
170
171 Ok(mesh)
172 }
173
174 fn supported_types(&self) -> Vec<IfcType> {
175 vec![IfcType::IfcExtrudedAreaSolidTapered]
176 }
177}