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}