ifc_lite_geometry/processors/boolean/
failures.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//! The boolean processor's own failure log: record, drain, hand off, plus the
6//! `GeometryProcessor` trait impl those methods serve (module-size ratchet
7//! moved the whole impl block here when #4083 added two more methods to it).
8//!
9//! Split out of `boolean/mod.rs` (module-size ratchet) when the one-record-
10//! per-step rule needed a home (#3821).
11
12use super::{BooleanClippingProcessor, OperandPath};
13use crate::diagnostics::{BoolFailure, BoolFailureReason, BoolOp};
14use crate::router::GeometryProcessor;
15use crate::{Mesh, Result, TessellationQuality};
16use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
17
18impl GeometryProcessor for BooleanClippingProcessor {
19    fn process(
20        &self,
21        entity: &DecodedEntity,
22        decoder: &mut EntityDecoder,
23        schema: &IfcSchema,
24        quality: TessellationQuality,
25    ) -> Result<Mesh> {
26        let mut visited = OperandPath::default();
27        self.process_with_depth(entity, decoder, schema, 0, quality, &mut visited)
28            .map(|(mesh, _)| mesh)
29    }
30
31    fn supported_types(&self) -> Vec<IfcType> {
32        vec![IfcType::IfcBooleanResult, IfcType::IfcBooleanClippingResult]
33    }
34
35    /// Hand the log to the router (#3821); rationale on the trait method.
36    fn take_bool_failures(&self) -> Vec<BoolFailure> {
37        self.take_failures()
38    }
39
40    /// #4083: reuses `failure_mark` below.
41    fn bool_failure_count(&self) -> usize {
42        self.failure_mark()
43    }
44
45    /// #4083: reuses `rewind_to` below to retract a redundant record.
46    fn truncate_bool_failures_to(&self, since: usize) {
47        self.rewind_to(since);
48    }
49}
50
51impl BooleanClippingProcessor {
52    /// Drain the boolean-failure log accumulated since this processor was
53    /// created (or the last `take_failures` call).
54    pub fn take_failures(&self) -> Vec<BoolFailure> {
55        std::mem::take(&mut *self.failures.borrow_mut())
56    }
57
58    pub(super) fn record_failure(&self, op: BoolOp, reason: BoolFailureReason) {
59        self.failures.borrow_mut().push(BoolFailure::new(op, reason));
60    }
61
62    /// Move a drained log into this processor's log. Used after a transient
63    /// helper — a `ClippingProcessor`, or the `CsgSolidProcessor` built for an
64    /// `IfcCsgSolid` operand — is about to drop and would take its records
65    /// with it. Takes the drained `Vec` rather than the producer, so one
66    /// method covers every transient kind.
67    pub(super) fn absorb_failures(&self, failures: Vec<BoolFailure>) {
68        self.failures.borrow_mut().extend(failures);
69    }
70
71    /// A mark in the failure log, for [`Self::rewind_to`].
72    pub(super) fn failure_mark(&self) -> usize {
73        self.failures.borrow().len()
74    }
75
76    /// Discard everything recorded since `mark`.
77    ///
78    /// Only for a SPECULATIVE attempt whose work the caller is about to redo.
79    /// `try_union_polygonal_chain` is a batched attempt at a chain the
80    /// sequential walk also resolves; when it gives up, `process_with_depth`
81    /// re-meshes the same base operand and the same cutters and records the
82    /// same losses again. Its trial subtracts already dropped their own probe
83    /// failures for this reason, but the base operand did not, so one authored
84    /// unsupported operand under a deferring chain was counted TWICE — the
85    /// total inflated and the reason breakdown skewed, which is what the
86    /// viewer's "top failure reason" reads.
87    ///
88    /// NOT a way to suppress a failure that really happened: the record has to
89    /// stay reachable from somewhere, and here that somewhere is the second
90    /// walk. A failure the sequential path does NOT re-encounter (the
91    /// `CutterUnionUnavailable` the union attempt alone can see) must be
92    /// recorded AFTER the rewind, not before it.
93    pub(super) fn rewind_to(&self, mark: usize) {
94        self.failures.borrow_mut().truncate(mark);
95    }
96
97    /// [`Self::rewind_to`] plus the deferral itself, so a guard in
98    /// `try_union_polygonal_chain` reads as one line and cannot rewind without
99    /// deferring or defer without rewinding.
100    pub(super) fn defer_after<T>(&self, mark: usize) -> crate::Result<Option<T>> {
101        self.rewind_to(mark);
102        Ok(None)
103    }
104
105    /// Record the `EmptyOperand` consequence for an operand that meshed
106    /// empty — UNLESS [`Self::process_operand_checked`] reports its loss
107    /// already on record (an unsupported operand, or a nested one emptied by
108    /// a drop inside it). One dropped step, one record: see the flag's
109    /// rationale there.
110    pub(super) fn record_empty_operand(&self, op: BoolOp, already_recorded: bool) {
111        if !already_recorded {
112            self.record_failure(op, BoolFailureReason::EmptyOperand);
113        }
114    }
115}