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:
- each
inheritsentry, in order, copies the children and attributes of the node it references; - each
childrenentry adds or replaces a child with the node it references, and aNoneentry deletes the child, inherited or not; - 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 ownTODOinFindRootsOrCyclesasks. Upstream compares whole reference strings, so it misses cycles throughhead/childreferences and recurses until the stack overflows, and it lists a node referenced only ashead/childas 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()));