ifc_lite_export/
rebar_schedule.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Opt-in reinforcing-bar schedule inputs, retaining IFC authoring provenance.
6
7use std::collections::{BTreeMap, HashMap, HashSet};
8
9use ifc_lite_core::{
10    attribute_names_for_schema, keyword_eq, EntityDecoder, EntityScanner, ProjectUnits,
11};
12use ifc_lite_processing::{
13    check_swept_disk, extract_swept_disk_views, DirectrixMetrics, SweptDiskCheckError,
14    SweptDiskCheckOptions, SweptDiskCheckReport, SweptDiskInstance, SweptDiskSourceKey,
15};
16use serde::Serialize;
17
18use crate::rebar_attributes::{attribute, FIELDS};
19use crate::schema_detect::detect_schema;
20use crate::rebar_preflight::{assess_sweep, RebarPreflightLimits, RebarPreflightReport, RebarSchedulePreflightError};
21use crate::rebar_fabrication::{assess_fabrication, AuthoredDiameterContext, RebarFabricationPolicy, RebarFabricationReport,
22    RebarScheduleFabricationError};
23
24/// Where a schema-declared value was authored. An occurrence wins a conflict.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
26#[serde(rename_all = "snake_case")]
27#[non_exhaustive]
28pub enum RebarSource {
29    Occurrence,
30    Type,
31}
32
33/// Authored IFC value. Measures retain the file value and an SI conversion.
34#[derive(Debug, Clone, PartialEq, Serialize)]
35#[serde(tag = "kind", rename_all = "snake_case")]
36#[non_exhaustive]
37pub enum AuthoredRebarValue {
38    Text {
39        value: String,
40    },
41    Measure {
42        value_file_units: f64,
43        value_si: f64,
44        si_unit: &'static str,
45    },
46}
47
48/// One explicitly authored EXPRESS attribute, keyed by its exact name.
49#[derive(Debug, Clone, PartialEq, Serialize)]
50#[non_exhaustive]
51pub struct AuthoredRebarAttribute {
52    pub source: RebarSource,
53    pub source_id: u32,
54    pub value: AuthoredRebarValue,
55}
56
57/// One represented swept-disk source. This is not a physical bar count.
58#[derive(Debug, Clone, Serialize)]
59#[non_exhaustive]
60pub struct RebarSweep {
61    pub occurrence_index: usize,
62    /// Reusable raw source identity; absent only if the definition output
63    /// budget omitted this product while the world description remained.
64    pub source: Option<SweptDiskSourceKey>,
65    pub solid_id: u32,
66    pub directrix_id: u32,
67    pub mapping_path: Vec<u32>,
68    pub source_modified: bool,
69    pub status: ifc_lite_geometry::analytic::AnalyticStatus,
70    /// Effective world radius for complete paths; source radius in metres otherwise.
71    pub radius_m: f64,
72    /// Effective world inner radius for complete paths; source value otherwise.
73    pub inner_radius_m: Option<f64>,
74    pub directrix_metrics: Option<DirectrixMetrics>,
75    pub checks: SweptDiskCheckReport,
76    /// Present only when caller requested fabrication preflight comparisons.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub preflight: Option<RebarPreflightReport>,
79    /// Present only for the caller-supplied optional fabrication policy.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub fabrication_precheck: Option<RebarFabricationReport>,
82}
83
84/// One IFC `IfcReinforcingBar` entity, including bars without analytic geometry.
85#[derive(Debug, Clone, Serialize)]
86#[non_exhaustive]
87#[allow(non_snake_case)] // Public IFC attributes retain their EXPRESS names.
88pub struct RebarScheduleRow {
89    /// The occurrence's `IfcRoot.GlobalId`.
90    pub GlobalId: Option<String>,
91    /// The occurrence's `IfcRoot.Name`.
92    pub Name: Option<String>,
93    pub type_id: Option<u32>,
94    pub authored: BTreeMap<String, AuthoredRebarAttribute>,
95    pub sweeps: Vec<RebarSweep>,
96    pub geometry_unavailable_reason: Option<String>,
97    /// For an opted-in row with no represented sweep.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub preflight_skipped_reason: Option<String>,
100    /// For an opted-in policy with no represented swept-disk source.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub fabrication_precheck_skipped_reason: Option<String>,
103    pub diagnostics: Vec<String>,
104}
105
106/// Occurrence-keyed schedule inputs. `physical_bar_count` is deliberately
107/// absent: IFC permits one entity to represent multiple manufactured bars.
108#[derive(Debug, Clone, Serialize)]
109#[non_exhaustive]
110pub struct RebarSchedule {
111    pub units: &'static str,
112    pub coordinate_space: &'static str,
113    pub length_unit_scale: f64,
114    pub bar_entity_count: usize,
115    pub represented_sweep_count: usize,
116    pub rows: BTreeMap<u32, RebarScheduleRow>,
117    pub diagnostics: Vec<String>,
118}
119
120const MAX_TYPE_RELATION_REFERENCES: usize = 100_000;
121
122#[path = "rebar_schedule_links.rs"]
123mod links;
124
125/// Build an occurrence-aware schedule without tessellating. The optional ID set
126/// filters `IfcReinforcingBar` occurrence IDs; an empty set returns no rows.
127/// Geometric findings concern source paths, not bending-code compliance.
128pub fn build_rebar_schedule(
129    content: &[u8],
130    ids: Option<&HashSet<u32>>,
131    options: &SweptDiskCheckOptions,
132) -> Result<RebarSchedule, SweptDiskCheckError> {
133    build_rebar_schedule_impl(content, ids, options, None, None)
134}
135
136/// Build a schedule with caller-supplied geometric fabrication comparisons.
137/// Results do not certify a cutting length or code compliance.
138pub fn build_rebar_schedule_with_preflight(
139    content: &[u8],
140    ids: Option<&HashSet<u32>>,
141    options: &SweptDiskCheckOptions,
142    limits: &RebarPreflightLimits,
143) -> Result<RebarSchedule, RebarSchedulePreflightError> {
144    limits.validate().map_err(RebarSchedulePreflightError::Limits)?;
145    build_rebar_schedule_impl(content, ids, options, Some(limits), None)
146        .map_err(RebarSchedulePreflightError::SweepChecks)
147}
148
149/// Compare only the caller's requested SI constraints against represented
150/// source geometry. The outcome is always `precheck_only`.
151pub fn build_rebar_schedule_with_fabrication_precheck(
152    content: &[u8],
153    ids: Option<&HashSet<u32>>,
154    options: &SweptDiskCheckOptions,
155    policy: &RebarFabricationPolicy,
156) -> Result<RebarSchedule, RebarScheduleFabricationError> {
157    policy.validate().map_err(RebarScheduleFabricationError::Policy)?;
158    build_rebar_schedule_impl(content, ids, options, None, Some(policy))
159        .map_err(RebarScheduleFabricationError::SweepChecks)
160}
161
162#[path = "rebar_schedule_build.rs"]
163mod build;
164use build::build_rebar_schedule_impl;
165
166#[cfg(test)]
167#[path = "rebar_schedule_tests.rs"]
168mod tests;