@ifc-lite/parser 4.3.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/attribute-helpers.d.ts +38 -4
  2. package/dist/attribute-helpers.d.ts.map +1 -1
  3. package/dist/attribute-helpers.js +71 -4
  4. package/dist/attribute-helpers.js.map +1 -1
  5. package/dist/columnar-parser-attributes.d.ts.map +1 -1
  6. package/dist/columnar-parser-attributes.js +35 -5
  7. package/dist/columnar-parser-attributes.js.map +1 -1
  8. package/dist/columnar-parser-indexes.d.ts.map +1 -1
  9. package/dist/columnar-parser-indexes.js +17 -2
  10. package/dist/columnar-parser-indexes.js.map +1 -1
  11. package/dist/columnar-parser-relationships.d.ts.map +1 -1
  12. package/dist/columnar-parser-relationships.js +6 -1
  13. package/dist/columnar-parser-relationships.js.map +1 -1
  14. package/dist/columnar-parser.d.ts +3 -1
  15. package/dist/columnar-parser.d.ts.map +1 -1
  16. package/dist/columnar-parser.js +27 -76
  17. package/dist/columnar-parser.js.map +1 -1
  18. package/dist/compact-entity-index-transport.d.ts +23 -0
  19. package/dist/compact-entity-index-transport.d.ts.map +1 -0
  20. package/dist/compact-entity-index-transport.js +28 -0
  21. package/dist/compact-entity-index-transport.js.map +1 -0
  22. package/dist/compact-entity-index.d.ts +0 -10
  23. package/dist/compact-entity-index.d.ts.map +1 -1
  24. package/dist/compact-entity-index.js +31 -24
  25. package/dist/compact-entity-index.js.map +1 -1
  26. package/dist/data-store-transport.d.ts +2 -12
  27. package/dist/data-store-transport.d.ts.map +1 -1
  28. package/dist/data-store-transport.js +7 -20
  29. package/dist/data-store-transport.js.map +1 -1
  30. package/dist/entity-extractor.d.ts.map +1 -1
  31. package/dist/entity-extractor.js +101 -6
  32. package/dist/entity-extractor.js.map +1 -1
  33. package/dist/entity-refs-from-index.d.ts.map +1 -1
  34. package/dist/entity-refs-from-index.js +12 -2
  35. package/dist/entity-refs-from-index.js.map +1 -1
  36. package/dist/entity-scanner.d.ts +63 -0
  37. package/dist/entity-scanner.d.ts.map +1 -1
  38. package/dist/entity-scanner.js +79 -2
  39. package/dist/entity-scanner.js.map +1 -1
  40. package/dist/express-id.d.ts +47 -0
  41. package/dist/express-id.d.ts.map +1 -0
  42. package/dist/express-id.js +57 -0
  43. package/dist/express-id.js.map +1 -0
  44. package/dist/generated/entities.d.ts +1 -1
  45. package/dist/generated/entities.d.ts.map +1 -1
  46. package/dist/generated/schema-registry.js +146 -146
  47. package/dist/generated/schema-registry.js.map +1 -1
  48. package/dist/georef-epset.d.ts +25 -0
  49. package/dist/georef-epset.d.ts.map +1 -0
  50. package/dist/georef-epset.js +214 -0
  51. package/dist/georef-epset.js.map +1 -0
  52. package/dist/georef-extractor.d.ts +9 -12
  53. package/dist/georef-extractor.d.ts.map +1 -1
  54. package/dist/georef-extractor.js +66 -304
  55. package/dist/georef-extractor.js.map +1 -1
  56. package/dist/georef-map-conversion.d.ts +29 -0
  57. package/dist/georef-map-conversion.d.ts.map +1 -0
  58. package/dist/georef-map-conversion.js +75 -0
  59. package/dist/georef-map-conversion.js.map +1 -0
  60. package/dist/georef-transform.d.ts +25 -0
  61. package/dist/georef-transform.d.ts.map +1 -0
  62. package/dist/georef-transform.js +97 -0
  63. package/dist/georef-transform.js.map +1 -0
  64. package/dist/ifc-schema.d.ts +4 -2
  65. package/dist/ifc-schema.d.ts.map +1 -1
  66. package/dist/ifc-schema.js +15 -5
  67. package/dist/ifc-schema.js.map +1 -1
  68. package/dist/index.d.ts +5 -2
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +5 -2
  71. package/dist/index.js.map +1 -1
  72. package/dist/map-unit-label.d.ts +43 -0
  73. package/dist/map-unit-label.d.ts.map +1 -0
  74. package/dist/map-unit-label.js +140 -0
  75. package/dist/map-unit-label.js.map +1 -0
  76. package/dist/material-extractor.d.ts +2 -17
  77. package/dist/material-extractor.d.ts.map +1 -1
  78. package/dist/material-extractor.js +12 -37
  79. package/dist/material-extractor.js.map +1 -1
  80. package/dist/material-layer-reader.d.ts +45 -0
  81. package/dist/material-layer-reader.d.ts.map +1 -0
  82. package/dist/material-layer-reader.js +63 -0
  83. package/dist/material-layer-reader.js.map +1 -0
  84. package/dist/material-resolver.d.ts.map +1 -1
  85. package/dist/material-resolver.js +14 -14
  86. package/dist/material-resolver.js.map +1 -1
  87. package/dist/measure-unit-scale.d.ts +41 -0
  88. package/dist/measure-unit-scale.d.ts.map +1 -0
  89. package/dist/measure-unit-scale.js +53 -0
  90. package/dist/measure-unit-scale.js.map +1 -0
  91. package/dist/on-demand-cache.d.ts +3 -0
  92. package/dist/on-demand-cache.d.ts.map +1 -0
  93. package/dist/on-demand-cache.js +25 -0
  94. package/dist/on-demand-cache.js.map +1 -0
  95. package/dist/on-demand-extractors.d.ts +5 -21
  96. package/dist/on-demand-extractors.d.ts.map +1 -1
  97. package/dist/on-demand-extractors.js +64 -437
  98. package/dist/on-demand-extractors.js.map +1 -1
  99. package/dist/on-demand-georeferencing.d.ts +19 -0
  100. package/dist/on-demand-georeferencing.d.ts.map +1 -0
  101. package/dist/on-demand-georeferencing.js +139 -0
  102. package/dist/on-demand-georeferencing.js.map +1 -0
  103. package/dist/owning-project.d.ts +54 -0
  104. package/dist/owning-project.d.ts.map +1 -0
  105. package/dist/owning-project.js +76 -0
  106. package/dist/owning-project.js.map +1 -0
  107. package/dist/parser.worker.d.ts +14 -0
  108. package/dist/parser.worker.d.ts.map +1 -1
  109. package/dist/parser.worker.js +2 -0
  110. package/dist/parser.worker.js.map +1 -1
  111. package/dist/project-units-symbols.d.ts +33 -0
  112. package/dist/project-units-symbols.d.ts.map +1 -0
  113. package/dist/project-units-symbols.js +265 -0
  114. package/dist/project-units-symbols.js.map +1 -0
  115. package/dist/project-units.d.ts +11 -19
  116. package/dist/project-units.d.ts.map +1 -1
  117. package/dist/project-units.js +9 -267
  118. package/dist/project-units.js.map +1 -1
  119. package/dist/property-set-merge.d.ts +65 -8
  120. package/dist/property-set-merge.d.ts.map +1 -1
  121. package/dist/property-set-merge.js +130 -29
  122. package/dist/property-set-merge.js.map +1 -1
  123. package/dist/property-value-parser.d.ts +55 -0
  124. package/dist/property-value-parser.d.ts.map +1 -0
  125. package/dist/property-value-parser.js +277 -0
  126. package/dist/property-value-parser.js.map +1 -0
  127. package/dist/quantity-collect.d.ts +135 -0
  128. package/dist/quantity-collect.d.ts.map +1 -0
  129. package/dist/quantity-collect.js +253 -0
  130. package/dist/quantity-collect.js.map +1 -0
  131. package/dist/query-backend-maps.d.ts +5 -8
  132. package/dist/query-backend-maps.d.ts.map +1 -1
  133. package/dist/query-backend-maps.js +159 -15
  134. package/dist/query-backend-maps.js.map +1 -1
  135. package/dist/relationship-extractor.d.ts.map +1 -1
  136. package/dist/relationship-extractor.js +18 -0
  137. package/dist/relationship-extractor.js.map +1 -1
  138. package/dist/scan-entities-balanced.d.ts +38 -0
  139. package/dist/scan-entities-balanced.d.ts.map +1 -0
  140. package/dist/scan-entities-balanced.js +258 -0
  141. package/dist/scan-entities-balanced.js.map +1 -0
  142. package/dist/scan-worker-inline.d.ts +17 -11
  143. package/dist/scan-worker-inline.d.ts.map +1 -1
  144. package/dist/scan-worker-inline.js +9 -235
  145. package/dist/scan-worker-inline.js.map +1 -1
  146. package/dist/scan-worker-source.d.ts +9 -0
  147. package/dist/scan-worker-source.d.ts.map +1 -0
  148. package/dist/scan-worker-source.js +383 -0
  149. package/dist/scan-worker-source.js.map +1 -0
  150. package/dist/source-header.d.ts +23 -1
  151. package/dist/source-header.d.ts.map +1 -1
  152. package/dist/source-header.js +133 -43
  153. package/dist/source-header.js.map +1 -1
  154. package/dist/spatial-hierarchy-builder.js +3 -3
  155. package/dist/spatial-hierarchy-builder.js.map +1 -1
  156. package/dist/step-lexing.d.ts +94 -0
  157. package/dist/step-lexing.d.ts.map +1 -1
  158. package/dist/step-lexing.js +292 -25
  159. package/dist/step-lexing.js.map +1 -1
  160. package/dist/step-trivia.d.ts +60 -0
  161. package/dist/step-trivia.d.ts.map +1 -0
  162. package/dist/step-trivia.js +63 -0
  163. package/dist/step-trivia.js.map +1 -0
  164. package/dist/tokenizer.d.ts +20 -21
  165. package/dist/tokenizer.d.ts.map +1 -1
  166. package/dist/tokenizer.js +164 -203
  167. package/dist/tokenizer.js.map +1 -1
  168. package/dist/unit-extractor.d.ts +56 -1
  169. package/dist/unit-extractor.d.ts.map +1 -1
  170. package/dist/unit-extractor.js +80 -6
  171. package/dist/unit-extractor.js.map +1 -1
  172. package/dist/worker-parser.d.ts +11 -1
  173. package/dist/worker-parser.d.ts.map +1 -1
  174. package/dist/worker-parser.js +16 -2
  175. package/dist/worker-parser.js.map +1 -1
  176. package/package.json +6 -6
@@ -0,0 +1,135 @@
1
+ import type { EntityRef } from './types.js';
2
+ import type { EntityExtractor } from './entity-extractor.js';
3
+ import { type ProjectUnits } from './project-units.js';
4
+ /** One extracted quantity, in the shape both call sites report. */
5
+ export interface CollectedQuantity {
6
+ name: string;
7
+ type: number;
8
+ value: number;
9
+ /** SI factor of this quantity's explicit `Unit`, when it declares one.
10
+ * An omitted unit inherits the project's unit assignment. */
11
+ explicitUnitSiScale?: number;
12
+ }
13
+ /**
14
+ * The part of `IfcDataStore` this walk needs, declared structurally so the
15
+ * module need not import `IfcDataStore` from `columnar-parser.ts`, which
16
+ * imports this file back.
17
+ */
18
+ export interface QuantityLookupStore {
19
+ entityIndex: {
20
+ byId: {
21
+ get(id: number): EntityRef | undefined;
22
+ };
23
+ };
24
+ deferredEntityIndex?: {
25
+ get(id: number): EntityRef | undefined;
26
+ };
27
+ }
28
+ /**
29
+ * Read an `IfcElementQuantity.Quantities` list into flat quantity records.
30
+ *
31
+ * An `IfcPhysicalComplexQuantity` is skipped: it has no measure to report, and
32
+ * a `{name, type, value}` triple has nowhere to put its `HasQuantities`
33
+ * children. Before #3254 it fell through the simple-quantity path and surfaced
34
+ * as a phantom `Count = 0` — a row that satisfied IDS existence requirements,
35
+ * counted as "has quantities" in `validate`, entered the compare fingerprints
36
+ * and rendered as a bogus quantity card. Skipping matches what the legacy
37
+ * `quantity-extractor.ts` already does for a type it does not recognise, so all
38
+ * three quantity readers now agree.
39
+ *
40
+ * **Its nested quantities are dropped with it, and that is a known gap with no
41
+ * tracking issue behind it.** Not "tracked separately" — an earlier version of
42
+ * this comment said so and nothing tracked it. The children are lost in every
43
+ * case — #3254's fixture nests two `IfcQuantityArea` totalling 26 m² that read
44
+ * back as nothing — and a set whose ONLY member is a complex quantity collects
45
+ * nothing, so since #3261 {@link readQuantitySet} drops the whole
46
+ * `IfcElementQuantity` rather than reporting an empty one.
47
+ *
48
+ * The gap is deliberate rather than overlooked. Flattening the children into
49
+ * this list would feed new names to a dozen name-keyed consumers, and — via the
50
+ * mutable property view that re-writes a touched `IfcElementQuantity` from these
51
+ * records, and `step-property-sets.ts` which emits them as flat siblings — would
52
+ * permanently destroy the complex structure on the next export. Surfacing them
53
+ * safely needs a representation these records do not have: one that read-side
54
+ * consumers can see and the write-back path provably skips. Until that exists,
55
+ * under-reporting is the lesser harm, and this paragraph is the whole of what
56
+ * anyone is doing about it.
57
+ *
58
+ * An entity of a type absent from {@link QUANTITY_TYPE_MAP} still reports as a
59
+ * `Count`, keeping its value under a wrong label rather than vanishing. No
60
+ * `IfcPhysicalSimpleQuantity` subtype relies on that fallback today —
61
+ * `IfcQuantityNumber` (IFC4X3) did until #3266 gave it `QuantityType.Number`,
62
+ * and `test/quantity-type-map-coverage.test.ts` now reds if a schema declares a
63
+ * subtype the map has not gained. That test guards the OTHER hand-written set
64
+ * too: `PROPERTY_ENTITY_TYPES` in `columnar-parser-indexes.ts` decides whether
65
+ * the entity is retained at all, so a subtype missing THERE never reaches this
66
+ * map and the quantity does not exist rather than being mislabelled.
67
+ */
68
+ export declare function collectQuantitiesFromRefs(store: QuantityLookupStore, extractor: EntityExtractor, refs: unknown): CollectedQuantity[];
69
+ /** One extracted quantity set, in the shape both call sites report. */
70
+ export interface CollectedQuantitySet {
71
+ name: string;
72
+ /** `GlobalId` of the source `IfcElementQuantity` instance, when read as a string. */
73
+ globalId?: string;
74
+ quantities: CollectedQuantity[];
75
+ }
76
+ /**
77
+ * Read one `IfcElementQuantity` into a reportable quantity set, or `null` when
78
+ * it carries nothing worth reporting.
79
+ *
80
+ * `IFC4_ADD2_TC1.exp` (identically `IFC4X3.exp`):
81
+ *
82
+ * ENTITY IfcElementQuantity
83
+ * SUBTYPE OF (IfcQuantitySet);
84
+ * MethodOfMeasurement : OPTIONAL IfcLabel;
85
+ * Quantities : SET [1:?] OF IfcPhysicalQuantity;
86
+ *
87
+ * `SET [1:?]` admits no empty set, so a set that walks to zero quantities —
88
+ * written empty, or filled only with members this reader cannot report, such as
89
+ * an unresolvable reference, an `IfcPhysicalComplexQuantity` (#3254), or a
90
+ * measure outside the IEEE-754 double range — is non-conformant data. Reporting it anyway would assert "this element has
91
+ * quantities" on the strength of a name alone, and the consumers act on exactly
92
+ * that: `validate` counts the element as quantified in its quantity-completeness
93
+ * figure, an IDS quantity-set existence check passes, and the viewer's fallback
94
+ * to the element's TYPE quantities is suppressed by the phantom occurrence set,
95
+ * hiding the real numbers the type carries. So it is dropped (#3259).
96
+ *
97
+ * That applies unchanged when every member was dropped for being
98
+ * unrepresentable: the set then vanishes rather than surviving empty. Keeping
99
+ * an empty shell would make exactly the claim #3259 removed — "this element is
100
+ * quantified" — while carrying no number to back it, and it would still
101
+ * suppress the type-quantity fallback. The reason each quantity went is on the
102
+ * console; the reason the set went is that nothing in it survived.
103
+ *
104
+ * The instance path and the type path both go through here, so the drop cannot
105
+ * come apart between them again: it used to be inlined at each site, and the
106
+ * type site dropped while the instance site kept.
107
+ */
108
+ export declare function readQuantitySet(store: QuantityLookupStore, extractor: EntityExtractor, qsetRef: EntityRef): CollectedQuantitySet | null;
109
+ /**
110
+ * SI scale factor for a `Qto_` value, preferring its explicit `Unit` and then
111
+ * resolving against the project's declared units.
112
+ *
113
+ * An `IfcQuantityLength`/`Area`/`Volume` is stored in the project's raw
114
+ * author unit exactly like an `IfcPropertySingleValue` of the matching
115
+ * measure type — the value is not pre-converted to SI by the exporter. A
116
+ * consumer that hashes or otherwise compares `CollectedQuantity.value`
117
+ * across two files (or against a base-SI literal) as-is therefore reads a
118
+ * project's choice of length unit as a change in the design itself: the
119
+ * same 2 m wall authored in millimetres carries the raw value `2000`
120
+ * instead of `2`.
121
+ *
122
+ * `1` for `Count`/`Weight`/`Time`/`Number` — this only scales the three
123
+ * quantity types that are themselves `IfcLengthMeasure`-family measures.
124
+ *
125
+ * Area and Volume scale by the SQUARE and CUBE of the length factor (a
126
+ * millimetre-authored 1 m² is stored as `1e6`, not `1e3`) — but only as a
127
+ * FALLBACK: `IFC` lets a project declare an explicit `AREAUNIT`/`VOLUMEUNIT`
128
+ * with no arithmetic relationship to `LENGTHUNIT`, so the file's own
129
+ * declaration is preferred and the length-derived power is used only when
130
+ * the project declares none. An explicit member `Unit` always wins; IFC uses
131
+ * it specifically to let a quantity depart from its containing project's
132
+ * assignment.
133
+ */
134
+ export declare function quantitySiScale(quantity: CollectedQuantity, units: ProjectUnits): number;
135
+ //# sourceMappingURL=quantity-collect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quantity-collect.d.ts","sourceRoot":"","sources":["../src/quantity-collect.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzE,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd;kEAC8D;IAC9D,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,WAAW,EAAE;QAAE,IAAI,EAAE;YAAE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;SAAE,CAAA;KAAE,CAAC;IAClE,mBAAmB,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;KAAE,CAAC;CACpE;AA2BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,yBAAyB,CACrC,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,IAAI,EAAE,OAAO,GACd,iBAAiB,EAAE,CA2ErB;AAUD,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,iBAAiB,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,SAAS,GACnB,oBAAoB,GAAG,IAAI,CAe7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAqBxF"}
@@ -0,0 +1,253 @@
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
+ * Shared reader for an `IfcElementQuantity.Quantities` list (#3254).
6
+ *
7
+ * The instance path (`ColumnarParser.extractQuantitiesOnDemand`) and the type
8
+ * path (`extractQsetsFromIds`) both walk that list, and each used to inline its
9
+ * own copy of the walk — two copies that would disagree the moment either was
10
+ * touched. The walk lives here and both call it.
11
+ */
12
+ import { QuantityType } from '@ifc-lite/data';
13
+ import { QUANTITY_TYPE_MAP } from './columnar-parser-indexes.js';
14
+ import { isUnrepresentableNumericValue } from './attribute-helpers.js';
15
+ import { resolveUnitByRef } from './project-units.js';
16
+ /**
17
+ * `IfcPhysicalComplexQuantity` groups other quantities instead of carrying a
18
+ * value of its own (`packages/codegen/schemas/IFC4_ADD2_TC1.exp`, identically
19
+ * in `IFC4X3.exp`):
20
+ *
21
+ * ENTITY IfcPhysicalComplexQuantity
22
+ * SUBTYPE OF (IfcPhysicalQuantity);
23
+ * HasQuantities : SET [1:?] OF IfcPhysicalQuantity;
24
+ * Discrimination : IfcLabel;
25
+ * Quality : OPTIONAL IfcLabel;
26
+ * Usage : OPTIONAL IfcLabel;
27
+ *
28
+ * With `Name` and `Description` inherited from `IfcPhysicalQuantity`, the
29
+ * flattened slots are HasQuantities[2], Discrimination[3], Quality[4],
30
+ * Usage[5]. Slot 3 — where every `IfcPhysicalSimpleQuantity` subtype keeps its
31
+ * measure — therefore holds a label here.
32
+ */
33
+ const COMPLEX_QUANTITY_TYPE = 'IFCPHYSICALCOMPLEXQUANTITY';
34
+ /**
35
+ * Value slot on every `IfcPhysicalSimpleQuantity` subtype: Name[0],
36
+ * Description[1], Unit[2], *Value[3].
37
+ */
38
+ const SIMPLE_QUANTITY_VALUE_SLOT = 3;
39
+ /**
40
+ * Read an `IfcElementQuantity.Quantities` list into flat quantity records.
41
+ *
42
+ * An `IfcPhysicalComplexQuantity` is skipped: it has no measure to report, and
43
+ * a `{name, type, value}` triple has nowhere to put its `HasQuantities`
44
+ * children. Before #3254 it fell through the simple-quantity path and surfaced
45
+ * as a phantom `Count = 0` — a row that satisfied IDS existence requirements,
46
+ * counted as "has quantities" in `validate`, entered the compare fingerprints
47
+ * and rendered as a bogus quantity card. Skipping matches what the legacy
48
+ * `quantity-extractor.ts` already does for a type it does not recognise, so all
49
+ * three quantity readers now agree.
50
+ *
51
+ * **Its nested quantities are dropped with it, and that is a known gap with no
52
+ * tracking issue behind it.** Not "tracked separately" — an earlier version of
53
+ * this comment said so and nothing tracked it. The children are lost in every
54
+ * case — #3254's fixture nests two `IfcQuantityArea` totalling 26 m² that read
55
+ * back as nothing — and a set whose ONLY member is a complex quantity collects
56
+ * nothing, so since #3261 {@link readQuantitySet} drops the whole
57
+ * `IfcElementQuantity` rather than reporting an empty one.
58
+ *
59
+ * The gap is deliberate rather than overlooked. Flattening the children into
60
+ * this list would feed new names to a dozen name-keyed consumers, and — via the
61
+ * mutable property view that re-writes a touched `IfcElementQuantity` from these
62
+ * records, and `step-property-sets.ts` which emits them as flat siblings — would
63
+ * permanently destroy the complex structure on the next export. Surfacing them
64
+ * safely needs a representation these records do not have: one that read-side
65
+ * consumers can see and the write-back path provably skips. Until that exists,
66
+ * under-reporting is the lesser harm, and this paragraph is the whole of what
67
+ * anyone is doing about it.
68
+ *
69
+ * An entity of a type absent from {@link QUANTITY_TYPE_MAP} still reports as a
70
+ * `Count`, keeping its value under a wrong label rather than vanishing. No
71
+ * `IfcPhysicalSimpleQuantity` subtype relies on that fallback today —
72
+ * `IfcQuantityNumber` (IFC4X3) did until #3266 gave it `QuantityType.Number`,
73
+ * and `test/quantity-type-map-coverage.test.ts` now reds if a schema declares a
74
+ * subtype the map has not gained. That test guards the OTHER hand-written set
75
+ * too: `PROPERTY_ENTITY_TYPES` in `columnar-parser-indexes.ts` decides whether
76
+ * the entity is retained at all, so a subtype missing THERE never reaches this
77
+ * map and the quantity does not exist rather than being mislabelled.
78
+ */
79
+ export function collectQuantitiesFromRefs(store, extractor, refs) {
80
+ const quantities = [];
81
+ if (!Array.isArray(refs))
82
+ return quantities;
83
+ for (const qtyRef of refs) {
84
+ if (typeof qtyRef !== 'number')
85
+ continue;
86
+ const qtyEntityRef = store.entityIndex.byId.get(qtyRef) ?? store.deferredEntityIndex?.get(qtyRef);
87
+ if (!qtyEntityRef)
88
+ continue;
89
+ const qtyEntity = extractor.extractEntity(qtyEntityRef);
90
+ if (!qtyEntity)
91
+ continue;
92
+ const qtyTypeUpper = qtyEntity.type.toUpperCase();
93
+ if (qtyTypeUpper === COMPLEX_QUANTITY_TYPE)
94
+ continue;
95
+ const qtyAttrs = qtyEntity.attributes || [];
96
+ const qtyName = typeof qtyAttrs[0] === 'string' ? qtyAttrs[0] : '';
97
+ if (!qtyName)
98
+ continue;
99
+ const qtyType = QUANTITY_TYPE_MAP[qtyTypeUpper] ?? QuantityType.Count;
100
+ // `IfcPhysicalSimpleQuantity.Unit` is optional, but it overrides the
101
+ // project assignment when present. Preserve its scale on the record so
102
+ // every downstream reader of this shared collection uses the same
103
+ // physical value rather than silently treating (say) 2000 mm as 2000 m.
104
+ const unitRef = qtyAttrs[2];
105
+ const unit = typeof unitRef === 'number'
106
+ ? resolveUnitByRef(extractor, store.entityIndex, unitRef)
107
+ : null;
108
+ const rawValue = qtyAttrs[SIMPLE_QUANTITY_VALUE_SLOT];
109
+ // A measure the double range cannot hold is dropped with a diagnostic,
110
+ // not reported as `0`. `CollectedQuantity.value` is `number` and is
111
+ // consumed by callers that do arithmetic on it, so there is no
112
+ // in-band way to say "unrepresentable" — and `0` is the worst of the
113
+ // available lies, because a 0 m^3 volume reads as a measurement
114
+ // somebody took. An absent quantity is detectable; a zero one is not.
115
+ //
116
+ // This matches what the sibling path already does:
117
+ // `QuantityExtractor.extractQuantity` returns `null` and warns when
118
+ // slot 3 is not a number. That path and this one walk the same
119
+ // `Quantities` list, so they must agree.
120
+ //
121
+ // The diagnostic is per occurrence, deliberately. Each line names a
122
+ // different entity id and quantity name, so it is the list of what was
123
+ // dropped rather than one message repeated — collapsing it to
124
+ // once-per-file would leave a reader knowing that something was
125
+ // discarded and not which. There is also no per-file context threaded
126
+ // through this function to hang a once-per-file flag on: the only
127
+ // place to keep one is module scope, which outlives a file in the
128
+ // viewer's long-lived worker and would then silence the *next* file's
129
+ // first warning. No `console.warn` in this package is throttled
130
+ // today, so a local cap here would be the one-off. The cost is bounded by how
131
+ // corrupt the file is, and a file with thousands of unrepresentable
132
+ // measures has a louder problem than its console output.
133
+ if (isUnrepresentableNumericValue(rawValue)) {
134
+ console.warn(`[quantity-collect] ${qtyEntity.type} #${qtyEntity.expressId} "${qtyName}" ` +
135
+ `has a value outside the IEEE-754 double range (${String(rawValue)}); ` +
136
+ `dropping the quantity rather than reporting it as 0.`);
137
+ continue;
138
+ }
139
+ const value = typeof rawValue === 'number' ? rawValue : 0;
140
+ quantities.push({
141
+ name: qtyName,
142
+ type: qtyType,
143
+ value,
144
+ ...(unit ? { explicitUnitSiScale: unit.resolved.siScale } : {}),
145
+ });
146
+ }
147
+ return quantities;
148
+ }
149
+ /**
150
+ * `Quantities` slot on `IfcElementQuantity`: GlobalId[0], OwnerHistory[1],
151
+ * Name[2], Description[3] inherited from `IfcRoot`, then MethodOfMeasurement[4]
152
+ * and Quantities[5]. GlobalId[0] is the identity of the `IfcElementQuantity`
153
+ * instance itself, not any of its quantities.
154
+ */
155
+ const QUANTITIES_SLOT = 5;
156
+ /**
157
+ * Read one `IfcElementQuantity` into a reportable quantity set, or `null` when
158
+ * it carries nothing worth reporting.
159
+ *
160
+ * `IFC4_ADD2_TC1.exp` (identically `IFC4X3.exp`):
161
+ *
162
+ * ENTITY IfcElementQuantity
163
+ * SUBTYPE OF (IfcQuantitySet);
164
+ * MethodOfMeasurement : OPTIONAL IfcLabel;
165
+ * Quantities : SET [1:?] OF IfcPhysicalQuantity;
166
+ *
167
+ * `SET [1:?]` admits no empty set, so a set that walks to zero quantities —
168
+ * written empty, or filled only with members this reader cannot report, such as
169
+ * an unresolvable reference, an `IfcPhysicalComplexQuantity` (#3254), or a
170
+ * measure outside the IEEE-754 double range — is non-conformant data. Reporting it anyway would assert "this element has
171
+ * quantities" on the strength of a name alone, and the consumers act on exactly
172
+ * that: `validate` counts the element as quantified in its quantity-completeness
173
+ * figure, an IDS quantity-set existence check passes, and the viewer's fallback
174
+ * to the element's TYPE quantities is suppressed by the phantom occurrence set,
175
+ * hiding the real numbers the type carries. So it is dropped (#3259).
176
+ *
177
+ * That applies unchanged when every member was dropped for being
178
+ * unrepresentable: the set then vanishes rather than surviving empty. Keeping
179
+ * an empty shell would make exactly the claim #3259 removed — "this element is
180
+ * quantified" — while carrying no number to back it, and it would still
181
+ * suppress the type-quantity fallback. The reason each quantity went is on the
182
+ * console; the reason the set went is that nothing in it survived.
183
+ *
184
+ * The instance path and the type path both go through here, so the drop cannot
185
+ * come apart between them again: it used to be inlined at each site, and the
186
+ * type site dropped while the instance site kept.
187
+ */
188
+ export function readQuantitySet(store, extractor, qsetRef) {
189
+ const qsetEntity = extractor.extractEntity(qsetRef);
190
+ if (!qsetEntity)
191
+ return null;
192
+ const qsetAttrs = qsetEntity.attributes || [];
193
+ // Left empty rather than a fabricated `QuantitySet #<id>` when the source
194
+ // declared no Name: this is `store.getQuantities()`'s answer, consumed
195
+ // verbatim downstream (MCP tool responses, `bim.quantities()`) as though
196
+ // the model had genuinely declared that name (#3530 census).
197
+ const qsetName = typeof qsetAttrs[2] === 'string' ? qsetAttrs[2] : '';
198
+ const qsetGlobalId = typeof qsetAttrs[0] === 'string' ? qsetAttrs[0] : undefined;
199
+ const quantities = collectQuantitiesFromRefs(store, extractor, qsetAttrs[QUANTITIES_SLOT]);
200
+ if (quantities.length === 0)
201
+ return null;
202
+ return { name: qsetName, globalId: qsetGlobalId, quantities };
203
+ }
204
+ /**
205
+ * SI scale factor for a `Qto_` value, preferring its explicit `Unit` and then
206
+ * resolving against the project's declared units.
207
+ *
208
+ * An `IfcQuantityLength`/`Area`/`Volume` is stored in the project's raw
209
+ * author unit exactly like an `IfcPropertySingleValue` of the matching
210
+ * measure type — the value is not pre-converted to SI by the exporter. A
211
+ * consumer that hashes or otherwise compares `CollectedQuantity.value`
212
+ * across two files (or against a base-SI literal) as-is therefore reads a
213
+ * project's choice of length unit as a change in the design itself: the
214
+ * same 2 m wall authored in millimetres carries the raw value `2000`
215
+ * instead of `2`.
216
+ *
217
+ * `1` for `Count`/`Weight`/`Time`/`Number` — this only scales the three
218
+ * quantity types that are themselves `IfcLengthMeasure`-family measures.
219
+ *
220
+ * Area and Volume scale by the SQUARE and CUBE of the length factor (a
221
+ * millimetre-authored 1 m² is stored as `1e6`, not `1e3`) — but only as a
222
+ * FALLBACK: `IFC` lets a project declare an explicit `AREAUNIT`/`VOLUMEUNIT`
223
+ * with no arithmetic relationship to `LENGTHUNIT`, so the file's own
224
+ * declaration is preferred and the length-derived power is used only when
225
+ * the project declares none. An explicit member `Unit` always wins; IFC uses
226
+ * it specifically to let a quantity depart from its containing project's
227
+ * assignment.
228
+ */
229
+ export function quantitySiScale(quantity, units) {
230
+ if (quantity.explicitUnitSiScale !== undefined)
231
+ return quantity.explicitUnitSiScale;
232
+ switch (quantity.type) {
233
+ case QuantityType.Length:
234
+ return units.unitForMeasure('IfcLengthMeasure')?.siScale ?? 1;
235
+ case QuantityType.Area: {
236
+ const explicit = units.resolvedForUnitType('AREAUNIT')?.siScale;
237
+ if (explicit !== undefined)
238
+ return explicit;
239
+ const length = units.unitForMeasure('IfcLengthMeasure')?.siScale ?? 1;
240
+ return length ** 2;
241
+ }
242
+ case QuantityType.Volume: {
243
+ const explicit = units.resolvedForUnitType('VOLUMEUNIT')?.siScale;
244
+ if (explicit !== undefined)
245
+ return explicit;
246
+ const length = units.unitForMeasure('IfcLengthMeasure')?.siScale ?? 1;
247
+ return length ** 3;
248
+ }
249
+ default:
250
+ return 1;
251
+ }
252
+ }
253
+ //# sourceMappingURL=quantity-collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quantity-collect.js","sourceRoot":"","sources":["../src/quantity-collect.ts"],"names":[],"mappings":"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"}
@@ -14,19 +14,16 @@
14
14
  import { RelationshipType } from '@ifc-lite/data';
15
15
  /**
16
16
  * IFC4 subtype map — parent types to their StandardCase/ElementedCase
17
- * subtypes. In IFC4 many element types have `*StandardCase` subtypes that the
18
- * parser stores under the full type name, so `byType('IfcWall')` has to look
19
- * for `IfcWallStandardCase` as well to answer what the caller meant.
17
+ * subtypes, kept for backward compatibility (`@ifc-lite/cli`'s
18
+ * `validate-subtypes.test.ts` reads this table directly as ground truth) and
19
+ * as a fixed, hand-legible cross-check of the schema-driven expansion below.
20
+ * `expandTypes` no longer walks this table itself — see its doc comment.
20
21
  *
21
22
  * Keys and values are UPPERCASE because `entityIndex.byType` is keyed by the
22
23
  * raw STEP type name (e.g. `IFCWALLSTANDARDCASE`).
23
24
  */
24
25
  export declare const IFC_SUBTYPES: Record<string, string[]>;
25
- /**
26
- * Expand a caller's type list to include the known IFC subtypes, uppercasing
27
- * PascalCase input (`'IfcWall'`) for the `entityIndex` lookup.
28
- */
29
- export declare function expandTypes(types: string[]): string[];
26
+ export declare function expandTypes(types: string[], schemaVersion?: string): string[];
30
27
  /**
31
28
  * Relationship names the SDK's `related(ref, relType, direction)` accepts,
32
29
  * keyed in the PascalCase spelling a caller writes.
@@ -1 +1 @@
1
- {"version":3,"file":"query-backend-maps.d.ts","sourceRoot":"","sources":["../src/query-backend-maps.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAUjD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAWrD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAM/D,CAAC"}
1
+ {"version":3,"file":"query-backend-maps.d.ts","sourceRoot":"","sources":["../src/query-backend-maps.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,gBAAgB,EAAgC,MAAM,gBAAgB,CAAC;AAGhF;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAcjD,CAAC;AAkHF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ7E;AAiDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAM/D,CAAC"}
@@ -14,12 +14,14 @@
14
14
  * many, so the tables live here, next to the `entityIndex` whose key shape
15
15
  * they are written against.
16
16
  */
17
- import { RelationshipType } from '@ifc-lite/data';
17
+ import { RelationshipType, expandTypeNamesToDescendants } from '@ifc-lite/data';
18
+ import { getInheritanceChain, isQueryableObjectType } from './ifc-schema.js';
18
19
  /**
19
20
  * IFC4 subtype map — parent types to their StandardCase/ElementedCase
20
- * subtypes. In IFC4 many element types have `*StandardCase` subtypes that the
21
- * parser stores under the full type name, so `byType('IfcWall')` has to look
22
- * for `IfcWallStandardCase` as well to answer what the caller meant.
21
+ * subtypes, kept for backward compatibility (`@ifc-lite/cli`'s
22
+ * `validate-subtypes.test.ts` reads this table directly as ground truth) and
23
+ * as a fixed, hand-legible cross-check of the schema-driven expansion below.
24
+ * `expandTypes` no longer walks this table itself — see its doc comment.
23
25
  *
24
26
  * Keys and values are UPPERCASE because `entityIndex.byType` is keyed by the
25
27
  * raw STEP type name (e.g. `IFCWALLSTANDARDCASE`).
@@ -34,23 +36,165 @@ export const IFC_SUBTYPES = {
34
36
  IFCMEMBER: ['IFCMEMBERSTANDARDCASE'],
35
37
  IFCPLATE: ['IFCPLATESTANDARDCASE'],
36
38
  IFCOPENINGELEMENT: ['IFCOPENINGSTANDARDCASE'],
39
+ // Not a `*StandardCase` family, and absent until #3229: IFC4 exporters write
40
+ // furniture as IFCFURNITURE, so `byType('IfcFurnishingElement')` answered
41
+ // with nothing on a model that plainly contained furniture.
42
+ IFCFURNISHINGELEMENT: ['IFCFURNITURE', 'IFCSYSTEMFURNITUREELEMENT'],
37
43
  };
44
+ // Cached per name: `rootBranchOf` and `isQueryableObjectType` both walk the
45
+ // inheritance chain, and the gate calls one of them once per descendant. On
46
+ // `byType('IfcRoot')` that is 294 uncached walks for one query.
47
+ const rootBranchCache = new Map();
38
48
  /**
39
- * Expand a caller's type list to include the known IFC subtypes, uppercasing
40
- * PascalCase input (`'IfcWall'`) for the `entityIndex` lookup.
49
+ * Bound on both memos below. Their keys are caller-supplied type names and
50
+ * type lists, and `byType()` is reached from the MCP server and the viewer,
51
+ * long-lived processes where those come from an agent or an SDK script — so
52
+ * an unbounded map grows on typos and vendor namespaces for the life of the
53
+ * process. Clearing wholesale costs one recompute and needs no bookkeeping;
54
+ * the bundled schemas declare ~1160 names, so a real workload never reaches
55
+ * it.
41
56
  */
42
- export function expandTypes(types) {
43
- const result = [];
57
+ const CACHE_LIMIT = 4096;
58
+ function rootBranchOf(type) {
59
+ const cached = rootBranchCache.get(type);
60
+ if (cached !== undefined)
61
+ return cached;
62
+ const branch = computeRootBranch(type);
63
+ if (rootBranchCache.size >= CACHE_LIMIT)
64
+ rootBranchCache.clear();
65
+ rootBranchCache.set(type, branch);
66
+ return branch;
67
+ }
68
+ function computeRootBranch(type) {
69
+ const chain = getInheritanceChain(type);
70
+ if (chain.includes('IfcRelationship'))
71
+ return 'relationship';
72
+ if (chain.includes('IfcPropertyDefinition'))
73
+ return 'propertyDefinition';
74
+ if (chain.includes('IfcTypeObject'))
75
+ return 'typeObject';
76
+ if (chain.includes('IfcObjectDefinition'))
77
+ return 'object';
78
+ return 'other';
79
+ }
80
+ /**
81
+ * Expand a caller's type list to every schema-declared descendant (itself
82
+ * plus every type that has it as an ancestor, direct or indirect), so
83
+ * `byType('IfcBuildingElement')` finds the concrete leaves a model actually
84
+ * contains instead of matching nothing — `IfcBuildingElement`/`IfcElement`
85
+ * are abstract EXPRESS supertypes, never a literal STEP entity type, so a
86
+ * caller asking for one always meant its subtypes.
87
+ *
88
+ * Delegates to `@ifc-lite/data`'s `expandTypeNamesToDescendants`, the single
89
+ * schema-authority resolver every `byType()` backend now shares — this used
90
+ * to be a fixed nine-entry `IFC_SUBTYPES` table (still above, unused here)
91
+ * that only aliased `*StandardCase`/`*ElementedCase` pairs and silently
92
+ * dropped every abstract-supertype query.
93
+ *
94
+ * PASS `schemaVersion`. It is the queried model's own `store.schemaVersion`,
95
+ * and it is what makes the answer exact. Descendant sets are not the same
96
+ * across versions -- buildingSMART re-parented entities, so
97
+ * `IfcReinforcingBar` is an `IfcBuildingElement` in IFC2X3 and an
98
+ * `IfcElementComponent` in IFC4 -- while the names a FILE contains need not
99
+ * belong to the version its header claims. The resolver reconciles the two;
100
+ * see its module doc for the exact rule.
101
+ *
102
+ * It is optional only because this function is a published export of
103
+ * `@ifc-lite/parser` and of `@ifc-lite/mcp/browser`, and requiring it would
104
+ * stop an existing `expandTypes(['IfcWall'])` compiling -- a major, which a
105
+ * bug-fix release is not. Omitted, the expansion falls back to the UNION
106
+ * across the three bundled schemas: a superset that finds every spelling but
107
+ * cannot tell a re-parented entity from a real subtype, so
108
+ * `expandTypes(['IfcBuildingElement'])` answers with reinforcing bars that
109
+ * `expandTypes(['IfcBuildingElement'], 'IFC4')` correctly leaves out. Every
110
+ * caller in this repository passes the version; a viewer test goes red if one
111
+ * stops.
112
+ *
113
+ * The expansion does not cross an `IfcRoot` branch. Descending the whole
114
+ * hierarchy from an abstract root turned `byType('IfcRoot')` into "every
115
+ * rooted record in the file" — 223 rows on `infra-bridge.ifc`, 36 of them
116
+ * `IfcRelDefinesByProperties` — which a caller then hands to `storey()` or
117
+ * `group_by`, written against products. `IfcObjectDefinition` swept in every
118
+ * `*Type` the same way, contradicting the untyped branch of the very same
119
+ * backends, which has always answered with {@link isQueryableObjectType}
120
+ * only.
121
+ *
122
+ * The gate reads the requested type's branch rather than "is a product",
123
+ * because `byType('IfcBuildingElementType')` means its subtypes exactly as
124
+ * much as `byType('IfcBuildingElement')` does. The requested names themselves
125
+ * are never gated: a caller who spells out `IfcPropertySet` said what they
126
+ * wanted.
127
+ *
128
+ * Memoized on (schemaVersion, type list). Every `byType()` call goes through
129
+ * here, and `validate` now calls it twice per store; the closure plus the gate
130
+ * cost 0.78 ms for `IfcRoot` on IFC4, which the caches take to a Map lookup.
131
+ * The key keeps the caller's order rather than sorting it: the output order is
132
+ * contractual — each requested type ahead of its own descendants — so two
133
+ * different orders are two different answers and must not share an entry.
134
+ */
135
+ const expandCache = new Map();
136
+ /**
137
+ * Cache-key stand-in for an omitted version. Not a value `resolveSchemaVersion`
138
+ * can ever see, so the union answer cannot share an entry with `''` or any
139
+ * other unrecognized string, which resolve to IFC4 instead.
140
+ */
141
+ const NO_VERSION_KEY = '\u0001union';
142
+ export function expandTypes(types, schemaVersion) {
143
+ const key = `${schemaVersion ?? NO_VERSION_KEY}\u0000${types.join('\u0000')}`;
144
+ const cached = expandCache.get(key);
145
+ if (cached)
146
+ return [...cached];
147
+ const result = computeExpandTypes(types, schemaVersion);
148
+ if (expandCache.size >= CACHE_LIMIT)
149
+ expandCache.clear();
150
+ expandCache.set(key, result);
151
+ return [...result];
152
+ }
153
+ /** The three versions with a bundled table, for the no-version union. */
154
+ const UNION_VERSIONS = ['IFC2X3', 'IFC4', 'IFC4X3'];
155
+ /**
156
+ * One type's expansion when the caller named no schema: the requested type,
157
+ * then every descendant ANY bundled schema gives it, sorted.
158
+ *
159
+ * Deliberately a superset rather than a guess at a version. A caller who has
160
+ * not said which schema the file is written in is better served finding a
161
+ * record that is arguably of the wrong class than silently finding none at
162
+ * all, which is what picking one table for them would do to the other two.
163
+ * The sort keeps the order stable across schema-table regeneration, as the
164
+ * per-version path does.
165
+ */
166
+ function unionExpansion(type) {
167
+ const self = expandTypeNamesToDescendants([type], UNION_VERSIONS[0])[0];
168
+ const rest = new Set();
169
+ for (const version of UNION_VERSIONS) {
170
+ const expanded = expandTypeNamesToDescendants([type], version);
171
+ for (let i = 1; i < expanded.length; i++)
172
+ rest.add(expanded[i]);
173
+ }
174
+ rest.delete(self);
175
+ return [self, ...[...rest].sort()];
176
+ }
177
+ function computeExpandTypes(types, schemaVersion) {
178
+ const out = [];
179
+ const seen = new Set();
44
180
  for (const type of types) {
45
- const upper = type.toUpperCase();
46
- result.push(upper);
47
- const subtypes = IFC_SUBTYPES[upper];
48
- if (subtypes) {
49
- for (const sub of subtypes)
50
- result.push(sub);
181
+ const branch = rootBranchOf(type);
182
+ const expanded = schemaVersion === undefined
183
+ ? unionExpansion(type)
184
+ : expandTypeNamesToDescendants([type], schemaVersion);
185
+ for (let i = 0; i < expanded.length; i++) {
186
+ const name = expanded[i];
187
+ // Index 0 is the requested type itself, which is never gated: a caller
188
+ // who spells out `IfcPropertySet` said what they wanted.
189
+ if (i > 0 && !isQueryableObjectType(name) && rootBranchOf(name) !== branch)
190
+ continue;
191
+ if (seen.has(name))
192
+ continue;
193
+ seen.add(name);
194
+ out.push(name);
51
195
  }
52
196
  }
53
- return result;
197
+ return out;
54
198
  }
55
199
  /**
56
200
  * Relationship names the SDK's `related(ref, relType, direction)` accepts,
@@ -1 +1 @@
1
- {"version":3,"file":"query-backend-maps.js","sourceRoot":"","sources":["../src/query-backend-maps.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAA6B;IACpD,OAAO,EAAE,CAAC,qBAAqB,EAAE,sBAAsB,CAAC;IACxD,OAAO,EAAE,CAAC,qBAAqB,CAAC;IAChC,SAAS,EAAE,CAAC,uBAAuB,CAAC;IACpC,OAAO,EAAE,CAAC,qBAAqB,CAAC;IAChC,SAAS,EAAE,CAAC,uBAAuB,CAAC;IACpC,OAAO,EAAE,CAAC,qBAAqB,EAAE,sBAAsB,CAAC;IACxD,SAAS,EAAE,CAAC,uBAAuB,CAAC;IACpC,QAAQ,EAAE,CAAC,sBAAsB,CAAC;IAClC,iBAAiB,EAAE,CAAC,wBAAwB,CAAC;CAC9C,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,GAAG,IAAI,QAAQ;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAqC;IAClE,iCAAiC,EAAE,gBAAgB,CAAC,gBAAgB;IACpE,gBAAgB,EAAE,gBAAgB,CAAC,UAAU;IAC7C,mBAAmB,EAAE,gBAAgB,CAAC,aAAa;IACnD,kBAAkB,EAAE,gBAAgB,CAAC,YAAY;IACjD,kBAAkB,EAAE,gBAAgB,CAAC,YAAY;CAClD,CAAC"}
1
+ {"version":3,"file":"query-backend-maps.js","sourceRoot":"","sources":["../src/query-backend-maps.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"relationship-extractor.d.ts","sourceRoot":"","sources":["../src/relationship-extractor.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAyB;gBAE7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;IAI5C;;OAEG;IACG,yBAAyB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA6B1D;;OAEG;IACH,oBAAoB,IAAI,YAAY,EAAE;IAsBtC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwD3B,OAAO,CAAC,iBAAiB;CAM1B"}
1
+ {"version":3,"file":"relationship-extractor.d.ts","sourceRoot":"","sources":["../src/relationship-extractor.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAyB;gBAE7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;IAI5C;;OAEG;IACG,yBAAyB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA6B1D;;OAEG;IACH,oBAAoB,IAAI,YAAY,EAAE;IAsBtC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAyE3B,OAAO,CAAC,iBAAiB;CAM1B"}