@bldrs-ai/conway 1.426.1283 → 1.428.1289

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 (27) hide show
  1. package/compiled/examples/browser-bundled.cjs +1 -1
  2. package/compiled/examples/cli-bundled.cjs +1 -1
  3. package/compiled/examples/cli-step-bundled.cjs +175 -21
  4. package/compiled/examples/validator-bundled.cjs +1 -1
  5. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts +69 -0
  6. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts.map +1 -1
  7. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.js +187 -31
  8. package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts +6 -0
  9. package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts.map +1 -1
  10. package/compiled/src/AP214E3_2010/ap214_model_geometry.js +9 -0
  11. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts +2 -0
  12. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts.map +1 -0
  13. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.js +190 -0
  14. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.d.ts.map +1 -1
  15. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.js +19 -0
  16. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.js +2 -2
  17. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts +105 -0
  18. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts.map +1 -1
  19. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.js +360 -0
  20. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  21. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +2 -2
  22. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +111 -38
  23. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -1
  24. package/compiled/src/compat/web-ifc/streamed_preview_channel.js +176 -106
  25. package/compiled/src/version/version.js +1 -1
  26. package/compiled/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +1 -1
@@ -1,5 +1,4 @@
1
1
  import { ConwayGeometry } from "../../index.js";
2
- import EntityTypesIfc from "../../ifc/ifc4_gen/entity_types_ifc.gen.js";
3
2
  import { ColumnarIndexSink } from "../../step/parsing/columnar_index.js";
4
3
  import * as glmatrix from "gl-matrix";
5
4
  /**
@@ -25,38 +24,109 @@ export interface PreviewMeshPayload {
25
24
  indexData?: Uint32Array;
26
25
  }
27
26
  /**
28
- * Parse-time preview channel (demand/tiled rendering slice A2): while the
29
- * deferred streamed open is still parsing, periodically snapshot the growing
30
- * columnar index into a PREFIX model, extract a bounded number of products
31
- * through a throwaway extraction, and emit self-contained mesh payloads —
32
- * first pixels within the first seconds of a large parse instead of after
33
- * it.
27
+ * A throwaway prefix extraction built by a {@link PreviewSchemaAdapter} —
28
+ * one preview "generation" over a snapshot of the growing columnar index.
29
+ */
30
+ export interface PreviewPrefixGeneration {
31
+ /** Scene the capture walks (canonical placed-instance tuples the
32
+ * exact tuple shape is schema-specific; the capture narrows it). */
33
+ scene: {
34
+ walk(): IterableIterator<unknown>;
35
+ };
36
+ /** Total pumpable units in this prefix. */
37
+ unitCount: number;
38
+ /** Scaling factor for the capture math. */
39
+ linearScalingFactor: number;
40
+ /**
41
+ * Execute units [from, from+count) — cheap per unit, exceptions per
42
+ * unit swallowed by the adapter (mid-parse forward references).
43
+ *
44
+ * @param from First unit ordinal to execute.
45
+ * @param count Max units.
46
+ * @return {number} Units actually executed.
47
+ */
48
+ runUnits(from: number, count: number): number;
49
+ /**
50
+ * ExpressID identifying a walked geometry (payload identity / dedup).
51
+ *
52
+ * @param geometryLocalID The canonical mesh's localID.
53
+ * @return {number | undefined} The geometry's expressID.
54
+ */
55
+ geometryExpressID(geometryLocalID: number): number | undefined;
56
+ /**
57
+ * Whether the capture math recenters geometry (IFC's classic
58
+ * normalize + center re-add) or uses bare coordination x placement
59
+ * composition (AP214 — instances share one geometry buffer, issue
60
+ * #308).
61
+ */
62
+ recenter: boolean;
63
+ }
64
+ /**
65
+ * Builds throwaway prefix generations for one schema — the only piece of
66
+ * the preview channel that knows what a "model" or a "unit" is.
67
+ */
68
+ export interface PreviewSchemaAdapter {
69
+ /**
70
+ * Build a generation over the given prefix columns.
71
+ *
72
+ * @param data The (fully resident) source buffer.
73
+ * @param conwaywasm The shared geometry wasm wrapper.
74
+ * @param columns A prefix snapshot of the columnar index.
75
+ * @return {PreviewPrefixGeneration | undefined} The generation, or
76
+ * undefined when the prefix cannot build one yet (throw is also
77
+ * tolerated — the channel retries on a later, larger prefix).
78
+ */
79
+ buildGeneration(data: Uint8Array, conwaywasm: ConwayGeometry, columns: unknown): PreviewPrefixGeneration | undefined;
80
+ }
81
+ /**
82
+ * IFC adapter: units are IfcProducts in localID order (stable across
83
+ * prefix growth), extracted through the per-product demand seam.
34
84
  *
35
- * Preview quality, by construction: IFC relationship records
36
- * (IfcRelVoidsElement, IfcRelAssociatesMaterial, styled items) spread to the
37
- * very end of real files (measured ~92–97% depth), so a prefix extraction
38
- * can miss openings and materials. That is why these extractions are
39
- * throwaway: the durable batch pump after the parse re-extracts every
40
- * product with the full model and REPLACES the preview — final geometry
41
- * parity is untouched by this channel.
85
+ * @return {PreviewSchemaAdapter} The adapter.
86
+ */
87
+ export declare function ifcPreviewAdapter(): PreviewSchemaAdapter;
88
+ /**
89
+ * AP214 adapter: units are assembly-tree units (see
90
+ * AP214GeometryExtraction.prepareDemandExtraction). Unit ordinals are
91
+ * only approximately stable across prefix growth (a root's child list
92
+ * can grow, shifting later ordinals) — for a preview that is
93
+ * acceptable: a shifted ordinal re-emits an instance at an identical
94
+ * placement (invisible overlap) or skips one (the durable pump renders
95
+ * it later).
96
+ *
97
+ * @return {PreviewSchemaAdapter} The adapter.
98
+ */
99
+ export declare function ap214PreviewAdapter(): PreviewSchemaAdapter;
100
+ /**
101
+ * Parse-time preview channel (demand/tiled rendering slice A2): while a
102
+ * deferred streamed open is still parsing, periodically snapshot the
103
+ * growing columnar index into a PREFIX model, extract a bounded number of
104
+ * units through a throwaway extraction, and emit self-contained mesh
105
+ * payloads — first pixels within the first seconds of a large parse
106
+ * instead of after it. Schema knowledge lives in the
107
+ * {@link PreviewSchemaAdapter}; the channel owns scheduling, generations,
108
+ * watermarks, payload copies, caps and the coordination pin.
42
109
  *
43
- * Scheduling: the cooperative parse yields to the event loop via macrotasks
44
- * every ~50ms; each pump tick runs in one of those gaps under a hard time
45
- * budget, so the parse keeps the bulk of the main thread.
110
+ * Preview quality, by construction: relationship records (IFC voids,
111
+ * materials, styled items) spread to the very end of real files
112
+ * (measured ~92–97% depth), so a prefix extraction can miss
113
+ * openings and materials. That is why these extractions are throwaway:
114
+ * the durable batch pump after the parse re-extracts every unit with the
115
+ * full model and REPLACES the preview — final geometry parity is
116
+ * untouched by this channel.
46
117
  *
47
- * Product cursor: top-level localIDs are stable across snapshots (dense
48
- * parse order) and `types(IfcProduct)` iterates in localID order, so a
49
- * single ordinal cursor advances across generations without re-extracting.
50
- * Products whose extraction throws mid-parse (unparsed forward references)
51
- * are skipped — the durable pump extracts them correctly later.
118
+ * Scheduling: the cooperative parse yields to the event loop via
119
+ * macrotasks every ~50ms; each pump tick runs in one of those gaps under
120
+ * a hard time budget, so the parse keeps the bulk of the main thread.
52
121
  */
53
122
  export declare class StreamedPreviewChannel {
54
123
  private readonly data;
55
124
  private readonly conwaywasm;
56
125
  private readonly sink;
126
+ private readonly adapter;
57
127
  private readonly coordinateToOrigin;
58
128
  private readonly onMesh;
59
- private readonly maxProducts;
129
+ private readonly maxUnits;
60
130
  private readonly maxBytes;
61
131
  private readonly firstGenerationMinRecords;
62
132
  /** Coordination matrix pinned from the first captured instance, exactly
@@ -65,10 +135,11 @@ export declare class StreamedPreviewChannel {
65
135
  coordinationMatrix?: glmatrix.mat4;
66
136
  private stopped_;
67
137
  private timer_?;
68
- private emittedProducts_;
138
+ private emittedUnits_;
69
139
  private emittedBytes_;
70
- /** Ordinal cursor into the (append-stable) product list. */
71
- private productOrdinal_;
140
+ /** Ordinal cursor into the unit list (see the adapter's stability
141
+ * notes). */
142
+ private unitOrdinal_;
72
143
  /** Geometry expressIDs whose payload has been emitted (cross-generation
73
144
  * dedup for mapped/shared geometry). */
74
145
  private readonly emittedGeometry_;
@@ -80,14 +151,15 @@ export declare class StreamedPreviewChannel {
80
151
  * — ticks run between parse yields, never concurrently with the durable
81
152
  * extraction, which is created after the parse completes).
82
153
  * @param sink The live columnar sink the streamed parse is filling.
154
+ * @param adapter The schema adapter building prefix generations.
83
155
  * @param coordinateToOrigin The open's COORDINATE_TO_ORIGIN setting.
84
156
  * @param onMesh Consumer callback for each preview payload.
85
- * @param maxProducts Cap on products ever preview-extracted.
157
+ * @param maxUnits Cap on units ever preview-extracted.
86
158
  * @param maxBytes Cap on total payload bytes copied out.
87
159
  * @param firstGenerationMinRecords Records required before the first
88
160
  * snapshot (tests lower it for tiny fixtures).
89
161
  */
90
- constructor(data: Uint8Array, conwaywasm: ConwayGeometry, sink: ColumnarIndexSink<EntityTypesIfc>, coordinateToOrigin: boolean, onMesh: (mesh: PreviewMeshPayload) => void, maxProducts?: number, maxBytes?: number, firstGenerationMinRecords?: number);
162
+ constructor(data: Uint8Array, conwaywasm: ConwayGeometry, sink: ColumnarIndexSink<number>, adapter: PreviewSchemaAdapter, coordinateToOrigin: boolean, onMesh: (mesh: PreviewMeshPayload) => void, maxUnits?: number, maxBytes?: number, firstGenerationMinRecords?: number);
91
163
  /** Begin ticking (call just before awaiting the parse). */
92
164
  start(): void;
93
165
  /**
@@ -100,29 +172,30 @@ export declare class StreamedPreviewChannel {
100
172
  get capped(): boolean;
101
173
  private schedule_;
102
174
  /**
103
- * One pump tick: ensure a generation with pending products exists, then
175
+ * One pump tick: ensure a generation with pending units exists, then
104
176
  * extract + capture under the time budget.
105
177
  */
106
178
  private tick_;
107
179
  /**
108
- * Ensure a generation with pending products: keep the current one while
109
- * it has work; otherwise snapshot a fresh prefix model once the index has
110
- * grown enough to be worth the copy.
180
+ * Ensure a generation with pending units: keep the current one while
181
+ * it has work; otherwise snapshot a fresh prefix model once the index
182
+ * has grown enough to be worth the copy.
111
183
  *
112
- * @return {boolean} True when a generation with pending products exists.
184
+ * @return {boolean} True when a generation with pending units exists.
113
185
  */
114
186
  private ensureGeneration_;
115
187
  /**
116
188
  * Walk the current generation's scene and emit every not-yet-captured
117
- * placed instance as a payload — the preview twin of the shim's durable
118
- * delta capture, with the same placed-geometry math (normalize, scaling,
119
- * coordination) so preview and durable placements coincide, but copying
120
- * geometry OUT of the wasm heap instead of retaining native references.
189
+ * placed instance as a payload — the preview twin of the durable delta
190
+ * captures, with the same placed-geometry math per schema (recentering
191
+ * for IFC, bare composition for AP214) so preview and durable
192
+ * placements coincide, but copying geometry OUT of the wasm heap
193
+ * instead of retaining native references.
121
194
  */
122
195
  private captureNewInstances_;
123
196
  /**
124
197
  * Test seam: run generation building + extraction + capture synchronously
125
- * until either every product currently in the sink is attempted or a cap
198
+ * until either every unit currently in the sink is attempted or a cap
126
199
  * is hit — what the timer-driven ticks do, without the timers.
127
200
  */
128
201
  drainForTest(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"streamed_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/streamed_preview_channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAM5C,OAAO,cAAc,MAAM,yCAAyC,CAAA;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAA;AAwCrC;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,SAAS,CAAC,EAAE,WAAW,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,sBAAsB;IA6C7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,yBAAyB;IAlD9C;;4DAEwD;IACjD,kBAAkB,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAA;IAEzC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAC,CAA+B;IAE9C,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,aAAa,CAAI;IAEzB,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAI;IAE3B;4CACwC;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;IAErD,OAAO,CAAC,WAAW,CAAC,CAOnB;IAED,OAAO,CAAC,oBAAoB,CAAI;IAEhC;;;;;;;;;;;;OAYG;gBAEkB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,cAAc,EAC1B,IAAI,EAAE,iBAAiB,CAAC,cAAc,CAAC,EACvC,kBAAkB,EAAE,OAAO,EAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,EAC1C,WAAW,GAAE,MAAqC,EAClD,QAAQ,GAAE,MAAkC,EAC5C,yBAAyB,GAAE,MAChB;IAGhC,2DAA2D;IACpD,KAAK,IAAI,IAAI;IAIpB;;;;OAIG;IACI,IAAI,IAAI,IAAI;IASnB,oEAAoE;IACpE,IAAW,MAAM,IAAI,OAAO,CAG3B;IAGD,OAAO,CAAC,SAAS;IAqBjB;;;OAGG;IACH,OAAO,CAAC,KAAK;IAoCb;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAiDzB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAgJ5B;;;;OAIG;IACI,YAAY,IAAI,IAAI;CA+B5B"}
1
+ {"version":3,"file":"streamed_preview_channel.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/streamed_preview_channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAU5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAA;AAwCrC;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,SAAS,CAAC,EAAE,WAAW,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IAEtC;wEACoE;IACpE,KAAK,EAAE;QAAE,IAAI,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAE5C,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAA;IAEjB,2CAA2C;IAC3C,mBAAmB,EAAE,MAAM,CAAA;IAE3B;;;;;;;OAOG;IACH,QAAQ,CAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAI,MAAM,CAAA;IAE/C;;;;;OAKG;IACH,iBAAiB,CAAE,eAAe,EAAE,MAAM,GAAI,MAAM,GAAG,SAAS,CAAA;IAEhE;;;;;OAKG;IACH,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IAEnC;;;;;;;;;OASG;IACH,eAAe,CACb,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,OAAO,GACf,uBAAuB,GAAG,SAAS,CAAA;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,oBAAoB,CA8CxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,oBAAoB,CAiC1D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,sBAAsB;IA2C7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,yBAAyB;IAjD9C;;4DAEwD;IACjD,kBAAkB,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAA;IAEzC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAC,CAA+B;IAE9C,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,aAAa,CAAI;IAEzB;iBACa;IACb,OAAO,CAAC,YAAY,CAAI;IAExB;4CACwC;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;IAErD,OAAO,CAAC,WAAW,CAAC,CAGnB;IAED,OAAO,CAAC,oBAAoB,CAAI;IAEhC;;;;;;;;;;;;;OAaG;gBAEkB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,cAAc,EAC1B,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,OAAO,EAAE,oBAAoB,EAC7B,kBAAkB,EAAE,OAAO,EAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,EAC1C,QAAQ,GAAE,MAAkC,EAC5C,QAAQ,GAAE,MAAkC,EAC5C,yBAAyB,GAAE,MAChB;IAGhC,2DAA2D;IACpD,KAAK,IAAI,IAAI;IAIpB;;;;OAIG;IACI,IAAI,IAAI,IAAI;IASnB,oEAAoE;IACpE,IAAW,MAAM,IAAI,OAAO,CAG3B;IAGD,OAAO,CAAC,SAAS;IAqBjB;;;OAGG;IACH,OAAO,CAAC,KAAK;IA4Bb;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAsCzB;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAgL5B;;;;OAIG;IACI,YAAY,IAAI,IAAI;CAuB5B"}
@@ -2,6 +2,8 @@ import { CanonicalMeshType } from "../../index.js";
2
2
  import IfcStepModel from "../../ifc/ifc_step_model.js";
3
3
  import { IfcGeometryExtraction } from "../../ifc/ifc_geometry_extraction.js";
4
4
  import { IfcProduct } from "../../ifc/ifc4_gen/index.js";
5
+ import AP214StepModel from "../../AP214E3_2010/ap214_step_model.js";
6
+ import { AP214GeometryExtraction, } from "../../AP214E3_2010/ap214_geometry_extraction.js";
5
7
  import * as glmatrix from "gl-matrix";
6
8
  /* eslint-disable no-magic-numbers */
7
9
  /** Ms between preview pump ticks (interleaves with the parse's yields). */
@@ -15,43 +17,123 @@ const FIRST_GENERATION_MIN_RECORDS = 1024;
15
17
  /** A new generation only when the index grew this much past the previous
16
18
  * snapshot (bounds snapshot copies to O(GROWTH/(GROWTH-1)) of the file). */
17
19
  const GENERATION_GROWTH_FACTOR = 1.5;
18
- /** Default cap on products the preview channel ever extracts. Preview
20
+ /** Default cap on units the preview channel ever extracts. Preview
19
21
  * generations are throwaway extractions whose native geometry is not
20
22
  * reclaimed until page teardown (the shim never frees classic scenes
21
23
  * either — see closeModel), so the cap bounds that one-time cost. */
22
- const DEFAULT_MAX_PREVIEW_PRODUCTS = 4096;
24
+ const DEFAULT_MAX_PREVIEW_UNITS = 4096;
23
25
  /** Default cap on total payload bytes copied out to the consumer. */
24
26
  const DEFAULT_MAX_PREVIEW_BYTES = 48 * 1024 * 1024;
25
27
  const FLOATS_PER_VERTEX = 6;
26
28
  const BYTES_PER_FLOAT = 4;
27
29
  const DEFAULT_COLOR = [0.8, 0.8, 0.8, 1];
28
- // Matches the shim proxy's NormalizeMat (Z-up -> Y-up).
30
+ // Matches the shim proxies' NormalizeMat (Z-up -> Y-up).
29
31
  const NORMALIZE_MAT = glmatrix.mat4.fromValues(1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1);
30
32
  /**
31
- * Parse-time preview channel (demand/tiled rendering slice A2): while the
32
- * deferred streamed open is still parsing, periodically snapshot the growing
33
- * columnar index into a PREFIX model, extract a bounded number of products
34
- * through a throwaway extraction, and emit self-contained mesh payloads —
35
- * first pixels within the first seconds of a large parse instead of after
36
- * it.
33
+ * IFC adapter: units are IfcProducts in localID order (stable across
34
+ * prefix growth), extracted through the per-product demand seam.
37
35
  *
38
- * Preview quality, by construction: IFC relationship records
39
- * (IfcRelVoidsElement, IfcRelAssociatesMaterial, styled items) spread to the
40
- * very end of real files (measured ~92–97% depth), so a prefix extraction
41
- * can miss openings and materials. That is why these extractions are
42
- * throwaway: the durable batch pump after the parse re-extracts every
43
- * product with the full model and REPLACES the preview — final geometry
44
- * parity is untouched by this channel.
36
+ * @return {PreviewSchemaAdapter} The adapter.
37
+ */
38
+ export function ifcPreviewAdapter() {
39
+ return {
40
+ buildGeneration(data, conwaywasm, columns) {
41
+ const model = new IfcStepModel(data, columns);
42
+ const products = [];
43
+ for (const product of model.types(IfcProduct)) {
44
+ products.push(product.localID);
45
+ }
46
+ if (products.length === 0) {
47
+ return void 0;
48
+ }
49
+ const extraction = new IfcGeometryExtraction(conwaywasm, model);
50
+ return {
51
+ scene: extraction.scene,
52
+ unitCount: products.length,
53
+ get linearScalingFactor() {
54
+ return extraction.getLinearScalingFactor();
55
+ },
56
+ runUnits: (from, count) => {
57
+ const end = Math.min(from + count, products.length);
58
+ let executed = 0;
59
+ for (let where = from; where < end; ++where) {
60
+ try {
61
+ if (extraction.extractProductGeometryByLocalID(products[where])) {
62
+ ++executed;
63
+ }
64
+ }
65
+ catch {
66
+ // Unparsed forward reference — the durable pump extracts
67
+ // this product from the full model later.
68
+ }
69
+ }
70
+ return executed;
71
+ },
72
+ geometryExpressID: (geometryLocalID) => model.getElementByLocalID(geometryLocalID)?.expressID,
73
+ recenter: true,
74
+ };
75
+ },
76
+ };
77
+ }
78
+ /**
79
+ * AP214 adapter: units are assembly-tree units (see
80
+ * AP214GeometryExtraction.prepareDemandExtraction). Unit ordinals are
81
+ * only approximately stable across prefix growth (a root's child list
82
+ * can grow, shifting later ordinals) — for a preview that is
83
+ * acceptable: a shifted ordinal re-emits an instance at an identical
84
+ * placement (invisible overlap) or skips one (the durable pump renders
85
+ * it later).
86
+ *
87
+ * @return {PreviewSchemaAdapter} The adapter.
88
+ */
89
+ export function ap214PreviewAdapter() {
90
+ return {
91
+ buildGeneration(data, conwaywasm, columns) {
92
+ const model = new AP214StepModel(data, columns);
93
+ const extraction = new AP214GeometryExtraction(conwaywasm, model);
94
+ extraction.prepareDemandExtraction();
95
+ if (extraction.demandUnitCount === 0) {
96
+ return void 0;
97
+ }
98
+ return {
99
+ scene: extraction.scene,
100
+ unitCount: extraction.demandUnitCount,
101
+ get linearScalingFactor() {
102
+ return extraction.getLinearScalingFactor();
103
+ },
104
+ runUnits: (from, count) => {
105
+ if (extraction.demandUnitCursor < from) {
106
+ extraction.skipDemandUnits(from - extraction.demandUnitCursor);
107
+ }
108
+ return extraction.extractDemandUnitBatch(count);
109
+ },
110
+ geometryExpressID: (geometryLocalID) => model.getElementByLocalID(geometryLocalID)?.expressID,
111
+ recenter: false,
112
+ };
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Parse-time preview channel (demand/tiled rendering slice A2): while a
118
+ * deferred streamed open is still parsing, periodically snapshot the
119
+ * growing columnar index into a PREFIX model, extract a bounded number of
120
+ * units through a throwaway extraction, and emit self-contained mesh
121
+ * payloads — first pixels within the first seconds of a large parse
122
+ * instead of after it. Schema knowledge lives in the
123
+ * {@link PreviewSchemaAdapter}; the channel owns scheduling, generations,
124
+ * watermarks, payload copies, caps and the coordination pin.
45
125
  *
46
- * Scheduling: the cooperative parse yields to the event loop via macrotasks
47
- * every ~50ms; each pump tick runs in one of those gaps under a hard time
48
- * budget, so the parse keeps the bulk of the main thread.
126
+ * Preview quality, by construction: relationship records (IFC voids,
127
+ * materials, styled items) spread to the very end of real files
128
+ * (measured ~92–97% depth), so a prefix extraction can miss
129
+ * openings and materials. That is why these extractions are throwaway:
130
+ * the durable batch pump after the parse re-extracts every unit with the
131
+ * full model and REPLACES the preview — final geometry parity is
132
+ * untouched by this channel.
49
133
  *
50
- * Product cursor: top-level localIDs are stable across snapshots (dense
51
- * parse order) and `types(IfcProduct)` iterates in localID order, so a
52
- * single ordinal cursor advances across generations without re-extracting.
53
- * Products whose extraction throws mid-parse (unparsed forward references)
54
- * are skipped — the durable pump extracts them correctly later.
134
+ * Scheduling: the cooperative parse yields to the event loop via
135
+ * macrotasks every ~50ms; each pump tick runs in one of those gaps under
136
+ * a hard time budget, so the parse keeps the bulk of the main thread.
55
137
  */
56
138
  export class StreamedPreviewChannel {
57
139
  /**
@@ -60,27 +142,30 @@ export class StreamedPreviewChannel {
60
142
  * — ticks run between parse yields, never concurrently with the durable
61
143
  * extraction, which is created after the parse completes).
62
144
  * @param sink The live columnar sink the streamed parse is filling.
145
+ * @param adapter The schema adapter building prefix generations.
63
146
  * @param coordinateToOrigin The open's COORDINATE_TO_ORIGIN setting.
64
147
  * @param onMesh Consumer callback for each preview payload.
65
- * @param maxProducts Cap on products ever preview-extracted.
148
+ * @param maxUnits Cap on units ever preview-extracted.
66
149
  * @param maxBytes Cap on total payload bytes copied out.
67
150
  * @param firstGenerationMinRecords Records required before the first
68
151
  * snapshot (tests lower it for tiny fixtures).
69
152
  */
70
- constructor(data, conwaywasm, sink, coordinateToOrigin, onMesh, maxProducts = DEFAULT_MAX_PREVIEW_PRODUCTS, maxBytes = DEFAULT_MAX_PREVIEW_BYTES, firstGenerationMinRecords = FIRST_GENERATION_MIN_RECORDS) {
153
+ constructor(data, conwaywasm, sink, adapter, coordinateToOrigin, onMesh, maxUnits = DEFAULT_MAX_PREVIEW_UNITS, maxBytes = DEFAULT_MAX_PREVIEW_BYTES, firstGenerationMinRecords = FIRST_GENERATION_MIN_RECORDS) {
71
154
  this.data = data;
72
155
  this.conwaywasm = conwaywasm;
73
156
  this.sink = sink;
157
+ this.adapter = adapter;
74
158
  this.coordinateToOrigin = coordinateToOrigin;
75
159
  this.onMesh = onMesh;
76
- this.maxProducts = maxProducts;
160
+ this.maxUnits = maxUnits;
77
161
  this.maxBytes = maxBytes;
78
162
  this.firstGenerationMinRecords = firstGenerationMinRecords;
79
163
  this.stopped_ = false;
80
- this.emittedProducts_ = 0;
164
+ this.emittedUnits_ = 0;
81
165
  this.emittedBytes_ = 0;
82
- /** Ordinal cursor into the (append-stable) product list. */
83
- this.productOrdinal_ = 0;
166
+ /** Ordinal cursor into the unit list (see the adapter's stability
167
+ * notes). */
168
+ this.unitOrdinal_ = 0;
84
169
  /** Geometry expressIDs whose payload has been emitted (cross-generation
85
170
  * dedup for mapped/shared geometry). */
86
171
  this.emittedGeometry_ = new Set();
@@ -104,7 +189,7 @@ export class StreamedPreviewChannel {
104
189
  }
105
190
  /** True when a cap was hit and the channel retired itself early. */
106
191
  get capped() {
107
- return this.emittedProducts_ >= this.maxProducts ||
192
+ return this.emittedUnits_ >= this.maxUnits ||
108
193
  this.emittedBytes_ >= this.maxBytes;
109
194
  }
110
195
  // eslint-disable-next-line require-jsdoc
@@ -126,7 +211,7 @@ export class StreamedPreviewChannel {
126
211
  }, TICK_INTERVAL_MS);
127
212
  }
128
213
  /**
129
- * One pump tick: ensure a generation with pending products exists, then
214
+ * One pump tick: ensure a generation with pending units exists, then
130
215
  * extract + capture under the time budget.
131
216
  */
132
217
  tick_() {
@@ -134,87 +219,70 @@ export class StreamedPreviewChannel {
134
219
  if (!this.ensureGeneration_()) {
135
220
  return;
136
221
  }
137
- const generation = this.generation_;
138
- const { extraction, products } = generation;
222
+ const active = this.generation_;
223
+ const { generation } = active;
139
224
  let extractedThisTick = 0;
140
- while (generation.nextIndex < products.length &&
141
- this.emittedProducts_ + extractedThisTick < this.maxProducts &&
225
+ while (this.unitOrdinal_ < generation.unitCount &&
226
+ this.emittedUnits_ + extractedThisTick < this.maxUnits &&
142
227
  Date.now() < deadline) {
143
- const localID = products[generation.nextIndex++];
144
- ++this.productOrdinal_;
145
- try {
146
- if (extraction.extractProductGeometryByLocalID(localID)) {
147
- ++extractedThisTick;
148
- }
149
- }
150
- catch {
151
- // Unparsed forward reference (or any other mid-parse gap): skip.
152
- // The durable pump extracts this product from the full model.
153
- }
228
+ const executed = generation.runUnits(this.unitOrdinal_, 1);
229
+ ++this.unitOrdinal_;
230
+ extractedThisTick += executed;
154
231
  }
155
232
  if (extractedThisTick > 0) {
156
233
  this.captureNewInstances_();
157
- this.emittedProducts_ += extractedThisTick;
234
+ this.emittedUnits_ += extractedThisTick;
158
235
  }
159
236
  }
160
237
  /**
161
- * Ensure a generation with pending products: keep the current one while
162
- * it has work; otherwise snapshot a fresh prefix model once the index has
163
- * grown enough to be worth the copy.
238
+ * Ensure a generation with pending units: keep the current one while
239
+ * it has work; otherwise snapshot a fresh prefix model once the index
240
+ * has grown enough to be worth the copy.
164
241
  *
165
- * @return {boolean} True when a generation with pending products exists.
242
+ * @return {boolean} True when a generation with pending units exists.
166
243
  */
167
244
  ensureGeneration_() {
168
- const generation = this.generation_;
169
- if (generation !== void 0 && generation.nextIndex < generation.products.length) {
245
+ const active = this.generation_;
246
+ if (active !== void 0 && this.unitOrdinal_ < active.generation.unitCount) {
170
247
  return true;
171
248
  }
172
249
  const records = this.sink.topLevelCount;
173
250
  if (records < this.firstGenerationMinRecords) {
174
251
  return false;
175
252
  }
176
- if (generation !== void 0 &&
253
+ if (active !== void 0 &&
177
254
  records < this.lastSnapshotRecords_ * GENERATION_GROWTH_FACTOR) {
178
255
  return false;
179
256
  }
180
257
  const columns = this.sink.snapshot();
181
- const model = new IfcStepModel(this.data, columns);
182
- const products = [];
183
- for (const product of model.types(IfcProduct)) {
184
- products.push(product.localID);
185
- }
186
- if (products.length <= this.productOrdinal_) {
187
- // Prefix grew but produced no new products — wait for more records.
188
- this.lastSnapshotRecords_ = records;
258
+ const generation = this.adapter.buildGeneration(this.data, this.conwaywasm, columns);
259
+ this.lastSnapshotRecords_ = records;
260
+ if (generation === void 0 || generation.unitCount <= this.unitOrdinal_) {
261
+ // Prefix grew but produced no new units — wait for more records.
189
262
  return false;
190
263
  }
191
- const extraction = new IfcGeometryExtraction(this.conwaywasm, model);
192
264
  this.generation_ = {
193
- model,
194
- extraction,
195
- products,
196
- nextIndex: this.productOrdinal_,
265
+ generation,
197
266
  capturedCounts: new Map(),
198
- snapshotRecords: records,
199
267
  };
200
- this.lastSnapshotRecords_ = records;
201
268
  return true;
202
269
  }
203
270
  /**
204
271
  * Walk the current generation's scene and emit every not-yet-captured
205
- * placed instance as a payload — the preview twin of the shim's durable
206
- * delta capture, with the same placed-geometry math (normalize, scaling,
207
- * coordination) so preview and durable placements coincide, but copying
208
- * geometry OUT of the wasm heap instead of retaining native references.
272
+ * placed instance as a payload — the preview twin of the durable delta
273
+ * captures, with the same placed-geometry math per schema (recentering
274
+ * for IFC, bare composition for AP214) so preview and durable
275
+ * placements coincide, but copying geometry OUT of the wasm heap
276
+ * instead of retaining native references.
209
277
  */
210
278
  captureNewInstances_() {
211
- const generation = this.generation_;
212
- const { model, extraction, capturedCounts } = generation;
213
- const scene = extraction.scene;
214
- const linearScalingFactor = extraction.getLinearScalingFactor();
279
+ const active = this.generation_;
280
+ const { generation, capturedCounts } = active;
281
+ const { scene, recenter } = generation;
282
+ const linearScalingFactor = generation.linearScalingFactor;
215
283
  const seenThisPass = new Map();
216
- // eslint-disable-next-line no-unused-vars
217
- for (const [_, nativeTransform, geometry, material, entity] of scene.walk()) {
284
+ for (const walked of scene.walk()) {
285
+ const [, nativeTransform, geometry, material, entity] = walked;
218
286
  if (entity?.localID === void 0 || entity.expressID === void 0) {
219
287
  continue;
220
288
  }
@@ -238,14 +306,16 @@ export class StreamedPreviewChannel {
238
306
  if (this.coordinationMatrix === void 0 && this.coordinateToOrigin) {
239
307
  nativePt = geometry.geometry.getPoint(0);
240
308
  }
241
- const geomCenter = glmatrix.vec3.create();
242
- const center = geometry.geometry.normalize();
243
- geomCenter[0] = center.x;
244
- geomCenter[1] = center.y;
245
- geomCenter[2] = center.z;
246
309
  const translationMatrixGeomMin = glmatrix.mat4.create();
247
- glmatrix.mat4.fromTranslation(translationMatrixGeomMin, geomCenter);
248
- const geometryExpressID = model.getElementByLocalID(geometry.localID)?.expressID;
310
+ if (recenter) {
311
+ const geomCenter = glmatrix.vec3.create();
312
+ const center = geometry.geometry.normalize();
313
+ geomCenter[0] = center.x;
314
+ geomCenter[1] = center.y;
315
+ geomCenter[2] = center.z;
316
+ glmatrix.mat4.fromTranslation(translationMatrixGeomMin, geomCenter);
317
+ }
318
+ const geometryExpressID = generation.geometryExpressID(geometry.localID);
249
319
  const geometryTransform = nativeTransform?.getValues();
250
320
  let newMatrix;
251
321
  if (geometryTransform !== void 0) {
@@ -268,11 +338,18 @@ export class StreamedPreviewChannel {
268
338
  }
269
339
  const coordination = this.coordinationMatrix ?? glmatrix.mat4.create();
270
340
  const newTransform = glmatrix.mat4.create();
271
- const scaleMatrix = glmatrix.mat4.create();
272
- const scaleVec = glmatrix.vec3.fromValues(linearScalingFactor, linearScalingFactor, linearScalingFactor);
273
- glmatrix.mat4.scale(scaleMatrix, scaleMatrix, scaleVec);
274
- glmatrix.mat4.multiply(newTransform, coordination, newMatrix);
275
- glmatrix.mat4.multiply(newTransform, newTransform, translationMatrixGeomMin);
341
+ if (recenter) {
342
+ const scaleMatrix = glmatrix.mat4.create();
343
+ const scaleVec = glmatrix.vec3.fromValues(linearScalingFactor, linearScalingFactor, linearScalingFactor);
344
+ glmatrix.mat4.scale(scaleMatrix, scaleMatrix, scaleVec);
345
+ glmatrix.mat4.multiply(newTransform, coordination, newMatrix);
346
+ glmatrix.mat4.multiply(newTransform, newTransform, translationMatrixGeomMin);
347
+ }
348
+ else {
349
+ // Bare composition — no per-leaf recenter (AP214 instances share
350
+ // one geometry buffer, issue #308), matching its durable capture.
351
+ glmatrix.mat4.multiply(newTransform, coordination, newMatrix);
352
+ }
276
353
  const payload = {
277
354
  expressID: entity.expressID,
278
355
  geometryExpressID,
@@ -305,7 +382,7 @@ export class StreamedPreviewChannel {
305
382
  }
306
383
  /**
307
384
  * Test seam: run generation building + extraction + capture synchronously
308
- * until either every product currently in the sink is attempted or a cap
385
+ * until either every unit currently in the sink is attempted or a cap
309
386
  * is hit — what the timer-driven ticks do, without the timers.
310
387
  */
311
388
  drainForTest() {
@@ -313,22 +390,15 @@ export class StreamedPreviewChannel {
313
390
  if (this.capped || !this.ensureGeneration_()) {
314
391
  return;
315
392
  }
316
- const generation = this.generation_;
317
- if (generation.nextIndex >= generation.products.length) {
393
+ const active = this.generation_;
394
+ if (this.unitOrdinal_ >= active.generation.unitCount) {
318
395
  return;
319
396
  }
320
- const localID = generation.products[generation.nextIndex++];
321
- ++this.productOrdinal_;
322
- let extracted = false;
323
- try {
324
- extracted = generation.extraction.extractProductGeometryByLocalID(localID);
325
- }
326
- catch {
327
- // Skip — mirrors tick_.
328
- }
329
- if (extracted) {
397
+ const executed = active.generation.runUnits(this.unitOrdinal_, 1);
398
+ ++this.unitOrdinal_;
399
+ if (executed > 0) {
330
400
  this.captureNewInstances_();
331
- ++this.emittedProducts_;
401
+ ++this.emittedUnits_;
332
402
  }
333
403
  }
334
404
  }
@@ -5,5 +5,5 @@
5
5
  // only the first segment (major) is meaningful and is the one CI carries forward.
6
6
  // Must stay in `vN.N.N` shape: the CI stamp regex, scripts/updateVersion.mjs, and
7
7
  // statistics.ts all match `v\d+\.\d+\.\d+`.
8
- const versionString = 'Conway v1.426.1283';
8
+ const versionString = 'Conway v1.428.1289';
9
9
  export { versionString };