Function compose

Source
pub fn compose(
    flat: &IndexMap<String, FlatNode>,
) -> Result<Composition, ComposeError>
Expand description

Composes flattened nodes into a tree, as upstream’s LoadIfcxFile does after flattening.

For each node, in this order:

  1. each inherits entry, in order, copies the children and attributes of the node it references;
  2. each children entry adds or replaces a child with the node it references, and a None entry deletes the child, inherited or not;
  3. local attributes override inherited ones.

A reference is a node path optionally followed by child names, such as type/Body; the child names are looked up in the composed node. Then nodes with paths such as wall/Body are applied to the matching child, parents before children; one whose child does not exist is ignored.

Every head path is composed once and shared. A node costs the size of its own maps plus the top-level maps of the nodes it inherits, not the size of any sub-tree, and an edit such as wall/Body copies only the nodes along its path. Nothing recurses per tree level, including dropping the result.

§Errors

ComposeError::Cycle if references form a cycle, checked before anything is composed, and ComposeError::UnknownReference for a reference that does not resolve.

§Differences from upstream

  • Cycles and roots look at the head of each reference, the part before the first /, as upstream’s own TODO in FindRootsOrCycles asks. Upstream compares whole reference strings, so it misses cycles through head/child references and recurses until the stack overflows, and it lists a node referenced only as head/child as a root as well.
  • A reference to a path with no node is an error. Upstream composes it as an empty node.
use openbim_ifcx::{compose, flatten, IfcxNode};

let nodes: Vec<IfcxNode> = serde_json::from_str(r#"[
    {"path": "type", "children": {"Body": "mesh"}, "attributes": {"x::kind": "T"}},
    {"path": "mesh", "attributes": {"x::points": [0, 1, 2]}},
    {"path": "wall", "inherits": {"Type": "type"}, "attributes": {"x::kind": "W"}}
]"#)?;
let composed = compose(&flatten(&nodes))?;
assert_eq!(composed.roots(), ["wall"]);
let wall = composed.get("wall").unwrap();
assert_eq!(*wall.attributes["x::kind"], "W");
// The body is the composed `mesh` node itself, not a copy.
assert!(std::sync::Arc::ptr_eq(&wall.children["Body"], composed.get("mesh").unwrap()));