IFClite TypeScript API
    Preparing search index...

    Function collectReferencedEntityIds

    • Collect all entity IDs transitively referenced from a set of root entities.

      Starting from rootIds, reads each entity's raw bytes from the source buffer and extracts all #ID references via byte-level scanning (no string allocation). Recursively follows references to build a complete closure that guarantees referential integrity.

      Parameters

      • rootIds: Set<number>

        Seed entity IDs to start the walk from

      • source: Uint8Array<ArrayBufferLike> | IfcSourceBytes

        The original STEP file source buffer

      • entityIndex: {
            effectiveType?(id: number, recordType: string): string;
            get(
                id: number,
            ): { byteLength: number; byteOffset: number; type?: string } | undefined;
            has(id: number): boolean;
            hasSourceMutation?(id: number): boolean;
            refGroupsOf?(
                id: number,
                sourceGroups?: readonly (number | readonly number[] | undefined)[],
            ): readonly (number | readonly number[])[] | undefined;
            refsOf?(id: number): readonly number[] | undefined;
        }

        Map of expressId → byte position in source. An index that also answers refsOf (see EffectiveEntityIndex) contributes overlay-created records, whose references live in an authored attribute list rather than in the source buffer — without that hook the walk stops dead at a created entity and everything reachable only through it is silently dropped from the export (#2012).

      • OptionalexcludeIds: Set<number>

        Entity IDs to NEVER follow during the walk. Also gates whether an IFCREL* entity may act as a BRIDGE into what it references: when every id it names would be filtered out by filterHiddenRefsFromRelationshipLine (same predicate — the relationship's own line would be withheld entirely), the walk does not follow ANY of its references. Without this, a relationship whose sole subject is excluded still pulled its pset/material/type/classification target into the closure — those are never themselves in excludeIds (not products) — so the target shipped as an orphan line nothing in the output names (#2548).

      • OptionalisRefExcluded: (id: number) => boolean

        The caller's own "is this id excluded from what a relationship names" predicate for the BRIDGE decision. When supplied, the bridge uses THIS predicate instead of inventing excludeIds.has(id) || !entityIndex.has(id) as a proxy for it. The proxy and a caller's real predicate can disagree on an id that never existed in the file at all (not hidden, not deleted — just absent, e.g. a pre-existing dangling ref in a truncated source): the proxy treats "not in the index" as excluded, blocking the bridge, while a caller whose predicate only excludes a hidden PRODUCT or a TOMBSTONED id does not, and still emits the relationship's line naming it. Left to disagree, that combination drops a VISIBLE sibling's pset from the closure while the unfiltered output line still names it — a dangling ref the emission pass did not intend to create. Callers with no caller-side predicate to share (demesh-prune.ts, merged-exporter.ts, whose own IFCREL* output-line filter already reduces to the same !entityIndex.has proxy) omit this and keep the previous behaviour.

        NOT NECESSARILY THE CALLER'S OUTPUT PREDICATE. #2637's remedy was worded as "the same function call, not two expressions that happened to agree", and StepExporter did pass its single output predicate here. It no longer can: its output filter is now isOmittedFromOutput, built on willBeEmitted, whose first act is to consult the very allowedEntityIds set THIS call produces, so wiring it in is circular (measured: ReferenceError: Cannot access 'willBeEmitted' before initialization; hoisting past the TDZ only trades the throw for a predicate whose answer changes as the set fills). StepExporter therefore passes the narrower isRefExcludedDuringClosureWalk — hidden product or tombstone — while its output filter also answers for the closure, an unreadable source ref and a geometry exclusion.

        The two are ORDERED, not merely different: every id the walk predicate excludes, the output predicate excludes too. So a relationship the walk refuses to bridge is also one the output withholds, and the #2548 orphan this parameter exists to prevent cannot come back that way. The reverse gap is open and observable: for an id the walk admits and the output omits, the walk bridges through a relationship the output then withholds, leaving its target in the closure with nothing naming it. Pinned by unreadable-ref-dangling.test.ts ("walk and output predicates diverge"), and an open design question rather than a settled contract.

        Performance: O(total bytes of included entities). Each entity visited once. Uses byte-level scanning — no TextDecoder, no regex, no string allocation — except for a SOURCE-BACKED IFCREL* entity when excludeIds was passed at all, which decodes and parses its line once, for BOTH the bridge decision and the refs enqueued (the same parsed groups feed both — see sourceRelGroups below), rather than once per purpose.

      Returns Set<number>