ifc_lite_processing/analytic_export/
quantities.rs1use super::{check_swept_disk, DirectrixMetrics, SweptDiskCheckOptions, SweptDiskFindingCode, SweptDiskOccurrence};
8use ifc_lite_geometry::analytic::{AnalyticExtrusion, AnalyticStatus, ProfileLoopKind};
9use serde::Serialize;
10
11#[derive(Debug, Clone, PartialEq, Serialize)]
15#[non_exhaustive]
16pub struct SweptDiskNominalQuantities {
17 pub cross_section_area: f64,
19 pub nominal_volume: f64,
21 pub outer_lateral_area: f64,
23 pub inner_lateral_area: Option<f64>,
25}
26
27impl SweptDiskNominalQuantities {
28 pub(super) fn from_occurrence(
29 disk: &SweptDiskOccurrence,
30 metrics: &DirectrixMetrics,
31 ) -> Option<Self> {
32 if disk.source_modified || !disk.radius.is_finite() || disk.radius <= 0.0 {
35 return None;
36 }
37 let inner = disk.inner_radius.unwrap_or(0.0);
38 if !inner.is_finite()
39 || disk.inner_radius.is_some_and(|radius| radius <= 0.0)
40 || inner >= disk.radius
41 {
42 return None;
43 }
44 let length = metrics.total_length;
45 if !length.is_finite() || length <= 0.0 {
46 return None;
47 }
48 let checks = check_swept_disk(disk, &SweptDiskCheckOptions::default()).ok()?;
52 if checks.skipped_reason.is_some() || checks.findings.iter().any(|finding|
53 finding.code != SweptDiskFindingCode::TangentDiscontinuity
54 ) {
55 return None;
56 }
57 let cross_section_area =
60 std::f64::consts::PI * (disk.radius - inner) * (disk.radius + inner);
61 let nominal_volume = cross_section_area * length;
62 let outer_lateral_area = std::f64::consts::TAU * disk.radius * length;
63 let inner_lateral_area = disk
64 .inner_radius
65 .map(|radius| std::f64::consts::TAU * radius * length);
66 if !cross_section_area.is_finite()
67 || !nominal_volume.is_finite()
68 || !outer_lateral_area.is_finite()
69 || inner_lateral_area.is_some_and(|area| !area.is_finite())
70 {
71 return None;
72 }
73 Some(Self {
74 cross_section_area,
75 nominal_volume,
76 outer_lateral_area,
77 inner_lateral_area,
78 })
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize)]
85#[non_exhaustive]
86pub struct ExtrusionNominalQuantities {
87 pub profile_area: f64,
89 pub projected_height: f64,
91 pub nominal_volume: f64,
93}
94
95pub fn extrusion_nominal_quantities(
99 extrusion: &AnalyticExtrusion,
100) -> Option<ExtrusionNominalQuantities> {
101 if extrusion.status != AnalyticStatus::Complete {
102 return None;
103 }
104 let profile = extrusion.profile.as_ref()?;
105 if profile.status != AnalyticStatus::Complete || profile.profile_type.as_deref() != Some("AREA")
106 {
107 return None;
108 }
109 let mut profile_area = 0.0;
110 let mut outer_count = 0;
111 for boundary in &profile.loops {
112 let area = boundary.signed_area.abs();
113 if !area.is_finite() || area <= 0.0 {
114 return None;
115 }
116 match boundary.kind {
117 ProfileLoopKind::Outer => {
118 outer_count += 1;
119 profile_area += area;
120 }
121 ProfileLoopKind::Inner => profile_area -= area,
122 }
123 }
124 if outer_count != 1 || !profile_area.is_finite() || profile_area <= 0.0 {
125 return None;
126 }
127 let depth = extrusion.depth?;
128 let axis = extrusion.axis_unit_vector?;
129 let projected_height = depth * axis[2].abs();
130 let nominal_volume = profile_area * projected_height;
131 if !projected_height.is_finite() || projected_height <= 0.0 || !nominal_volume.is_finite() {
132 return None;
133 }
134 Some(ExtrusionNominalQuantities {
135 profile_area,
136 projected_height,
137 nominal_volume,
138 })
139}