@bldrs-ai/conway 1.424.1275 → 1.426.1283

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 (30) hide show
  1. package/compiled/examples/browser-bundled.cjs +56 -1
  2. package/compiled/examples/cli-bundled.cjs +56 -1
  3. package/compiled/examples/cli-step-bundled.cjs +15 -1
  4. package/compiled/examples/validator-bundled.cjs +56 -1
  5. package/compiled/src/compat/web-ifc/ifc_api.d.ts +20 -0
  6. package/compiled/src/compat/web-ifc/ifc_api.d.ts.map +1 -1
  7. package/compiled/src/compat/web-ifc/ifc_api.js +7 -0
  8. package/compiled/src/compat/web-ifc/ifc_api_deferred_open.test.js +66 -9
  9. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.d.ts +2 -0
  10. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.d.ts.map +1 -0
  11. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.js +185 -0
  12. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +40 -8
  13. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  14. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +115 -28
  15. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +130 -0
  16. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -0
  17. package/compiled/src/compat/web-ifc/streamed_preview_channel.js +335 -0
  18. package/compiled/src/core/progress.d.ts +8 -1
  19. package/compiled/src/core/progress.d.ts.map +1 -1
  20. package/compiled/src/core/progress.js +22 -1
  21. package/compiled/src/core/progress.test.js +11 -2
  22. package/compiled/src/ifc/ifc_model_geometry.d.ts +12 -0
  23. package/compiled/src/ifc/ifc_model_geometry.d.ts.map +1 -1
  24. package/compiled/src/ifc/ifc_model_geometry.js +15 -0
  25. package/compiled/src/step/parsing/columnar_index.d.ts +22 -0
  26. package/compiled/src/step/parsing/columnar_index.d.ts.map +1 -1
  27. package/compiled/src/step/parsing/columnar_index.js +26 -0
  28. package/compiled/src/version/version.js +1 -1
  29. package/compiled/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ /* eslint-disable no-magic-numbers */
2
+ // Parse-time preview channel (demand/tiled rendering slice A2): prefix
3
+ // snapshots of the live columnar sink must be exact prefixes of the final
4
+ // columns, and a full drain of the channel (prefix == whole file) must
5
+ // reproduce the classic StreamAllMeshes instance set — same entities,
6
+ // same geometry ids, same placed transforms — since the durable pump the
7
+ // preview hands over to is already pinned to classic parity.
8
+ import * as fs from "fs";
9
+ import { beforeAll, describe, expect, test } from "@jest/globals";
10
+ import { ConwayGeometry } from "../../../dependencies/conway-geom/index.js";
11
+ import IfcStepParser from "../../ifc/ifc_step_parser.js";
12
+ import { BufferByteSource } from "../../step/parsing/byte_source.js";
13
+ import { ColumnarIndexSink, } from "../../step/parsing/columnar_index.js";
14
+ import { ParseResult } from "../../step/parsing/step_parser.js";
15
+ import { buildIndexStreaming } from "../../step/parsing/streaming_index_builder.js";
16
+ import { IfcAPI } from "./ifc_api.js";
17
+ import { StreamedPreviewChannel, } from "./streamed_preview_channel.js";
18
+ const SETTINGS = { COORDINATE_TO_ORIGIN: true, USE_FAST_BOOLS: true };
19
+ const POOL = 1024 * 1024;
20
+ let api;
21
+ let conwayGeometry;
22
+ let data;
23
+ /**
24
+ * Parse index.ifc's data block into a fresh columnar sink.
25
+ *
26
+ * @param wrap Optional sink wrapper (snapshot triggers).
27
+ * @return {ColumnarIndexSink} The filled sink.
28
+ */
29
+ function buildSink(wrap) {
30
+ const sink = new ColumnarIndexSink();
31
+ const { result } = buildIndexStreaming(new BufferByteSource(data), IfcStepParser.Instance, POOL, void 0, wrap !== void 0 ? wrap(sink) : sink);
32
+ expect(result).toBe(ParseResult.COMPLETE);
33
+ return sink;
34
+ }
35
+ /**
36
+ * Classic reference: expressID -> list of placed instances.
37
+ *
38
+ * @param modelID An open classic model.
39
+ * @return {Map} expressID -> {geometryExpressID, flatTransformation}[].
40
+ */
41
+ function classicInstances(modelID) {
42
+ const instances = new Map();
43
+ api.StreamAllMeshes(modelID, (mesh) => {
44
+ const list = instances.get(mesh.expressID) ?? [];
45
+ for (let where = 0; where < mesh.geometries.size(); ++where) {
46
+ const placed = mesh.geometries.get(where);
47
+ list.push({
48
+ geometryExpressID: placed.geometryExpressID,
49
+ flatTransformation: [...placed.flatTransformation],
50
+ });
51
+ }
52
+ instances.set(mesh.expressID, list);
53
+ });
54
+ return instances;
55
+ }
56
+ beforeAll(async () => {
57
+ api = new IfcAPI();
58
+ await api.Init();
59
+ conwayGeometry = new ConwayGeometry();
60
+ expect(await conwayGeometry.initialize()).toBe(true);
61
+ data = new Uint8Array(fs.readFileSync("data/index.ifc"));
62
+ }, 120000);
63
+ describe("ColumnarIndexSink.snapshot", () => {
64
+ test("a mid-parse snapshot is an exact prefix of the final columns", () => {
65
+ const snapshotAt = 50;
66
+ let snapshot;
67
+ const sink = buildSink((inner) => ({
68
+ pushTopLevel: (entry) => {
69
+ inner.pushTopLevel(entry);
70
+ if (inner.topLevelCount === snapshotAt) {
71
+ snapshot = inner.snapshot();
72
+ }
73
+ },
74
+ reset: () => inner.reset(),
75
+ }));
76
+ const full = sink.finalize();
77
+ expect(snapshot).toBeDefined();
78
+ expect(snapshot.firstInlineElement).toBe(snapshotAt);
79
+ expect(full.firstInlineElement).toBeGreaterThan(snapshotAt);
80
+ for (let where = 0; where < snapshotAt; ++where) {
81
+ expect(snapshot.address[where]).toBe(full.address[where]);
82
+ expect(snapshot.length[where]).toBe(full.length[where]);
83
+ expect(snapshot.typeID[where]).toBe(full.typeID[where]);
84
+ expect(snapshot.expressID[where]).toBe(full.expressID[where]);
85
+ }
86
+ });
87
+ test("snapshotting does not disturb the sink (finalize sees everything)", () => {
88
+ const undisturbed = buildSink().finalize();
89
+ const snapshotted = buildSink((inner) => ({
90
+ pushTopLevel: (entry) => {
91
+ inner.pushTopLevel(entry);
92
+ // Snapshot aggressively — every 25 records.
93
+ if (inner.topLevelCount % 25 === 0) {
94
+ inner.snapshot();
95
+ }
96
+ },
97
+ reset: () => inner.reset(),
98
+ })).finalize();
99
+ expect(snapshotted.count).toBe(undisturbed.count);
100
+ expect(snapshotted.firstInlineElement).toBe(undisturbed.firstInlineElement);
101
+ expect([...snapshotted.address]).toEqual([...undisturbed.address]);
102
+ expect([...snapshotted.expressID]).toEqual([...undisturbed.expressID]);
103
+ });
104
+ });
105
+ describe("StreamedPreviewChannel", () => {
106
+ test("a full drain reproduces the classic instance set exactly", async () => {
107
+ const classicID = api.OpenModel(data, SETTINGS);
108
+ const classic = classicInstances(classicID);
109
+ let classicTotal = 0;
110
+ for (const list of classic.values()) {
111
+ classicTotal += list.length;
112
+ }
113
+ expect(classicTotal).toBeGreaterThan(0);
114
+ // Channel over a finished sink: the "prefix" is the whole file, so a
115
+ // drain must be a complete, classic-parity extraction.
116
+ const sink = buildSink();
117
+ const payloads = [];
118
+ const channel = new StreamedPreviewChannel(data, conwayGeometry, sink, true, (mesh) => payloads.push(mesh), void 0, void 0, 1);
119
+ channel.drainForTest();
120
+ expect(payloads.length).toBe(classicTotal);
121
+ // Every payload matches a classic instance of the same entity and
122
+ // geometry, with the same placed transform.
123
+ const unmatched = new Map();
124
+ for (const [expressID, list] of classic) {
125
+ unmatched.set(expressID, list.map((entry) => ({ ...entry })));
126
+ }
127
+ for (const payload of payloads) {
128
+ const candidates = unmatched.get(payload.expressID);
129
+ expect(candidates).toBeDefined();
130
+ const matchIndex = candidates.findIndex((candidate) => candidate.geometryExpressID === payload.geometryExpressID &&
131
+ candidate.flatTransformation.every((value, where) => Math.abs(value - payload.flatTransformation[where]) < 0.000001));
132
+ expect(matchIndex).toBeGreaterThanOrEqual(0);
133
+ candidates.splice(matchIndex, 1);
134
+ }
135
+ // Geometry payloads: exactly one carrier per distinct geometry, each
136
+ // carrying a sane interleaved buffer.
137
+ const carriers = payloads.filter((payload) => payload.vertexData !== void 0);
138
+ const distinctGeometry = new Set(payloads.map((p) => p.geometryExpressID));
139
+ expect(carriers.length).toBe(distinctGeometry.size);
140
+ for (const carrier of carriers) {
141
+ expect(carrier.vertexData.length % 6).toBe(0);
142
+ expect(carrier.vertexData.length).toBeGreaterThan(0);
143
+ expect(carrier.indexData.length % 3).toBe(0);
144
+ expect(carrier.indexData.length).toBeGreaterThan(0);
145
+ }
146
+ // The channel derived and pinned a coordination frame (proved
147
+ // equivalent to classic's by the transform parity above), and the
148
+ // deferred open must keep the classic GetCoordinationMatrix
149
+ // contract: identity, because placed transforms are premultiplied
150
+ // and consumers stamp the result onto assembled models — a
151
+ // non-identity return would coordinate twice.
152
+ expect(channel.coordinationMatrix).toBeDefined();
153
+ const deferredID = await api.OpenModelStreamed(data, { ...SETTINGS, DEFER_GEOMETRY: true });
154
+ api.StreamAllMeshes(deferredID, () => { });
155
+ const classicCoordination = api.GetCoordinationMatrix(classicID);
156
+ const deferredCoordination = api.GetCoordinationMatrix(deferredID);
157
+ expect(deferredCoordination).toEqual(classicCoordination);
158
+ api.CloseModel(classicID);
159
+ api.CloseModel(deferredID);
160
+ }, 240000);
161
+ test("ON_PREVIEW_MESH on a deferred streamed open never breaks the durable path", async () => {
162
+ const classicID = api.OpenModel(data, SETTINGS);
163
+ const classic = classicInstances(classicID);
164
+ const payloads = [];
165
+ const deferredID = await api.OpenModelStreamed(data, {
166
+ ...SETTINGS,
167
+ DEFER_GEOMETRY: true,
168
+ ON_PREVIEW_MESH: (mesh) => payloads.push(mesh),
169
+ });
170
+ expect(deferredID).toBeGreaterThanOrEqual(0);
171
+ // index.ifc parses inside one cooperative slice, so the timer-driven
172
+ // channel usually never fires — the contract here is that its presence
173
+ // changes nothing about the durable pump's output.
174
+ const drained = new Map();
175
+ api.StreamAllMeshes(deferredID, (mesh) => {
176
+ drained.set(mesh.expressID, (drained.get(mesh.expressID) ?? 0) + mesh.geometries.size());
177
+ });
178
+ expect(drained.size).toBe(classic.size);
179
+ for (const [expressID, list] of classic) {
180
+ expect(drained.get(expressID)).toBe(list.length);
181
+ }
182
+ api.CloseModel(classicID);
183
+ api.CloseModel(deferredID);
184
+ }, 240000);
185
+ });
@@ -20,6 +20,9 @@ interface IfcProxyLoadState {
20
20
  conwaywasm: ConwayGeometry;
21
21
  /** True when opened without extraction (createDeferred). */
22
22
  deferred?: boolean;
23
+ /** Coordination matrix the parse-time preview channel derived (slice
24
+ * A2) — adopted by the durable capture so both share one frame. */
25
+ previewCoordinationMatrix?: glmatrix.mat4;
23
26
  allTimeStart: number;
24
27
  stepHeader: StepHeader;
25
28
  model: IfcStepModel;
@@ -57,8 +60,28 @@ export declare class IfcApiProxyIfc implements IfcApiModelPassthrough {
57
60
  private deferredMode_;
58
61
  /** Deferred-mode product worklist (file order), lazily enumerated. */
59
62
  private demandProducts_?;
63
+ /**
64
+ * Deferred capture watermarks: entity localID -> how many of its
65
+ * placed instances (in scene-walk order) have been captured. Shared
66
+ * (mapped) geometry attributes instances to an entity from OTHER
67
+ * products' extractions, so an entity's instance set grows across
68
+ * batches - the watermark makes each instance captured exactly once,
69
+ * in the same order the classic single walk would process it.
70
+ */
71
+ private readonly demandCapturedCounts_;
60
72
  /** Cursor into demandProducts_ — products before it are extracted. */
61
73
  private demandCursor_;
74
+ /**
75
+ * Coordination matrix the deferred capture derived (or adopted from
76
+ * the parse-time preview channel). Kept OFF the model tuple's slot 5
77
+ * deliberately: classic streamAllMeshes derives its coordination into
78
+ * a local and getCoordinationMatrix therefore returns identity —
79
+ * consumers (Share) stamp that result onto the assembled model, so a
80
+ * deferred open must present the same identity or coordination would
81
+ * apply twice. This field is only the pump's internal multi-call
82
+ * memory.
83
+ */
84
+ private demandCoordination_?;
62
85
  _isCoordinated: boolean;
63
86
  linearScalingFactor: number;
64
87
  identity: number[];
@@ -393,17 +416,26 @@ export declare class IfcApiProxyIfc implements IfcApiModelPassthrough {
393
416
  remaining: number;
394
417
  };
395
418
  /**
396
- * Walk the scene and emit FlatMeshes for the given entities only —
397
- * the filtered core of streamAllMeshes with identical placed-geometry
398
- * math. Also fixes a latent multi-call bug: the derived coordination
399
- * matrix is stored back to the model tuple, so later batches place
419
+ * Walk the scene and emit every not-yet-captured placed instance as
420
+ * per-entity DELTA FlatMeshes the incremental core of
421
+ * streamAllMeshes with identical placed-geometry math, processed in
422
+ * walk order exactly once per instance (per-entity watermarks). An
423
+ * entity re-emits with only its NEW instances when shared/mapped
424
+ * geometry attributes more to it in later batches; consumers render
425
+ * deltas additively (the shared meshMap still accumulates each
426
+ * entity's FULL vector, so getFlatMesh stays whole-model correct).
427
+ *
428
+ * Also fixes a latent multi-call bug: the derived coordination
429
+ * matrix is remembered (demandCoordination_), so later batches place
400
430
  * with the SAME coordination the first batch established
401
- * (streamAllMeshes never needed this — it runs once).
431
+ * (streamAllMeshes never needed this — it runs once). It is NOT
432
+ * exposed through getCoordinationMatrix, which keeps the classic
433
+ * identity contract consumers stamp onto assembled models.
402
434
  *
403
- * @param entityFilter Entity localIDs whose meshes to emit.
404
- * @param meshCallback Receives one FlatMesh per matched entity.
435
+ * @param meshCallback Receives one delta FlatMesh per entity that
436
+ * gained instances this call.
405
437
  */
406
- private streamMeshesForEntities_;
438
+ private streamNewMeshes_;
407
439
  /**
408
440
  *
409
441
  * @param modelID
@@ -1 +1 @@
1
- {"version":3,"file":"ifc_api_proxy_ifc.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/ifc_api_proxy_ifc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ifc_api_proxy_ifc.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/ifc_api_proxy_ifc.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,9 @@ import { extractModelInfo } from "../../loaders/loading_utilities.js";
9
9
  import IfcStepParser from "../../ifc/ifc_step_parser.js";
10
10
  import ParsingBuffer from "../../parsing/parsing_buffer.js";
11
11
  import { BufferByteSource } from "../../step/parsing/byte_source.js";
12
- import { buildColumnarIndexStreamingAsync, } from "../../step/parsing/streaming_index_builder.js";
12
+ import { buildIndexStreamingAsync, } from "../../step/parsing/streaming_index_builder.js";
13
+ import { ColumnarIndexSink } from "../../step/parsing/columnar_index.js";
14
+ import { StreamedPreviewChannel } from "./streamed_preview_channel.js";
13
15
  import { ExtractResult } from "../../index.js";
14
16
  import { IfcGeometryExtraction } from "../../ifc/ifc_geometry_extraction.js";
15
17
  import { ParseResult } from "../../index.js";
@@ -19,6 +21,9 @@ import { shimIfcEntityMap, shimIfcEntityReverseMap } from "./shim_schema_mapping
19
21
  import { EntityTypesIfcCount } from "../../ifc/ifc4_gen/entity_types_ifc.gen.js";
20
22
  import { IfcProduct, IfcRoot } from "../../ifc/ifc4_gen/index.js";
21
23
  import { CanonicalMeshType } from "../../index.js";
24
+ // Batch size used when a whole-model consumer (streamAllMeshes) drains
25
+ // a deferred model's remaining products synchronously.
26
+ const DEFERRED_DRAIN_BATCH = 256;
22
27
  /* Moving-window size for the streamed columnar parse (matches the
23
28
  * ifc_stream_open default; the window bounds parse-time scratch, not
24
29
  * the source buffer, which the model keeps resident here). */
@@ -41,6 +46,15 @@ export class IfcApiProxyIfc {
41
46
  this.fs = undefined;
42
47
  /** Was this model opened without extraction (DEFER_GEOMETRY)? */
43
48
  this.deferredMode_ = false;
49
+ /**
50
+ * Deferred capture watermarks: entity localID -> how many of its
51
+ * placed instances (in scene-walk order) have been captured. Shared
52
+ * (mapped) geometry attributes instances to an entity from OTHER
53
+ * products' extractions, so an entity's instance set grows across
54
+ * batches - the watermark makes each instance captured exactly once,
55
+ * in the same order the classic single walk would process it.
56
+ */
57
+ this.demandCapturedCounts_ = new Map();
44
58
  /** Cursor into demandProducts_ — products before it are extracted. */
45
59
  this.demandCursor_ = 0;
46
60
  this._isCoordinated = false;
@@ -138,6 +152,15 @@ export class IfcApiProxyIfc {
138
152
  ];
139
153
  // save settings
140
154
  this.settings = settings;
155
+ // Deferred opens whose preview channel already established the
156
+ // coordination frame: adopt it, so the durable capture skips its own
157
+ // derivation and places exactly where the preview did. (Internal
158
+ // only — getCoordinationMatrix stays identity, see
159
+ // demandCoordination_.)
160
+ if (this.deferredMode_ && loadState.previewCoordinationMatrix !== void 0) {
161
+ this.demandCoordination_ = loadState.previewCoordinationMatrix;
162
+ this._isCoordinated = true;
163
+ }
141
164
  let FILE_NAME = stepHeader.headers.get("FILE_NAME");
142
165
  if (FILE_NAME !== void 0) {
143
166
  // strip start / end parenthesis
@@ -439,7 +462,21 @@ export class IfcApiProxyIfc {
439
462
  const parseTick = tracker !== void 0 ?
440
463
  (cursorBytes) => tracker.update(cursorBytes) : void 0;
441
464
  const parseStartTime = Date.now();
442
- const { columns, result } = await buildColumnarIndexStreamingAsync(new BufferByteSource(data), parser, STREAMED_PARSE_POOL_BYTES, void 0, parseTick);
465
+ // Inline twin of buildColumnarIndexStreamingAsync the sink is created
466
+ // here so the parse-time preview channel (slice A2) can watch it grow
467
+ // and snapshot prefix models between the parse's cooperative yields.
468
+ const sink = new ColumnarIndexSink();
469
+ const previewChannel = deferGeometry && settings?.ON_PREVIEW_MESH !== void 0 ?
470
+ new StreamedPreviewChannel(data, conwaywasm, sink, settings.COORDINATE_TO_ORIGIN === true, settings.ON_PREVIEW_MESH) : void 0;
471
+ previewChannel?.start();
472
+ let result;
473
+ try {
474
+ ({ result } = await buildIndexStreamingAsync(new BufferByteSource(data), parser, STREAMED_PARSE_POOL_BYTES, void 0, sink, parseTick));
475
+ }
476
+ finally {
477
+ previewChannel?.stop();
478
+ }
479
+ const columns = sink.finalize();
443
480
  const parseEndTime = Date.now();
444
481
  tracker?.endPhase(data.length);
445
482
  if (result !== ParseResult.COMPLETE) {
@@ -460,6 +497,10 @@ export class IfcApiProxyIfc {
460
497
  return {
461
498
  conwaywasm,
462
499
  deferred: true,
500
+ // Pin the durable pump's coordination to the preview channel's
501
+ // (derived from the same first instance with the same math), so
502
+ // preview payloads and durable meshes share one frame.
503
+ previewCoordinationMatrix: previewChannel?.coordinationMatrix,
463
504
  allTimeStart,
464
505
  stepHeader,
465
506
  model,
@@ -990,40 +1031,57 @@ export class IfcApiProxyIfc {
990
1031
  this.demandCursor_ = 0;
991
1032
  }
992
1033
  const end = Math.min(this.demandCursor_ + Math.max(batchSize, 1), this.demandProducts_.length);
993
- const batch = new Set();
1034
+ let extracted = 0;
994
1035
  for (; this.demandCursor_ < end; ++this.demandCursor_) {
995
1036
  const localID = this.demandProducts_[this.demandCursor_];
996
1037
  if (this.conwayGeometry_.extractProductGeometryByLocalID(localID)) {
997
- batch.add(localID);
1038
+ ++extracted;
998
1039
  }
999
1040
  }
1000
- if (meshCallback !== void 0 && batch.size > 0) {
1001
- this.streamMeshesForEntities_(batch, meshCallback);
1041
+ if (meshCallback !== void 0) {
1042
+ this.streamNewMeshes_(meshCallback);
1002
1043
  }
1003
1044
  return {
1004
- extracted: batch.size,
1045
+ extracted,
1005
1046
  remaining: this.demandProducts_.length - this.demandCursor_,
1006
1047
  };
1007
1048
  }
1008
1049
  /**
1009
- * Walk the scene and emit FlatMeshes for the given entities only —
1010
- * the filtered core of streamAllMeshes with identical placed-geometry
1011
- * math. Also fixes a latent multi-call bug: the derived coordination
1012
- * matrix is stored back to the model tuple, so later batches place
1050
+ * Walk the scene and emit every not-yet-captured placed instance as
1051
+ * per-entity DELTA FlatMeshes the incremental core of
1052
+ * streamAllMeshes with identical placed-geometry math, processed in
1053
+ * walk order exactly once per instance (per-entity watermarks). An
1054
+ * entity re-emits with only its NEW instances when shared/mapped
1055
+ * geometry attributes more to it in later batches; consumers render
1056
+ * deltas additively (the shared meshMap still accumulates each
1057
+ * entity's FULL vector, so getFlatMesh stays whole-model correct).
1058
+ *
1059
+ * Also fixes a latent multi-call bug: the derived coordination
1060
+ * matrix is remembered (demandCoordination_), so later batches place
1013
1061
  * with the SAME coordination the first batch established
1014
- * (streamAllMeshes never needed this — it runs once).
1062
+ * (streamAllMeshes never needed this — it runs once). It is NOT
1063
+ * exposed through getCoordinationMatrix, which keeps the classic
1064
+ * identity contract consumers stamp onto assembled models.
1015
1065
  *
1016
- * @param entityFilter Entity localIDs whose meshes to emit.
1017
- * @param meshCallback Receives one FlatMesh per matched entity.
1066
+ * @param meshCallback Receives one delta FlatMesh per entity that
1067
+ * gained instances this call.
1018
1068
  */
1019
- streamMeshesForEntities_(entityFilter, meshCallback) {
1069
+ streamNewMeshes_(meshCallback) {
1020
1070
  const [model, scene, meshMap, geometryMaterialTransformMap] = this.model;
1021
- let coordinationMatrix = this.model[5];
1022
- const emitted = new Set();
1071
+ let coordinationMatrix = this.demandCoordination_ ?? glmatrix.mat4.create();
1072
+ const seenThisPass = new Map();
1073
+ const deltas = new Map();
1023
1074
  // eslint-disable-next-line no-unused-vars
1024
1075
  for (const [_, nativeTransform, geometry, material, entity] of scene.walk()) {
1025
- if (entity?.localID === void 0 || !entityFilter.has(entity.localID) ||
1026
- entity.expressID === void 0) {
1076
+ if (entity?.localID === void 0 || entity.expressID === void 0) {
1077
+ continue;
1078
+ }
1079
+ // Per-entity walk position vs watermark: instances before the
1080
+ // watermark were captured in an earlier call (append-only walk
1081
+ // order makes the count a stable cursor).
1082
+ const walkIndex = seenThisPass.get(entity.localID) ?? 0;
1083
+ seenThisPass.set(entity.localID, walkIndex + 1);
1084
+ if (walkIndex < (this.demandCapturedCounts_.get(entity.localID) ?? 0)) {
1027
1085
  continue;
1028
1086
  }
1029
1087
  if (geometry.type !== CanonicalMeshType.BUFFER_GEOMETRY || geometry.temporary) {
@@ -1069,8 +1127,9 @@ export class IfcApiProxyIfc {
1069
1127
  glmatrix.mat4.scale(scaleMatrix, scaleMatrix, scaleVec);
1070
1128
  glmatrix.mat4.multiply(coordinationMatrix, this.NormalizeMat, coordinationMatrix);
1071
1129
  glmatrix.mat4.multiply(coordinationMatrix, scaleMatrix, coordinationMatrix);
1072
- // Persist for every later batch (and getCoordinationMatrix).
1073
- this.model[5] = coordinationMatrix;
1130
+ // Persist for every later batch (internal only — see
1131
+ // demandCoordination_'s identity-contract note).
1132
+ this.demandCoordination_ = coordinationMatrix;
1074
1133
  this._isCoordinated = true;
1075
1134
  }
1076
1135
  const newTransform = glmatrix.mat4.create();
@@ -1111,15 +1170,26 @@ export class IfcApiProxyIfc {
1111
1170
  }
1112
1171
  mesh[0].push(placed);
1113
1172
  mesh[1].geometries = mesh[0];
1114
- emitted.add(entity.expressID);
1173
+ this.demandCapturedCounts_.set(entity.localID, (this.demandCapturedCounts_.get(entity.localID) ?? 0) + 1);
1174
+ let delta = deltas.get(entity.expressID);
1175
+ if (delta === void 0) {
1176
+ delta = [];
1177
+ deltas.set(entity.expressID, delta);
1178
+ }
1179
+ delta.push(placed);
1115
1180
  }
1116
1181
  const vectorFlatMesh = this.model[4];
1117
- for (const expressID of emitted) {
1118
- const mesh = meshMap.get(expressID);
1119
- if (mesh !== void 0) {
1120
- vectorFlatMesh.push(mesh[1]);
1121
- meshCallback(mesh[1]);
1122
- }
1182
+ for (const [expressID, placedList] of deltas) {
1183
+ const placedVector = {
1184
+ get: (index) => placedList[index] ?? placedList[0],
1185
+ size: () => placedList.length,
1186
+ push: (parameter) => {
1187
+ placedList.push(parameter);
1188
+ },
1189
+ };
1190
+ const deltaMesh = { geometries: placedVector, expressID };
1191
+ vectorFlatMesh.push(deltaMesh);
1192
+ meshCallback(deltaMesh);
1123
1193
  }
1124
1194
  }
1125
1195
  /**
@@ -1128,6 +1198,23 @@ export class IfcApiProxyIfc {
1128
1198
  * @param meshCallback
1129
1199
  */
1130
1200
  streamAllMeshes(meshCallback) {
1201
+ // Deferred models: the delta capture has already populated (or will
1202
+ // populate) the shared meshMap — re-running the classic walk would
1203
+ // push every instance a second time. Pump any remainder to
1204
+ // completion and serve the accumulated full per-entity meshes.
1205
+ if (this.deferredMode_) {
1206
+ const noCallback = void 0;
1207
+ while (this.extractGeometryBatch(DEFERRED_DRAIN_BATCH, noCallback).remaining > 0) {
1208
+ // draining
1209
+ }
1210
+ this.streamNewMeshes_(() => { });
1211
+ const [, , meshMap, , vectorFlatMesh] = this.model;
1212
+ meshMap.forEach((mesh) => {
1213
+ vectorFlatMesh.push(mesh[1]);
1214
+ meshCallback(mesh[1]);
1215
+ });
1216
+ return;
1217
+ }
1131
1218
  const [model, scene, meshMap, geometryMaterialTransformMap, vectorFlatMesh] = this.model;
1132
1219
  let coordinationMatrix = this.model[5];
1133
1220
  // eslint-disable-next-line no-unused-vars
@@ -0,0 +1,130 @@
1
+ import { ConwayGeometry } from "../../index.js";
2
+ import EntityTypesIfc from "../../ifc/ifc4_gen/entity_types_ifc.gen.js";
3
+ import { ColumnarIndexSink } from "../../step/parsing/columnar_index.js";
4
+ import * as glmatrix from "gl-matrix";
5
+ /**
6
+ * One preview mesh instance, self-contained: geometry payload is COPIED out
7
+ * of the wasm heap at emission, so consumers can upload it directly and
8
+ * never touch the (still-loading) model. `vertexData` is interleaved
9
+ * position+normal, 6 floats per vertex, exactly the GetGeometry layout the
10
+ * classic FlatMesh path reads. For instances sharing geometry with an
11
+ * earlier emission (mapped items), `vertexData`/`indexData` are omitted and
12
+ * `geometryExpressID` identifies the earlier payload to reuse.
13
+ */
14
+ export interface PreviewMeshPayload {
15
+ expressID: number;
16
+ geometryExpressID: number;
17
+ color: {
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ w: number;
22
+ };
23
+ flatTransformation: number[];
24
+ vertexData?: Float32Array;
25
+ indexData?: Uint32Array;
26
+ }
27
+ /**
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.
34
+ *
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.
42
+ *
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.
46
+ *
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.
52
+ */
53
+ export declare class StreamedPreviewChannel {
54
+ private readonly data;
55
+ private readonly conwaywasm;
56
+ private readonly sink;
57
+ private readonly coordinateToOrigin;
58
+ private readonly onMesh;
59
+ private readonly maxProducts;
60
+ private readonly maxBytes;
61
+ private readonly firstGenerationMinRecords;
62
+ /** Coordination matrix pinned from the first captured instance, exactly
63
+ * the derivation the durable capture would perform — the proxy adopts it
64
+ * so preview and durable placements share one frame. */
65
+ coordinationMatrix?: glmatrix.mat4;
66
+ private stopped_;
67
+ private timer_?;
68
+ private emittedProducts_;
69
+ private emittedBytes_;
70
+ /** Ordinal cursor into the (append-stable) product list. */
71
+ private productOrdinal_;
72
+ /** Geometry expressIDs whose payload has been emitted (cross-generation
73
+ * dedup for mapped/shared geometry). */
74
+ private readonly emittedGeometry_;
75
+ private generation_?;
76
+ private lastSnapshotRecords_;
77
+ /**
78
+ * @param data The (fully resident) source buffer the parse is indexing.
79
+ * @param conwaywasm The shared geometry wasm wrapper (sequential use only
80
+ * — ticks run between parse yields, never concurrently with the durable
81
+ * extraction, which is created after the parse completes).
82
+ * @param sink The live columnar sink the streamed parse is filling.
83
+ * @param coordinateToOrigin The open's COORDINATE_TO_ORIGIN setting.
84
+ * @param onMesh Consumer callback for each preview payload.
85
+ * @param maxProducts Cap on products ever preview-extracted.
86
+ * @param maxBytes Cap on total payload bytes copied out.
87
+ * @param firstGenerationMinRecords Records required before the first
88
+ * snapshot (tests lower it for tiny fixtures).
89
+ */
90
+ constructor(data: Uint8Array, conwaywasm: ConwayGeometry, sink: ColumnarIndexSink<EntityTypesIfc>, coordinateToOrigin: boolean, onMesh: (mesh: PreviewMeshPayload) => void, maxProducts?: number, maxBytes?: number, firstGenerationMinRecords?: number);
91
+ /** Begin ticking (call just before awaiting the parse). */
92
+ start(): void;
93
+ /**
94
+ * Stop ticking (call once the parse settles, before finalize/fallback).
95
+ * Idempotent; no tick runs after this returns (ticks are synchronous and
96
+ * scheduled on the same event loop).
97
+ */
98
+ stop(): void;
99
+ /** True when a cap was hit and the channel retired itself early. */
100
+ get capped(): boolean;
101
+ private schedule_;
102
+ /**
103
+ * One pump tick: ensure a generation with pending products exists, then
104
+ * extract + capture under the time budget.
105
+ */
106
+ private tick_;
107
+ /**
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.
111
+ *
112
+ * @return {boolean} True when a generation with pending products exists.
113
+ */
114
+ private ensureGeneration_;
115
+ /**
116
+ * 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.
121
+ */
122
+ private captureNewInstances_;
123
+ /**
124
+ * Test seam: run generation building + extraction + capture synchronously
125
+ * until either every product currently in the sink is attempted or a cap
126
+ * is hit — what the timer-driven ticks do, without the timers.
127
+ */
128
+ drainForTest(): void;
129
+ }
130
+ //# sourceMappingURL=streamed_preview_channel.d.ts.map
@@ -0,0 +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"}