1use crate::profile::Profile2D;
10use crate::{Error, Point2, Point3, Result, Vector3};
11use ifc_lite_core::{AttributeValue, DecodedEntity, EntityDecoder, IfcSchema, IfcType, ProfileCategory};
12use std::f64::consts::PI;
13
14const MAX_CURVE_DEPTH: u32 = 50;
17
18pub struct ProfileProcessor {
20 schema: IfcSchema,
21}
22
23impl ProfileProcessor {
24 pub fn new(schema: IfcSchema) -> Self {
26 Self { schema }
27 }
28
29 #[inline]
31 pub fn process(
32 &self,
33 profile: &DecodedEntity,
34 decoder: &mut EntityDecoder,
35 ) -> Result<Profile2D> {
36 match self.schema.profile_category(&profile.ifc_type) {
37 Some(ProfileCategory::Parametric) => self.process_parametric(profile, decoder),
38 Some(ProfileCategory::Arbitrary) => self.process_arbitrary(profile, decoder),
39 Some(ProfileCategory::Composite) => self.process_composite(profile, decoder),
40 _ => Err(Error::geometry(format!(
41 "Unsupported profile type: {}",
42 profile.ifc_type
43 ))),
44 }
45 }
46
47 #[inline]
49 fn process_parametric(
50 &self,
51 profile: &DecodedEntity,
52 decoder: &mut EntityDecoder,
53 ) -> Result<Profile2D> {
54 let mut base_profile = match profile.ifc_type {
56 IfcType::IfcRectangleProfileDef => self.process_rectangle(profile),
57 IfcType::IfcCircleProfileDef => self.process_circle(profile),
58 IfcType::IfcCircleHollowProfileDef => self.process_circle_hollow(profile),
59 IfcType::IfcRectangleHollowProfileDef => self.process_rectangle_hollow(profile),
60 IfcType::IfcIShapeProfileDef => self.process_i_shape(profile),
61 IfcType::IfcLShapeProfileDef => self.process_l_shape(profile),
62 IfcType::IfcUShapeProfileDef => self.process_u_shape(profile),
63 IfcType::IfcTShapeProfileDef => self.process_t_shape(profile),
64 IfcType::IfcCShapeProfileDef => self.process_c_shape(profile),
65 IfcType::IfcZShapeProfileDef => self.process_z_shape(profile),
66 _ => Err(Error::geometry(format!(
67 "Unsupported parametric profile: {}",
68 profile.ifc_type
69 ))),
70 }?;
71
72 if let Some(pos_attr) = profile.get(2) {
74 if !pos_attr.is_null() {
75 if let Some(pos_entity) = decoder.resolve_ref(pos_attr)? {
76 if pos_entity.ifc_type == IfcType::IfcAxis2Placement2D {
77 self.apply_profile_position(&mut base_profile, &pos_entity, decoder)?;
78 }
79 }
80 }
81 }
82
83 Ok(base_profile)
84 }
85
86 fn apply_profile_position(
89 &self,
90 profile: &mut Profile2D,
91 placement: &DecodedEntity,
92 decoder: &mut EntityDecoder,
93 ) -> Result<()> {
94 let (loc_x, loc_y) = if let Some(loc_attr) = placement.get(0) {
96 if !loc_attr.is_null() {
97 if let Some(loc_entity) = decoder.resolve_ref(loc_attr)? {
98 let coords = loc_entity
99 .get(0)
100 .and_then(|v| v.as_list())
101 .ok_or_else(|| Error::geometry("Missing point coordinates".to_string()))?;
102 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
103 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
104 (x, y)
105 } else {
106 (0.0, 0.0)
107 }
108 } else {
109 (0.0, 0.0)
110 }
111 } else {
112 (0.0, 0.0)
113 };
114
115 let (dir_x, dir_y) = if let Some(dir_attr) = placement.get(1) {
117 if !dir_attr.is_null() {
118 if let Some(dir_entity) = decoder.resolve_ref(dir_attr)? {
119 let ratios = dir_entity
120 .get(0)
121 .and_then(|v| v.as_list())
122 .ok_or_else(|| Error::geometry("Missing direction ratios".to_string()))?;
123 let x = ratios.first().and_then(|v| v.as_float()).unwrap_or(1.0);
124 let y = ratios.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
125 let len = (x * x + y * y).sqrt();
127 if len > 1e-10 {
128 (x / len, y / len)
129 } else {
130 (1.0, 0.0)
131 }
132 } else {
133 (1.0, 0.0)
134 }
135 } else {
136 (1.0, 0.0)
137 }
138 } else {
139 (1.0, 0.0)
140 };
141
142 if loc_x.abs() < 1e-10
144 && loc_y.abs() < 1e-10
145 && (dir_x - 1.0).abs() < 1e-10
146 && dir_y.abs() < 1e-10
147 {
148 return Ok(());
149 }
150
151 let x_axis = (dir_x, dir_y);
154 let y_axis = (-dir_y, dir_x);
155
156 for point in &mut profile.outer {
158 let old_x = point.x;
159 let old_y = point.y;
160 point.x = old_x * x_axis.0 + old_y * y_axis.0 + loc_x;
162 point.y = old_x * x_axis.1 + old_y * y_axis.1 + loc_y;
163 }
164
165 for hole in &mut profile.holes {
167 for point in hole {
168 let old_x = point.x;
169 let old_y = point.y;
170 point.x = old_x * x_axis.0 + old_y * y_axis.0 + loc_x;
171 point.y = old_x * x_axis.1 + old_y * y_axis.1 + loc_y;
172 }
173 }
174
175 Ok(())
176 }
177
178 #[inline]
181 fn process_rectangle(&self, profile: &DecodedEntity) -> Result<Profile2D> {
182 let x_dim = profile
184 .get_float(3)
185 .ok_or_else(|| Error::geometry("Rectangle missing XDim".to_string()))?;
186 let y_dim = profile
187 .get_float(4)
188 .ok_or_else(|| Error::geometry("Rectangle missing YDim".to_string()))?;
189
190 let half_x = x_dim / 2.0;
192 let half_y = y_dim / 2.0;
193
194 let points = vec![
195 Point2::new(-half_x, -half_y),
196 Point2::new(half_x, -half_y),
197 Point2::new(half_x, half_y),
198 Point2::new(-half_x, half_y),
199 ];
200
201 Ok(Profile2D::new(points))
202 }
203
204 #[inline]
207 fn process_circle(&self, profile: &DecodedEntity) -> Result<Profile2D> {
208 let radius = profile
210 .get_float(3)
211 .ok_or_else(|| Error::geometry("Circle missing Radius".to_string()))?;
212
213 let segments = 36;
215 let mut points = Vec::with_capacity(segments);
216
217 for i in 0..segments {
218 let angle = (i as f64) * 2.0 * PI / (segments as f64);
219 let x = radius * angle.cos();
220 let y = radius * angle.sin();
221 points.push(Point2::new(x, y));
222 }
223
224 Ok(Profile2D::new(points))
225 }
226
227 fn process_i_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
230 let overall_width = profile
232 .get_float(3)
233 .ok_or_else(|| Error::geometry("I-Shape missing OverallWidth".to_string()))?;
234 let overall_depth = profile
235 .get_float(4)
236 .ok_or_else(|| Error::geometry("I-Shape missing OverallDepth".to_string()))?;
237 let web_thickness = profile
238 .get_float(5)
239 .ok_or_else(|| Error::geometry("I-Shape missing WebThickness".to_string()))?;
240 let flange_thickness = profile
241 .get_float(6)
242 .ok_or_else(|| Error::geometry("I-Shape missing FlangeThickness".to_string()))?;
243
244 let half_width = overall_width / 2.0;
245 let half_depth = overall_depth / 2.0;
246 let half_web = web_thickness / 2.0;
247
248 let points = vec![
250 Point2::new(-half_width, -half_depth),
252 Point2::new(half_width, -half_depth),
253 Point2::new(half_width, -half_depth + flange_thickness),
254 Point2::new(half_web, -half_depth + flange_thickness),
256 Point2::new(half_web, half_depth - flange_thickness),
257 Point2::new(half_width, half_depth - flange_thickness),
259 Point2::new(half_width, half_depth),
260 Point2::new(-half_width, half_depth),
261 Point2::new(-half_width, half_depth - flange_thickness),
262 Point2::new(-half_web, half_depth - flange_thickness),
264 Point2::new(-half_web, -half_depth + flange_thickness),
265 Point2::new(-half_width, -half_depth + flange_thickness),
266 ];
267
268 Ok(Profile2D::new(points))
269 }
270
271 fn process_circle_hollow(&self, profile: &DecodedEntity) -> Result<Profile2D> {
274 let radius = profile
275 .get_float(3)
276 .ok_or_else(|| Error::geometry("CircleHollow missing Radius".to_string()))?;
277 let wall_thickness = profile
278 .get_float(4)
279 .ok_or_else(|| Error::geometry("CircleHollow missing WallThickness".to_string()))?;
280
281 let inner_radius = radius - wall_thickness;
282 let segments = 36;
283
284 let mut outer_points = Vec::with_capacity(segments);
286 for i in 0..segments {
287 let angle = (i as f64) * 2.0 * PI / (segments as f64);
288 outer_points.push(Point2::new(radius * angle.cos(), radius * angle.sin()));
289 }
290
291 let mut inner_points = Vec::with_capacity(segments);
293 for i in (0..segments).rev() {
294 let angle = (i as f64) * 2.0 * PI / (segments as f64);
295 inner_points.push(Point2::new(
296 inner_radius * angle.cos(),
297 inner_radius * angle.sin(),
298 ));
299 }
300
301 let mut result = Profile2D::new(outer_points);
302 result.add_hole(inner_points);
303 Ok(result)
304 }
305
306 fn process_rectangle_hollow(&self, profile: &DecodedEntity) -> Result<Profile2D> {
309 let x_dim = profile
310 .get_float(3)
311 .ok_or_else(|| Error::geometry("RectangleHollow missing XDim".to_string()))?;
312 let y_dim = profile
313 .get_float(4)
314 .ok_or_else(|| Error::geometry("RectangleHollow missing YDim".to_string()))?;
315 let wall_thickness = profile
316 .get_float(5)
317 .ok_or_else(|| Error::geometry("RectangleHollow missing WallThickness".to_string()))?;
318
319 let half_x = x_dim / 2.0;
320 let half_y = y_dim / 2.0;
321
322 if wall_thickness >= half_x || wall_thickness >= half_y {
324 return Err(Error::geometry(format!(
325 "RectangleHollow WallThickness {} exceeds half dimensions ({}, {})",
326 wall_thickness, half_x, half_y
327 )));
328 }
329
330 let inner_half_x = half_x - wall_thickness;
331 let inner_half_y = half_y - wall_thickness;
332
333 let outer_points = vec![
335 Point2::new(-half_x, -half_y),
336 Point2::new(half_x, -half_y),
337 Point2::new(half_x, half_y),
338 Point2::new(-half_x, half_y),
339 ];
340
341 let inner_points = vec![
343 Point2::new(-inner_half_x, -inner_half_y),
344 Point2::new(-inner_half_x, inner_half_y),
345 Point2::new(inner_half_x, inner_half_y),
346 Point2::new(inner_half_x, -inner_half_y),
347 ];
348
349 let mut result = Profile2D::new(outer_points);
350 result.add_hole(inner_points);
351 Ok(result)
352 }
353
354 fn process_l_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
357 let depth = profile
358 .get_float(3)
359 .ok_or_else(|| Error::geometry("L-Shape missing Depth".to_string()))?;
360 let width = profile
361 .get_float(4)
362 .ok_or_else(|| Error::geometry("L-Shape missing Width".to_string()))?;
363 let thickness = profile
364 .get_float(5)
365 .ok_or_else(|| Error::geometry("L-Shape missing Thickness".to_string()))?;
366
367 let points = vec![
369 Point2::new(0.0, 0.0),
370 Point2::new(width, 0.0),
371 Point2::new(width, thickness),
372 Point2::new(thickness, thickness),
373 Point2::new(thickness, depth),
374 Point2::new(0.0, depth),
375 ];
376
377 Ok(Profile2D::new(points))
378 }
379
380 fn process_u_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
383 let depth = profile
384 .get_float(3)
385 .ok_or_else(|| Error::geometry("U-Shape missing Depth".to_string()))?;
386 let flange_width = profile
387 .get_float(4)
388 .ok_or_else(|| Error::geometry("U-Shape missing FlangeWidth".to_string()))?;
389 let web_thickness = profile
390 .get_float(5)
391 .ok_or_else(|| Error::geometry("U-Shape missing WebThickness".to_string()))?;
392 let flange_thickness = profile
393 .get_float(6)
394 .ok_or_else(|| Error::geometry("U-Shape missing FlangeThickness".to_string()))?;
395
396 let half_depth = depth / 2.0;
397
398 let points = vec![
400 Point2::new(0.0, -half_depth),
401 Point2::new(flange_width, -half_depth),
402 Point2::new(flange_width, -half_depth + flange_thickness),
403 Point2::new(web_thickness, -half_depth + flange_thickness),
404 Point2::new(web_thickness, half_depth - flange_thickness),
405 Point2::new(flange_width, half_depth - flange_thickness),
406 Point2::new(flange_width, half_depth),
407 Point2::new(0.0, half_depth),
408 ];
409
410 Ok(Profile2D::new(points))
411 }
412
413 fn process_t_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
416 let depth = profile
417 .get_float(3)
418 .ok_or_else(|| Error::geometry("T-Shape missing Depth".to_string()))?;
419 let flange_width = profile
420 .get_float(4)
421 .ok_or_else(|| Error::geometry("T-Shape missing FlangeWidth".to_string()))?;
422 let web_thickness = profile
423 .get_float(5)
424 .ok_or_else(|| Error::geometry("T-Shape missing WebThickness".to_string()))?;
425 let flange_thickness = profile
426 .get_float(6)
427 .ok_or_else(|| Error::geometry("T-Shape missing FlangeThickness".to_string()))?;
428
429 let half_flange = flange_width / 2.0;
430 let half_web = web_thickness / 2.0;
431
432 let points = vec![
434 Point2::new(-half_web, 0.0),
435 Point2::new(-half_web, depth - flange_thickness),
436 Point2::new(-half_flange, depth - flange_thickness),
437 Point2::new(-half_flange, depth),
438 Point2::new(half_flange, depth),
439 Point2::new(half_flange, depth - flange_thickness),
440 Point2::new(half_web, depth - flange_thickness),
441 Point2::new(half_web, 0.0),
442 ];
443
444 Ok(Profile2D::new(points))
445 }
446
447 fn process_c_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
450 let depth = profile
451 .get_float(3)
452 .ok_or_else(|| Error::geometry("C-Shape missing Depth".to_string()))?;
453 let _width = profile
454 .get_float(4)
455 .ok_or_else(|| Error::geometry("C-Shape missing Width".to_string()))?;
456 let wall_thickness = profile
457 .get_float(5)
458 .ok_or_else(|| Error::geometry("C-Shape missing WallThickness".to_string()))?;
459 let girth = profile.get_float(6).unwrap_or(wall_thickness * 2.0); let half_depth = depth / 2.0;
462
463 let points = vec![
465 Point2::new(girth, -half_depth),
466 Point2::new(0.0, -half_depth),
467 Point2::new(0.0, half_depth),
468 Point2::new(girth, half_depth),
469 Point2::new(girth, half_depth - wall_thickness),
470 Point2::new(wall_thickness, half_depth - wall_thickness),
471 Point2::new(wall_thickness, -half_depth + wall_thickness),
472 Point2::new(girth, -half_depth + wall_thickness),
473 ];
474
475 Ok(Profile2D::new(points))
476 }
477
478 fn process_z_shape(&self, profile: &DecodedEntity) -> Result<Profile2D> {
481 let depth = profile
482 .get_float(3)
483 .ok_or_else(|| Error::geometry("Z-Shape missing Depth".to_string()))?;
484 let flange_width = profile
485 .get_float(4)
486 .ok_or_else(|| Error::geometry("Z-Shape missing FlangeWidth".to_string()))?;
487 let web_thickness = profile
488 .get_float(5)
489 .ok_or_else(|| Error::geometry("Z-Shape missing WebThickness".to_string()))?;
490 let flange_thickness = profile
491 .get_float(6)
492 .ok_or_else(|| Error::geometry("Z-Shape missing FlangeThickness".to_string()))?;
493
494 let half_depth = depth / 2.0;
495 let half_web = web_thickness / 2.0;
496
497 let points = vec![
499 Point2::new(-half_web, -half_depth),
500 Point2::new(-half_web - flange_width, -half_depth),
501 Point2::new(-half_web - flange_width, -half_depth + flange_thickness),
502 Point2::new(-half_web, -half_depth + flange_thickness),
503 Point2::new(-half_web, half_depth - flange_thickness),
504 Point2::new(half_web, half_depth - flange_thickness),
505 Point2::new(half_web, half_depth),
506 Point2::new(half_web + flange_width, half_depth),
507 Point2::new(half_web + flange_width, half_depth - flange_thickness),
508 Point2::new(half_web, half_depth - flange_thickness),
509 Point2::new(half_web, -half_depth + flange_thickness),
510 Point2::new(-half_web, -half_depth + flange_thickness),
511 ];
512
513 Ok(Profile2D::new(points))
514 }
515
516 fn process_arbitrary(
520 &self,
521 profile: &DecodedEntity,
522 decoder: &mut EntityDecoder,
523 ) -> Result<Profile2D> {
524 let curve_attr = profile
526 .get(2)
527 .ok_or_else(|| Error::geometry("Arbitrary profile missing OuterCurve".to_string()))?;
528
529 let curve = decoder
530 .resolve_ref(curve_attr)?
531 .ok_or_else(|| Error::geometry("Failed to resolve OuterCurve".to_string()))?;
532
533 let outer_points = self.process_curve(&curve, decoder)?;
535 let mut result = Profile2D::new(outer_points);
536
537 if profile.ifc_type == IfcType::IfcArbitraryProfileDefWithVoids {
539 if let Some(inner_curves_attr) = profile.get(3) {
541 let inner_curves = decoder.resolve_ref_list(inner_curves_attr)?;
542 for inner_curve in inner_curves {
543 let hole_points = self.process_curve(&inner_curve, decoder)?;
544 result.add_hole(hole_points);
545 }
546 }
547 }
548
549 Ok(result)
550 }
551
552 #[inline]
554 fn process_curve(
555 &self,
556 curve: &DecodedEntity,
557 decoder: &mut EntityDecoder,
558 ) -> Result<Vec<Point2<f64>>> {
559 self.process_curve_with_depth(curve, decoder, 0)
560 }
561
562 fn process_curve_with_depth(
564 &self,
565 curve: &DecodedEntity,
566 decoder: &mut EntityDecoder,
567 depth: u32,
568 ) -> Result<Vec<Point2<f64>>> {
569 if depth > MAX_CURVE_DEPTH {
570 return Err(Error::geometry(format!(
571 "Curve nesting depth {} exceeds limit {}",
572 depth, MAX_CURVE_DEPTH
573 )));
574 }
575 match curve.ifc_type {
576 IfcType::IfcPolyline => self.process_polyline(curve, decoder),
577 IfcType::IfcIndexedPolyCurve => self.process_indexed_polycurve(curve, decoder),
578 IfcType::IfcCompositeCurve => self.process_composite_curve_with_depth(curve, decoder, depth),
579 IfcType::IfcTrimmedCurve => self.process_trimmed_curve_with_depth(curve, decoder, depth),
580 IfcType::IfcCircle => self.process_circle_curve(curve, decoder),
581 IfcType::IfcEllipse => self.process_ellipse_curve(curve, decoder),
582 _ => Err(Error::geometry(format!(
583 "Unsupported curve type: {}",
584 curve.ifc_type
585 ))),
586 }
587 }
588
589 #[inline]
591 pub fn get_curve_points(
592 &self,
593 curve: &DecodedEntity,
594 decoder: &mut EntityDecoder,
595 ) -> Result<Vec<Point3<f64>>> {
596 self.get_curve_points_with_depth(curve, decoder, 0)
597 }
598
599 fn get_curve_points_with_depth(
601 &self,
602 curve: &DecodedEntity,
603 decoder: &mut EntityDecoder,
604 depth: u32,
605 ) -> Result<Vec<Point3<f64>>> {
606 if depth > MAX_CURVE_DEPTH {
607 return Err(Error::geometry(format!(
608 "Curve nesting depth {} exceeds limit {}",
609 depth, MAX_CURVE_DEPTH
610 )));
611 }
612 match curve.ifc_type {
613 IfcType::IfcPolyline => self.process_polyline_3d(curve, decoder),
614 IfcType::IfcCompositeCurve => self.process_composite_curve_3d_with_depth(curve, decoder, depth),
615 IfcType::IfcCircle => self.process_circle_3d(curve, decoder),
616 IfcType::IfcTrimmedCurve => {
617 let points_2d = self.process_trimmed_curve_with_depth(curve, decoder, depth)?;
619 Ok(points_2d
620 .into_iter()
621 .map(|p| Point3::new(p.x, p.y, 0.0))
622 .collect())
623 }
624 _ => {
625 let points_2d = self.process_curve_with_depth(curve, decoder, depth)?;
627 Ok(points_2d
628 .into_iter()
629 .map(|p| Point3::new(p.x, p.y, 0.0))
630 .collect())
631 }
632 }
633 }
634
635 fn process_circle_3d(
637 &self,
638 curve: &DecodedEntity,
639 decoder: &mut EntityDecoder,
640 ) -> Result<Vec<Point3<f64>>> {
641 let position_attr = curve
643 .get(0)
644 .ok_or_else(|| Error::geometry("Circle missing Position".to_string()))?;
645
646 let radius = curve
647 .get_float(1)
648 .ok_or_else(|| Error::geometry("Circle missing Radius".to_string()))?;
649
650 let position = decoder
651 .resolve_ref(position_attr)?
652 .ok_or_else(|| Error::geometry("Failed to resolve circle position".to_string()))?;
653
654 let (center, x_axis, y_axis) = if position.ifc_type == IfcType::IfcAxis2Placement3D {
656 let loc_attr = position
658 .get(0)
659 .ok_or_else(|| Error::geometry("Axis2Placement3D missing Location".to_string()))?;
660 let loc = decoder
661 .resolve_ref(loc_attr)?
662 .ok_or_else(|| Error::geometry("Failed to resolve location".to_string()))?;
663 let coords = loc
664 .get(0)
665 .and_then(|v| v.as_list())
666 .ok_or_else(|| Error::geometry("Location missing coordinates".to_string()))?;
667 let center = Point3::new(
668 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
669 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
670 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
671 );
672
673 let z_axis = if let Some(axis_attr) = position.get(1) {
675 if !axis_attr.is_null() {
676 let axis = decoder.resolve_ref(axis_attr)?;
677 if let Some(axis) = axis {
678 let coords = axis.get(0).and_then(|v| v.as_list());
679 if let Some(coords) = coords {
680 Vector3::new(
681 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
682 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
683 coords.get(2).and_then(|v| v.as_float()).unwrap_or(1.0),
684 )
685 .normalize()
686 } else {
687 Vector3::new(0.0, 0.0, 1.0)
688 }
689 } else {
690 Vector3::new(0.0, 0.0, 1.0)
691 }
692 } else {
693 Vector3::new(0.0, 0.0, 1.0)
694 }
695 } else {
696 Vector3::new(0.0, 0.0, 1.0)
697 };
698
699 let x_axis = if let Some(ref_attr) = position.get(2) {
701 if !ref_attr.is_null() {
702 let ref_dir = decoder.resolve_ref(ref_attr)?;
703 if let Some(ref_dir) = ref_dir {
704 let coords = ref_dir.get(0).and_then(|v| v.as_list());
705 if let Some(coords) = coords {
706 Vector3::new(
707 coords.first().and_then(|v| v.as_float()).unwrap_or(1.0),
708 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
709 coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0),
710 )
711 .normalize()
712 } else {
713 Vector3::new(1.0, 0.0, 0.0)
714 }
715 } else {
716 Vector3::new(1.0, 0.0, 0.0)
717 }
718 } else {
719 Vector3::new(1.0, 0.0, 0.0)
720 }
721 } else {
722 Vector3::new(1.0, 0.0, 0.0)
723 };
724
725 let y_axis = z_axis.cross(&x_axis).normalize();
727
728 (center, x_axis, y_axis)
729 } else {
730 let loc_attr = position.get(0);
732 let (cx, cy) = if let Some(attr) = loc_attr {
733 let loc = decoder.resolve_ref(attr)?;
734 if let Some(loc) = loc {
735 let coords = loc.get(0).and_then(|v| v.as_list());
736 if let Some(coords) = coords {
737 (
738 coords.first().and_then(|v| v.as_float()).unwrap_or(0.0),
739 coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0),
740 )
741 } else {
742 (0.0, 0.0)
743 }
744 } else {
745 (0.0, 0.0)
746 }
747 } else {
748 (0.0, 0.0)
749 };
750 (
751 Point3::new(cx, cy, 0.0),
752 Vector3::new(1.0, 0.0, 0.0),
753 Vector3::new(0.0, 1.0, 0.0),
754 )
755 };
756
757 let segments = 24usize;
759 let mut points = Vec::with_capacity(segments + 1);
760
761 for i in 0..=segments {
762 let angle = 2.0 * std::f64::consts::PI * i as f64 / segments as f64;
763 let p = center + x_axis * (radius * angle.cos()) + y_axis * (radius * angle.sin());
764 points.push(p);
765 }
766
767 Ok(points)
768 }
769
770 fn process_polyline_3d(
772 &self,
773 curve: &DecodedEntity,
774 decoder: &mut EntityDecoder,
775 ) -> Result<Vec<Point3<f64>>> {
776 let points_attr = curve
778 .get(0)
779 .ok_or_else(|| Error::geometry("Polyline missing Points".to_string()))?;
780
781 let points = decoder.resolve_ref_list(points_attr)?;
782 let mut result = Vec::with_capacity(points.len());
783
784 for point in points {
785 let coords_attr = point
787 .get(0)
788 .ok_or_else(|| Error::geometry("CartesianPoint missing Coordinates".to_string()))?;
789
790 let coords = coords_attr
791 .as_list()
792 .ok_or_else(|| Error::geometry("Coordinates is not a list".to_string()))?;
793
794 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
795 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
796 let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
797
798 result.push(Point3::new(x, y, z));
799 }
800
801 Ok(result)
802 }
803
804 fn process_composite_curve_3d_with_depth(
806 &self,
807 curve: &DecodedEntity,
808 decoder: &mut EntityDecoder,
809 depth: u32,
810 ) -> Result<Vec<Point3<f64>>> {
811 let segments_attr = curve
813 .get(0)
814 .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
815
816 let segments = decoder.resolve_ref_list(segments_attr)?;
817 let mut result = Vec::new();
818
819 for segment in segments {
820 let parent_curve_attr = segment.get(2).ok_or_else(|| {
822 Error::geometry("CompositeCurveSegment missing ParentCurve".to_string())
823 })?;
824
825 let parent_curve = decoder
826 .resolve_ref(parent_curve_attr)?
827 .ok_or_else(|| Error::geometry("Failed to resolve ParentCurve".to_string()))?;
828
829 let same_sense = segment
831 .get(1)
832 .and_then(|v| match v {
833 ifc_lite_core::AttributeValue::Enum(e) => Some(e.as_str()),
834 _ => None,
835 })
836 .map(|e| e == "T" || e == "TRUE")
837 .unwrap_or(true);
838
839 let mut segment_points = self.get_curve_points_with_depth(&parent_curve, decoder, depth + 1)?;
840
841 if !same_sense {
842 segment_points.reverse();
843 }
844
845 if !result.is_empty() && !segment_points.is_empty() {
847 result.extend(segment_points.into_iter().skip(1));
848 } else {
849 result.extend(segment_points);
850 }
851 }
852
853 Ok(result)
854 }
855
856 fn process_trimmed_curve_with_depth(
859 &self,
860 curve: &DecodedEntity,
861 decoder: &mut EntityDecoder,
862 depth: u32,
863 ) -> Result<Vec<Point2<f64>>> {
864 let basis_attr = curve
866 .get(0)
867 .ok_or_else(|| Error::geometry("TrimmedCurve missing BasisCurve".to_string()))?;
868
869 let basis_curve = decoder
870 .resolve_ref(basis_attr)?
871 .ok_or_else(|| Error::geometry("Failed to resolve BasisCurve".to_string()))?;
872
873 let trim1 = curve.get(1).and_then(|v| self.extract_trim_param(v));
875 let trim2 = curve.get(2).and_then(|v| self.extract_trim_param(v));
876
877 let sense = curve
879 .get(3)
880 .and_then(|v| match v {
881 ifc_lite_core::AttributeValue::Enum(s) => Some(s == "T"),
882 _ => None,
883 })
884 .unwrap_or(true);
885
886 match basis_curve.ifc_type {
888 IfcType::IfcCircle | IfcType::IfcEllipse => {
889 self.process_trimmed_conic(&basis_curve, trim1, trim2, sense, decoder)
890 }
891 _ => {
892 self.process_curve_with_depth(&basis_curve, decoder, depth + 1)
894 }
895 }
896 }
897
898 fn extract_trim_param(&self, attr: &ifc_lite_core::AttributeValue) -> Option<f64> {
900 if let Some(list) = attr.as_list() {
901 for item in list {
902 if let Some(inner_list) = item.as_list() {
904 if inner_list.len() >= 2 {
905 if let Some(type_name) = inner_list.first().and_then(|v| v.as_string()) {
906 if type_name == "IFCPARAMETERVALUE" {
907 return inner_list.get(1).and_then(|v| v.as_float());
908 }
909 }
910 }
911 }
912 if let Some(f) = item.as_float() {
913 return Some(f);
914 }
915 }
916 }
917 None
918 }
919
920 fn process_trimmed_conic(
922 &self,
923 basis: &DecodedEntity,
924 trim1: Option<f64>,
925 trim2: Option<f64>,
926 sense: bool,
927 decoder: &mut EntityDecoder,
928 ) -> Result<Vec<Point2<f64>>> {
929 let radius = basis.get_float(1).unwrap_or(1.0);
930 let radius2 = if basis.ifc_type == IfcType::IfcEllipse {
931 basis.get_float(2).unwrap_or(radius)
932 } else {
933 radius
934 };
935
936 let (center, rotation) = self.get_placement_2d(basis, decoder)?;
937
938 let start_angle = trim1.unwrap_or(0.0).to_radians();
940 let mut end_angle = trim2.unwrap_or(360.0).to_radians();
941
942 if sense && end_angle < start_angle {
946 end_angle += 2.0 * std::f64::consts::PI;
947 } else if !sense && end_angle > start_angle {
948 end_angle -= 2.0 * std::f64::consts::PI;
949 }
950
951 let arc_angle = (end_angle - start_angle).abs();
954 let num_segments = ((arc_angle / std::f64::consts::FRAC_PI_2 * 8.0).ceil() as usize).max(2);
955 let mut points = Vec::with_capacity(num_segments + 1);
956
957 let angle_range = if sense {
958 end_angle - start_angle
959 } else {
960 start_angle - end_angle
961 };
962
963 for i in 0..=num_segments {
964 let t = i as f64 / num_segments as f64;
965 let angle = if sense {
966 start_angle + t * angle_range
967 } else {
968 start_angle - t * angle_range.abs()
969 };
970
971 let x = radius * angle.cos();
972 let y = radius2 * angle.sin();
973
974 let rx = x * rotation.cos() - y * rotation.sin() + center.x;
975 let ry = x * rotation.sin() + y * rotation.cos() + center.y;
976
977 points.push(Point2::new(rx, ry));
978 }
979
980 Ok(points)
981 }
982
983 fn get_placement_2d(
985 &self,
986 entity: &DecodedEntity,
987 decoder: &mut EntityDecoder,
988 ) -> Result<(Point2<f64>, f64)> {
989 let placement_attr = match entity.get(0) {
990 Some(attr) if !attr.is_null() => attr,
991 _ => return Ok((Point2::new(0.0, 0.0), 0.0)),
992 };
993
994 let placement = match decoder.resolve_ref(placement_attr)? {
995 Some(p) => p,
996 None => return Ok((Point2::new(0.0, 0.0), 0.0)),
997 };
998
999 let location_attr = placement.get(0);
1000 let center = if let Some(loc_attr) = location_attr {
1001 if let Some(loc) = decoder.resolve_ref(loc_attr)? {
1002 let coords = loc.get(0).and_then(|v| v.as_list());
1003 if let Some(coords) = coords {
1004 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
1005 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
1006 Point2::new(x, y)
1007 } else {
1008 Point2::new(0.0, 0.0)
1009 }
1010 } else {
1011 Point2::new(0.0, 0.0)
1012 }
1013 } else {
1014 Point2::new(0.0, 0.0)
1015 };
1016
1017 let rotation = if let Some(dir_attr) = placement.get(1) {
1018 if let Some(dir) = decoder.resolve_ref(dir_attr)? {
1019 let ratios = dir.get(0).and_then(|v| v.as_list());
1020 if let Some(ratios) = ratios {
1021 let x = ratios.first().and_then(|v| v.as_float()).unwrap_or(1.0);
1022 let y = ratios.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
1023 y.atan2(x)
1024 } else {
1025 0.0
1026 }
1027 } else {
1028 0.0
1029 }
1030 } else {
1031 0.0
1032 };
1033
1034 Ok((center, rotation))
1035 }
1036
1037 fn process_circle_curve(
1039 &self,
1040 curve: &DecodedEntity,
1041 decoder: &mut EntityDecoder,
1042 ) -> Result<Vec<Point2<f64>>> {
1043 let radius = curve.get_float(1).unwrap_or(1.0);
1044 let (center, rotation) = self.get_placement_2d(curve, decoder)?;
1045
1046 let segments = 36;
1047 let mut points = Vec::with_capacity(segments);
1048
1049 for i in 0..segments {
1050 let angle = (i as f64) * 2.0 * PI / (segments as f64);
1051 let x = radius * angle.cos();
1052 let y = radius * angle.sin();
1053
1054 let rx = x * rotation.cos() - y * rotation.sin() + center.x;
1055 let ry = x * rotation.sin() + y * rotation.cos() + center.y;
1056
1057 points.push(Point2::new(rx, ry));
1058 }
1059
1060 Ok(points)
1061 }
1062
1063 fn process_ellipse_curve(
1065 &self,
1066 curve: &DecodedEntity,
1067 decoder: &mut EntityDecoder,
1068 ) -> Result<Vec<Point2<f64>>> {
1069 let semi_axis1 = curve.get_float(1).unwrap_or(1.0);
1070 let semi_axis2 = curve.get_float(2).unwrap_or(1.0);
1071 let (center, rotation) = self.get_placement_2d(curve, decoder)?;
1072
1073 let segments = 36;
1074 let mut points = Vec::with_capacity(segments);
1075
1076 for i in 0..segments {
1077 let angle = (i as f64) * 2.0 * PI / (segments as f64);
1078 let x = semi_axis1 * angle.cos();
1079 let y = semi_axis2 * angle.sin();
1080
1081 let rx = x * rotation.cos() - y * rotation.sin() + center.x;
1082 let ry = x * rotation.sin() + y * rotation.cos() + center.y;
1083
1084 points.push(Point2::new(rx, ry));
1085 }
1086
1087 Ok(points)
1088 }
1089
1090 #[inline]
1093 fn process_polyline(
1094 &self,
1095 polyline: &DecodedEntity,
1096 decoder: &mut EntityDecoder,
1097 ) -> Result<Vec<Point2<f64>>> {
1098 let points_attr = polyline
1100 .get(0)
1101 .ok_or_else(|| Error::geometry("Polyline missing Points".to_string()))?;
1102
1103 let point_entities = decoder.resolve_ref_list(points_attr)?;
1104
1105 let mut points = Vec::with_capacity(point_entities.len());
1106 for point_entity in point_entities {
1107 if point_entity.ifc_type != IfcType::IfcCartesianPoint {
1108 continue;
1109 }
1110
1111 let coords_attr = point_entity
1113 .get(0)
1114 .ok_or_else(|| Error::geometry("CartesianPoint missing coordinates".to_string()))?;
1115
1116 let coords = coords_attr
1117 .as_list()
1118 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
1119
1120 let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
1121 let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
1122
1123 points.push(Point2::new(x, y));
1124 }
1125
1126 Ok(points)
1127 }
1128
1129 fn process_indexed_polycurve(
1132 &self,
1133 curve: &DecodedEntity,
1134 decoder: &mut EntityDecoder,
1135 ) -> Result<Vec<Point2<f64>>> {
1136 let points_attr = curve
1138 .get(0)
1139 .ok_or_else(|| Error::geometry("IndexedPolyCurve missing Points".to_string()))?;
1140
1141 let points_list = decoder
1142 .resolve_ref(points_attr)?
1143 .ok_or_else(|| Error::geometry("Failed to resolve Points list".to_string()))?;
1144
1145 let coord_list_attr = points_list
1147 .get(0)
1148 .ok_or_else(|| Error::geometry("CartesianPointList2D missing CoordList".to_string()))?;
1149
1150 let coord_list = coord_list_attr
1151 .as_list()
1152 .ok_or_else(|| Error::geometry("Expected coordinate list".to_string()))?;
1153
1154 let all_points: Vec<Point2<f64>> = coord_list
1156 .iter()
1157 .filter_map(|coord| {
1158 coord.as_list().and_then(|coords| {
1159 let x = coords.first()?.as_float()?;
1160 let y = coords.get(1)?.as_float()?;
1161 Some(Point2::new(x, y))
1162 })
1163 })
1164 .collect();
1165
1166 let segments_attr = curve.get(1);
1168
1169 if segments_attr.is_none() || segments_attr.map(|a| a.is_null()).unwrap_or(true) {
1170 return Ok(all_points);
1172 }
1173
1174 let segments = segments_attr
1176 .unwrap()
1177 .as_list()
1178 .ok_or_else(|| Error::geometry("Expected segments list".to_string()))?;
1179
1180 let mut result_points = Vec::new();
1181
1182 for segment in segments {
1183 let (is_arc, indices) = if let Some(segment_list) = segment.as_list() {
1187 if segment_list.len() >= 2 {
1191 let type_name = segment_list.first()
1193 .and_then(|v| v.as_string())
1194 .unwrap_or("");
1195 let is_arc_type = type_name.to_uppercase().contains("ARC");
1196
1197 if let Some(AttributeValue::List(indices_list)) = segment_list.get(1) {
1198 (is_arc_type, Some(indices_list.as_slice()))
1199 } else {
1200 (false, Some(segment_list))
1202 }
1203 } else {
1204 (false, Some(segment_list))
1206 }
1207 } else {
1208 (false, None)
1209 };
1210
1211 if let Some(indices) = indices {
1212 let idx_values: Vec<usize> = indices
1213 .iter()
1214 .filter_map(|v| v.as_float().map(|f| f as usize - 1)) .collect();
1216
1217 if is_arc && idx_values.len() == 3 {
1218 let p1 = all_points.get(idx_values[0]).copied();
1220 let p2 = all_points.get(idx_values[1]).copied(); let p3 = all_points.get(idx_values[2]).copied();
1222
1223 if let (Some(start), Some(mid), Some(end)) = (p1, p2, p3) {
1224 let chord_len =
1227 ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
1228 let mid_chord = ((mid.x - (start.x + end.x) / 2.0).powi(2)
1229 + (mid.y - (start.y + end.y) / 2.0).powi(2))
1230 .sqrt();
1231 let arc_estimate = if chord_len > 1e-10 {
1233 (mid_chord / chord_len).abs().min(1.0).acos() * 2.0
1234 } else {
1235 0.5
1236 };
1237 let num_segments = ((arc_estimate / std::f64::consts::FRAC_PI_2 * 8.0)
1238 .ceil() as usize)
1239 .clamp(4, 16);
1240 let arc_points = self.approximate_arc_3pt(start, mid, end, num_segments);
1241 for pt in arc_points {
1242 if result_points.last() != Some(&pt) {
1243 result_points.push(pt);
1244 }
1245 }
1246 }
1247 } else {
1248 for &idx in &idx_values {
1250 if let Some(&pt) = all_points.get(idx) {
1251 if result_points.last() != Some(&pt) {
1252 result_points.push(pt);
1253 }
1254 }
1255 }
1256 }
1257 }
1258 }
1260
1261 Ok(result_points)
1262 }
1263
1264 fn approximate_arc_3pt(
1266 &self,
1267 p1: Point2<f64>,
1268 p2: Point2<f64>,
1269 p3: Point2<f64>,
1270 num_segments: usize,
1271 ) -> Vec<Point2<f64>> {
1272 let ax = p1.x;
1274 let ay = p1.y;
1275 let bx = p2.x;
1276 let by = p2.y;
1277 let cx = p3.x;
1278 let cy = p3.y;
1279
1280 let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
1281
1282 let arc_span = ((p3.x - p1.x).powi(2) + (p3.y - p1.y).powi(2)).sqrt();
1285 let collinear_tolerance = 1e-6 * arc_span.powi(2).max(1e-10);
1286
1287 if d.abs() < collinear_tolerance {
1288 return vec![p1, p2, p3];
1290 }
1291
1292 let ux_num = (ax * ax + ay * ay) * (by - cy)
1294 + (bx * bx + by * by) * (cy - ay)
1295 + (cx * cx + cy * cy) * (ay - by);
1296 let uy_num = (ax * ax + ay * ay) * (cx - bx)
1297 + (bx * bx + by * by) * (ax - cx)
1298 + (cx * cx + cy * cy) * (bx - ax);
1299 let ux = ux_num / d;
1300 let uy = uy_num / d;
1301 let center = Point2::new(ux, uy);
1302 let radius = ((p1.x - center.x).powi(2) + (p1.y - center.y).powi(2)).sqrt();
1303
1304 if radius > arc_span * 100.0 {
1306 return vec![p1, p2, p3];
1307 }
1308
1309 let angle1 = (p1.y - center.y).atan2(p1.x - center.x);
1311 let angle3 = (p3.y - center.y).atan2(p3.x - center.x);
1312 let angle2 = (p2.y - center.y).atan2(p2.x - center.x);
1313
1314 fn normalize_angle(a: f64) -> f64 {
1316 let mut a = a % (2.0 * PI);
1317 if a > PI {
1318 a -= 2.0 * PI;
1319 } else if a < -PI {
1320 a += 2.0 * PI;
1321 }
1322 a
1323 }
1324
1325 let diff_direct = normalize_angle(angle3 - angle1);
1328 let diff_to_mid = normalize_angle(angle2 - angle1);
1329
1330 let go_direct = if diff_direct > 0.0 {
1331 diff_to_mid > 0.0 && diff_to_mid < diff_direct
1333 } else {
1334 diff_to_mid < 0.0 && diff_to_mid > diff_direct
1336 };
1337
1338 let start_angle = angle1;
1339 let end_angle = if go_direct {
1340 angle1 + diff_direct
1341 } else {
1342 if diff_direct > 0.0 {
1344 angle1 + diff_direct - 2.0 * PI
1345 } else {
1346 angle1 + diff_direct + 2.0 * PI
1347 }
1348 };
1349
1350 let mut points = Vec::with_capacity(num_segments + 1);
1352 for i in 0..=num_segments {
1353 let t = i as f64 / num_segments as f64;
1354 let angle = start_angle + t * (end_angle - start_angle);
1355 points.push(Point2::new(
1356 center.x + radius * angle.cos(),
1357 center.y + radius * angle.sin(),
1358 ));
1359 }
1360
1361 points
1362 }
1363
1364 fn process_composite_curve_with_depth(
1367 &self,
1368 curve: &DecodedEntity,
1369 decoder: &mut EntityDecoder,
1370 depth: u32,
1371 ) -> Result<Vec<Point2<f64>>> {
1372 let segments_attr = curve
1374 .get(0)
1375 .ok_or_else(|| Error::geometry("CompositeCurve missing Segments".to_string()))?;
1376
1377 let segments = decoder.resolve_ref_list(segments_attr)?;
1378
1379 let mut all_points = Vec::new();
1380
1381 for segment in segments {
1382 if segment.ifc_type != IfcType::IfcCompositeCurveSegment {
1384 continue;
1385 }
1386
1387 let parent_curve_attr = segment.get(2).ok_or_else(|| {
1389 Error::geometry("CompositeCurveSegment missing ParentCurve".to_string())
1390 })?;
1391
1392 let parent_curve = decoder
1393 .resolve_ref(parent_curve_attr)?
1394 .ok_or_else(|| Error::geometry("Failed to resolve ParentCurve".to_string()))?;
1395
1396 let same_sense = segment
1399 .get(1)
1400 .and_then(|v| match v {
1401 ifc_lite_core::AttributeValue::Enum(s) => Some(s == "T" || s == "TRUE"),
1402 _ => None,
1403 })
1404 .unwrap_or(true);
1405
1406 let mut segment_points = self.process_curve_with_depth(&parent_curve, decoder, depth + 1)?;
1408
1409 if !same_sense {
1410 segment_points.reverse();
1411 }
1412
1413 for pt in segment_points {
1415 if all_points.last() != Some(&pt) {
1416 all_points.push(pt);
1417 }
1418 }
1419 }
1420
1421 Ok(all_points)
1422 }
1423
1424 fn process_composite(
1427 &self,
1428 profile: &DecodedEntity,
1429 decoder: &mut EntityDecoder,
1430 ) -> Result<Profile2D> {
1431 let profiles_attr = profile
1433 .get(2)
1434 .ok_or_else(|| Error::geometry("Composite profile missing Profiles".to_string()))?;
1435
1436 let sub_profiles = decoder.resolve_ref_list(profiles_attr)?;
1437
1438 if sub_profiles.is_empty() {
1439 return Err(Error::geometry(
1440 "Composite profile has no sub-profiles".to_string(),
1441 ));
1442 }
1443
1444 let mut result = self.process(&sub_profiles[0], decoder)?;
1446
1447 for sub_profile in &sub_profiles[1..] {
1449 let hole = self.process(sub_profile, decoder)?;
1450 result.add_hole(hole.outer);
1451 }
1452
1453 Ok(result)
1454 }
1455}
1456
1457#[cfg(test)]
1458mod tests {
1459 use super::*;
1460
1461 #[test]
1462 fn test_rectangle_profile() {
1463 let content = r#"
1464#1=IFCRECTANGLEPROFILEDEF(.AREA.,$,$,100.0,200.0);
1465"#;
1466
1467 let mut decoder = EntityDecoder::new(content);
1468 let schema = IfcSchema::new();
1469 let processor = ProfileProcessor::new(schema);
1470
1471 let profile_entity = decoder.decode_by_id(1).unwrap();
1472 let profile = processor.process(&profile_entity, &mut decoder).unwrap();
1473
1474 assert_eq!(profile.outer.len(), 4);
1475 assert!(!profile.outer.is_empty());
1476 }
1477
1478 #[test]
1479 fn test_circle_profile() {
1480 let content = r#"
1481#1=IFCCIRCLEPROFILEDEF(.AREA.,$,$,50.0);
1482"#;
1483
1484 let mut decoder = EntityDecoder::new(content);
1485 let schema = IfcSchema::new();
1486 let processor = ProfileProcessor::new(schema);
1487
1488 let profile_entity = decoder.decode_by_id(1).unwrap();
1489 let profile = processor.process(&profile_entity, &mut decoder).unwrap();
1490
1491 assert_eq!(profile.outer.len(), 36); assert!(!profile.outer.is_empty());
1493 }
1494
1495 #[test]
1496 fn test_i_shape_profile() {
1497 let content = r#"
1498#1=IFCISHAPEPROFILEDEF(.AREA.,$,$,200.0,300.0,10.0,15.0,$,$,$,$);
1499"#;
1500
1501 let mut decoder = EntityDecoder::new(content);
1502 let schema = IfcSchema::new();
1503 let processor = ProfileProcessor::new(schema);
1504
1505 let profile_entity = decoder.decode_by_id(1).unwrap();
1506 let profile = processor.process(&profile_entity, &mut decoder).unwrap();
1507
1508 assert_eq!(profile.outer.len(), 12); assert!(!profile.outer.is_empty());
1510 }
1511
1512 #[test]
1513 fn test_arbitrary_profile() {
1514 let content = r#"
1515#1=IFCCARTESIANPOINT((0.0,0.0));
1516#2=IFCCARTESIANPOINT((100.0,0.0));
1517#3=IFCCARTESIANPOINT((100.0,100.0));
1518#4=IFCCARTESIANPOINT((0.0,100.0));
1519#5=IFCPOLYLINE((#1,#2,#3,#4,#1));
1520#6=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#5);
1521"#;
1522
1523 let mut decoder = EntityDecoder::new(content);
1524 let schema = IfcSchema::new();
1525 let processor = ProfileProcessor::new(schema);
1526
1527 let profile_entity = decoder.decode_by_id(6).unwrap();
1528 let profile = processor.process(&profile_entity, &mut decoder).unwrap();
1529
1530 assert_eq!(profile.outer.len(), 5); assert!(!profile.outer.is_empty());
1532 }
1533}