1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//! IFC source attribution for format-neutral geometry nodes.
//!
//! Provenance is deliberately a side table. `axiolid-model` stays IFC-agnostic,
//! while diagnostics and consumers of a lowered result can still trace a node
//! back to the entity that emitted it.
use axiolid_model::NodeId;
use ifc_model::EntityId;
use std::collections::{BTreeMap, BTreeSet};
/// Source IFC entities for nodes in one lowered geometry graph.
///
/// The map is partial: caller-synthesized nodes created outside an active IFC
/// entity scope have no source instead of receiving a fabricated entity id.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ProvenanceMap {
sources: BTreeMap<NodeId, EntityId>,
/// Faces left out under `DegenerateFacePolicy::DropAndReport` (#46).
dropped_faces: BTreeSet<EntityId>,
}
impl ProvenanceMap {
/// Return the IFC entity that emitted `node`, if it has an IFC source.
pub fn source(&self, node: NodeId) -> Option<EntityId> {
self.sources.get(&node).copied()
}
/// Number of attributed graph nodes.
pub fn len(&self) -> usize {
self.sources.len()
}
/// Whether no graph nodes carry IFC attribution.
pub fn is_empty(&self) -> bool {
self.sources.is_empty()
}
/// Iterate deterministically over `(node, source entity)` pairs.
pub fn iter(&self) -> impl Iterator<Item = (NodeId, EntityId)> + '_ {
self.sources.iter().map(|(&node, &source)| (node, source))
}
/// `IfcFace` entities left out because their outer loop collapsed, in
/// ascending id.
///
/// Always empty under the default `DegenerateFacePolicy::Refuse`. A face
/// shared by several items is listed once. A non-empty list means the
/// lowered geometry is NOT exactly what the file authored: a consumer
/// using it as evidence decides whether that is acceptable.
pub fn dropped_faces(&self) -> impl Iterator<Item = EntityId> + '_ {
self.dropped_faces.iter().copied()
}
pub(crate) fn record_dropped_face(&mut self, face: EntityId) {
self.dropped_faces.insert(face);
}
pub(crate) fn record(&mut self, node: NodeId, source: EntityId) {
if let Some(previous) = self.sources.insert(node, source) {
debug_assert_eq!(
previous, source,
"one graph node cannot have two IFC sources"
);
}
}
}