@bldrs-ai/conway 1.426.1283 → 1.427.1287
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.
- package/compiled/examples/browser-bundled.cjs +1 -1
- package/compiled/examples/cli-bundled.cjs +1 -1
- package/compiled/examples/cli-step-bundled.cjs +161 -21
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts +61 -0
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts.map +1 -1
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.js +173 -31
- package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts +6 -0
- package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts.map +1 -1
- package/compiled/src/AP214E3_2010/ap214_model_geometry.js +9 -0
- package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts +2 -0
- package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts.map +1 -0
- package/compiled/src/compat/web-ifc/ap214_streamed_open.test.js +150 -0
- package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.js +19 -0
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts +102 -0
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.js +338 -0
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -88,6 +88,7 @@ export class AP214GeometryExtraction {
|
|
|
88
88
|
* Statistics by the loader/proxies after extraction.
|
|
89
89
|
*/
|
|
90
90
|
this.geometryTypeCounts = new Map();
|
|
91
|
+
this.demandCursor_ = 0;
|
|
91
92
|
this.csgMemoization = !this.lowMemoryMode;
|
|
92
93
|
this.materials = model.materials;
|
|
93
94
|
this.scene = new AP214SceneBuilder(model, conwayModel, this.materials);
|
|
@@ -2902,16 +2903,27 @@ export class AP214GeometryExtraction {
|
|
|
2902
2903
|
* @return {[ExtractResult, AP214SceneBuilder]} - Enum indicating extraction result
|
|
2903
2904
|
* + Geometry array
|
|
2904
2905
|
*/
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2906
|
+
/**
|
|
2907
|
+
* Prepare per-unit demand extraction (STEP demand parity phase 2)
|
|
2908
|
+
* without executing any geometry work: builds the assembly tree and
|
|
2909
|
+
* thunks exactly like the whole-model walk, but the root executions
|
|
2910
|
+
* are captured as an ordered list of UNITS — a childless root is one
|
|
2911
|
+
* unit; a root with children is flattened one level (one unit per
|
|
2912
|
+
* immediate child, in order, then one for the root's own items), so
|
|
2913
|
+
* single-root assemblies still pump progressively per part. Executing
|
|
2914
|
+
* every unit in order then calling {@link finishDemandExtraction}
|
|
2915
|
+
* reproduces the whole-model walk exactly — the classic
|
|
2916
|
+
* {@link extractAP214GeometryData} runs through this same path.
|
|
2917
|
+
* Idempotent.
|
|
2918
|
+
*/
|
|
2919
|
+
// eslint-disable-next-line max-lines-per-function
|
|
2920
|
+
prepareDemandExtraction() {
|
|
2921
|
+
if (this.demandUnits_ !== void 0) {
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2912
2924
|
const model = this.model;
|
|
2913
2925
|
const treeMap = new Map();
|
|
2914
|
-
const makeThunk = (representation, owningElementLocalID, mappedTreeNode) => {
|
|
2926
|
+
const makeThunk = (representation, owningElementLocalID, mappedTreeNode, slice) => {
|
|
2915
2927
|
return (owningLocalID, parentTransform) => {
|
|
2916
2928
|
owningLocalID ??= owningElementLocalID;
|
|
2917
2929
|
const mappedItem = mappedTreeNode !== void 0 || parentTransform !== void 0;
|
|
@@ -2920,8 +2932,12 @@ export class AP214GeometryExtraction {
|
|
|
2920
2932
|
if (parentTransform !== void 0) {
|
|
2921
2933
|
this.scene.addTransform(representation.localID, parentTransform.getValues(), parentTransform, true);
|
|
2922
2934
|
}
|
|
2923
|
-
if (mappedTreeNode?.children !== void 0
|
|
2924
|
-
|
|
2935
|
+
if (mappedTreeNode?.children !== void 0 &&
|
|
2936
|
+
(slice === void 0 || slice.childStart !== slice.childEnd)) {
|
|
2937
|
+
const sliceChildren = slice !== void 0 ?
|
|
2938
|
+
mappedTreeNode.children.slice(slice.childStart, slice.childEnd) :
|
|
2939
|
+
mappedTreeNode.children;
|
|
2940
|
+
for (const [childLocalID, childOwningLocalID, childTransform] of sliceChildren) {
|
|
2925
2941
|
const mappedChild = treeMap.get(childLocalID);
|
|
2926
2942
|
const enterChildStackDepth = this.scene.stackLength;
|
|
2927
2943
|
const enterChildParent = this.scene.currentParent;
|
|
@@ -2971,7 +2987,8 @@ export class AP214GeometryExtraction {
|
|
|
2971
2987
|
}
|
|
2972
2988
|
}
|
|
2973
2989
|
}
|
|
2974
|
-
|
|
2990
|
+
const includeItems = slice?.includeItems ?? true;
|
|
2991
|
+
for (const item of includeItems ? representation.items : []) {
|
|
2975
2992
|
try {
|
|
2976
2993
|
if (item instanceof placement) {
|
|
2977
2994
|
this.extractPlacement(item, mappedItem);
|
|
@@ -3010,11 +3027,19 @@ export class AP214GeometryExtraction {
|
|
|
3010
3027
|
}
|
|
3011
3028
|
};
|
|
3012
3029
|
};
|
|
3013
|
-
|
|
3030
|
+
// Roots the whole-model walk would execute, in execution order —
|
|
3031
|
+
// expanded into units at the end of preparation (children arrays
|
|
3032
|
+
// are final only once every edge loop has run).
|
|
3033
|
+
const pendingRoots = [];
|
|
3034
|
+
{
|
|
3014
3035
|
this.scene.clearParentStack();
|
|
3015
3036
|
// 256 meg limit for memoization - smaller models get a big
|
|
3016
3037
|
// win from memoization, but much larger models it uses far too much heap.
|
|
3017
3038
|
const MEMOIZATION_THRESHOLD = 256 * 1024 * 1024;
|
|
3039
|
+
// Remembered for finishDemandExtraction / the classic wrapper's
|
|
3040
|
+
// finally — the pump spans many calls, so restoration cannot live
|
|
3041
|
+
// in a single method's finally anymore.
|
|
3042
|
+
this.demandMemoizationRestore_ = this.model.elementMemoization;
|
|
3018
3043
|
if (this.lowMemoryMode || model.bufferBytesize > MEMOIZATION_THRESHOLD) {
|
|
3019
3044
|
this.model.elementMemoization = false;
|
|
3020
3045
|
}
|
|
@@ -3089,6 +3114,8 @@ export class AP214GeometryExtraction {
|
|
|
3089
3114
|
sourceNode = { parents: 1 };
|
|
3090
3115
|
treeMap.set(sourceID, sourceNode);
|
|
3091
3116
|
sourceNode.thunk = makeThunk(sourceShape, owningLocalID, sourceNode);
|
|
3117
|
+
sourceNode.rep = sourceShape;
|
|
3118
|
+
sourceNode.owningLocalID = owningLocalID;
|
|
3092
3119
|
}
|
|
3093
3120
|
else {
|
|
3094
3121
|
sourceNode.parents ??= 0;
|
|
@@ -3135,6 +3162,8 @@ export class AP214GeometryExtraction {
|
|
|
3135
3162
|
++sourceNode.parents;
|
|
3136
3163
|
}
|
|
3137
3164
|
sourceNode.thunk = makeThunk(sourceShape, owningLocalID, sourceNode);
|
|
3165
|
+
sourceNode.rep = sourceShape;
|
|
3166
|
+
sourceNode.owningLocalID = owningLocalID;
|
|
3138
3167
|
let targetNode = treeMap.get(targetID);
|
|
3139
3168
|
if (targetNode === void 0) {
|
|
3140
3169
|
targetNode = { parents: 0, children: [[sourceID, owningLocalID, transform]] };
|
|
@@ -3166,6 +3195,8 @@ export class AP214GeometryExtraction {
|
|
|
3166
3195
|
const mappedTreeNode = treeNode;
|
|
3167
3196
|
const thunk = makeThunk(shapeRepresentation, owningElementLocalID, mappedTreeNode);
|
|
3168
3197
|
mappedTreeNode.thunk = thunk;
|
|
3198
|
+
mappedTreeNode.rep = shapeRepresentation;
|
|
3199
|
+
mappedTreeNode.owningLocalID = owningElementLocalID;
|
|
3169
3200
|
if (!hasMappedNode) {
|
|
3170
3201
|
mappedTreeNode.processed = true;
|
|
3171
3202
|
const sourceShapeContext = shapeRepresentation.context_of_items.findVariant(global_unit_assigned_context)?.units?.
|
|
@@ -3176,8 +3207,14 @@ export class AP214GeometryExtraction {
|
|
|
3176
3207
|
scaleTransform =
|
|
3177
3208
|
this.uniformScaleAffine(this.identity3DNativeMatrix, 1 / sourceUnitInM);
|
|
3178
3209
|
}
|
|
3179
|
-
// not an assembly mapped item
|
|
3180
|
-
|
|
3210
|
+
// not an assembly mapped item — capture as a pending root
|
|
3211
|
+
// instead of executing inline (these nodes are childless by
|
|
3212
|
+
// construction: they were absent from the tree before this
|
|
3213
|
+
// loop, and all edges were added by the earlier loops).
|
|
3214
|
+
pendingRoots.push({
|
|
3215
|
+
node: mappedTreeNode,
|
|
3216
|
+
scaleTransform,
|
|
3217
|
+
});
|
|
3181
3218
|
continue;
|
|
3182
3219
|
}
|
|
3183
3220
|
}
|
|
@@ -3195,12 +3232,14 @@ export class AP214GeometryExtraction {
|
|
|
3195
3232
|
const mappedTreeNode = treeNode;
|
|
3196
3233
|
const owningElementLocalID = shapeRepresentation.localID;
|
|
3197
3234
|
mappedTreeNode.thunk = makeThunk(shapeRepresentation, owningElementLocalID, mappedTreeNode);
|
|
3235
|
+
mappedTreeNode.rep = shapeRepresentation;
|
|
3236
|
+
mappedTreeNode.owningLocalID = owningElementLocalID;
|
|
3198
3237
|
}
|
|
3199
|
-
// All thunks are set
|
|
3200
|
-
//
|
|
3238
|
+
// All thunks are set — capture remaining assembly-tree roots as
|
|
3239
|
+
// pending roots in the exact order the whole-model walk executed
|
|
3240
|
+
// them.
|
|
3201
3241
|
for (const [sourceID, mappedNode] of treeMap.entries()) {
|
|
3202
3242
|
if ((mappedNode.parents ?? 0) === 0 && mappedNode.thunk !== void 0 && mappedNode.processed !== true) {
|
|
3203
|
-
//this.scene.clearParentStack()
|
|
3204
3243
|
const shapeRepresentation = this.model.getTypedElementByLocalID(sourceID, shape_representation);
|
|
3205
3244
|
const sourceShapeContext = shapeRepresentation.context_of_items.findVariant(global_unit_assigned_context)?.units?.
|
|
3206
3245
|
find(unit => unit.findVariant(length_unit))?.findVariant(length_unit);
|
|
@@ -3210,30 +3249,133 @@ export class AP214GeometryExtraction {
|
|
|
3210
3249
|
scaleTransform =
|
|
3211
3250
|
this.uniformScaleAffine(this.identity3DNativeMatrix, 1 / sourceUnitInM);
|
|
3212
3251
|
}
|
|
3213
|
-
|
|
3214
|
-
mappedNode.thunk(void 0, scaleTransform);
|
|
3252
|
+
pendingRoots.push({ node: mappedNode, scaleTransform });
|
|
3215
3253
|
}
|
|
3216
3254
|
}
|
|
3217
|
-
//
|
|
3218
|
-
//
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3255
|
+
// Expand pending roots into ordered execution units: childless
|
|
3256
|
+
// roots run their original thunk whole; roots with children are
|
|
3257
|
+
// flattened one level — one unit per immediate child (in order),
|
|
3258
|
+
// then one for the root's own representation items, matching the
|
|
3259
|
+
// thunk body's children-then-items order exactly.
|
|
3260
|
+
const units = [];
|
|
3261
|
+
for (const root of pendingRoots) {
|
|
3262
|
+
const node = root.node;
|
|
3263
|
+
const scaleTransform = root.scaleTransform;
|
|
3264
|
+
const childCount = node.children?.length ?? 0;
|
|
3265
|
+
if (childCount === 0 || node.rep === void 0) {
|
|
3266
|
+
units.push(() => node.thunk(void 0, scaleTransform));
|
|
3267
|
+
continue;
|
|
3268
|
+
}
|
|
3269
|
+
for (let child = 0; child < childCount; ++child) {
|
|
3270
|
+
const sliced = makeThunk(node.rep, node.owningLocalID, node, { childStart: child, childEnd: child + 1, includeItems: false });
|
|
3271
|
+
units.push(() => sliced(void 0, scaleTransform));
|
|
3272
|
+
}
|
|
3273
|
+
const itemsOnly = makeThunk(node.rep, node.owningLocalID, node, { childStart: 0, childEnd: 0, includeItems: true });
|
|
3274
|
+
units.push(() => itemsOnly(void 0, scaleTransform));
|
|
3275
|
+
}
|
|
3276
|
+
this.demandUnits_ = units;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Total demand units (see prepareDemandExtraction).
|
|
3281
|
+
*
|
|
3282
|
+
* @return {number} The unit count (0 before preparation).
|
|
3283
|
+
*/
|
|
3284
|
+
get demandUnitCount() {
|
|
3285
|
+
return this.demandUnits_?.length ?? 0;
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Units already executed by extractDemandUnitBatch.
|
|
3289
|
+
*
|
|
3290
|
+
* @return {number} The cursor.
|
|
3291
|
+
*/
|
|
3292
|
+
get demandUnitCursor() {
|
|
3293
|
+
return this.demandCursor_;
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Execute the next `count` demand units (STEP demand parity phase 2)
|
|
3297
|
+
* — the per-unit pump behind the shim's ExtractGeometryBatch for
|
|
3298
|
+
* AP214 models. Staged face tessellation is finalized after every
|
|
3299
|
+
* batch so captured geometry is complete and readable.
|
|
3300
|
+
*
|
|
3301
|
+
* @param count Max units to execute this call (min 1).
|
|
3302
|
+
* @return {number} Units actually executed.
|
|
3303
|
+
*/
|
|
3304
|
+
extractDemandUnitBatch(count) {
|
|
3305
|
+
const units = this.demandUnits_;
|
|
3306
|
+
if (units === void 0) {
|
|
3307
|
+
return 0;
|
|
3308
|
+
}
|
|
3309
|
+
const end = Math.min(this.demandCursor_ + Math.max(count, 1), units.length);
|
|
3310
|
+
let executed = 0;
|
|
3311
|
+
for (; this.demandCursor_ < end; ++this.demandCursor_) {
|
|
3312
|
+
try {
|
|
3313
|
+
units[this.demandCursor_]();
|
|
3314
|
+
++executed;
|
|
3222
3315
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3316
|
+
catch (ex) {
|
|
3317
|
+
if (ex instanceof Error) {
|
|
3318
|
+
Logger.error(`Error processing demand unit: \n\t${ex.name}\n\t${ex.message}`);
|
|
3319
|
+
}
|
|
3320
|
+
else {
|
|
3321
|
+
Logger.error(`Unknown exception processing demand unit (${ex})`);
|
|
3322
|
+
}
|
|
3228
3323
|
}
|
|
3229
|
-
|
|
3324
|
+
}
|
|
3325
|
+
// Deferred face tessellation must land before geometry is read.
|
|
3326
|
+
this.finalizeStagedFaces();
|
|
3327
|
+
return executed;
|
|
3328
|
+
}
|
|
3329
|
+
/**
|
|
3330
|
+
* Complete a demand extraction after every unit has run: final staged
|
|
3331
|
+
* tessellation, temporaries cleanup, point-buffer release and
|
|
3332
|
+
* memoization restore — the tail the whole-model walk runs.
|
|
3333
|
+
*
|
|
3334
|
+
* @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
|
|
3335
|
+
* The completed extraction result.
|
|
3336
|
+
*/
|
|
3337
|
+
finishDemandExtraction() {
|
|
3338
|
+
this.finalizeStagedFaces();
|
|
3339
|
+
if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
|
|
3340
|
+
this.model.geometry.deleteTemporaries();
|
|
3341
|
+
}
|
|
3342
|
+
if (this.pointBuffer?.pointer) {
|
|
3343
|
+
this.wasmModule._free(this.pointBuffer.pointer);
|
|
3344
|
+
this.pointBuffer = null;
|
|
3345
|
+
}
|
|
3346
|
+
if (this.demandMemoizationRestore_ !== void 0) {
|
|
3347
|
+
this.model.elementMemoization = this.demandMemoizationRestore_;
|
|
3348
|
+
this.demandMemoizationRestore_ = void 0;
|
|
3349
|
+
}
|
|
3350
|
+
return [ExtractResult.COMPLETE, this.scene, this.productShapeMap];
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Whole-model extraction: prepare + run every demand unit + finish —
|
|
3354
|
+
* a single code path with the per-unit pump, so pumped and classic
|
|
3355
|
+
* extractions are identical by construction.
|
|
3356
|
+
*
|
|
3357
|
+
* @param logTime Unused (kept for call compatibility).
|
|
3358
|
+
* @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
|
|
3359
|
+
* The completed extraction result.
|
|
3360
|
+
*/
|
|
3361
|
+
// eslint-disable-next-line no-unused-vars
|
|
3362
|
+
extractAP214GeometryData(logTime = false) {
|
|
3363
|
+
try {
|
|
3364
|
+
this.prepareDemandExtraction();
|
|
3365
|
+
while (this.demandCursor_ < this.demandUnitCount) {
|
|
3366
|
+
this.extractDemandUnitBatch(this.demandUnitCount);
|
|
3367
|
+
}
|
|
3368
|
+
return this.finishDemandExtraction();
|
|
3230
3369
|
}
|
|
3231
3370
|
finally {
|
|
3232
3371
|
// Ensure no staged face jobs outlive extraction (their target
|
|
3233
3372
|
// geometry may be freed once the model is invalidated), even if
|
|
3234
3373
|
// extraction exits through an exception.
|
|
3235
3374
|
this.finalizeStagedFaces();
|
|
3236
|
-
this.
|
|
3375
|
+
if (this.demandMemoizationRestore_ !== void 0) {
|
|
3376
|
+
this.model.elementMemoization = this.demandMemoizationRestore_;
|
|
3377
|
+
this.demandMemoizationRestore_ = void 0;
|
|
3378
|
+
}
|
|
3237
3379
|
}
|
|
3238
3380
|
}
|
|
3239
3381
|
/**
|
|
@@ -13,6 +13,12 @@ export declare class AP214ModelGeometry implements ModelGeometry {
|
|
|
13
13
|
/**
|
|
14
14
|
* Add a mesh to this geometry collection.
|
|
15
15
|
*
|
|
16
|
+
* Freezes the native float vertex mirror at add time, exactly like
|
|
17
|
+
* IfcModelGeometry.add (see its note): consumers read float vertices
|
|
18
|
+
* whose frame must match the scene transforms, which are authored
|
|
19
|
+
* against the geometry AS CREATED — later count-preserving native
|
|
20
|
+
* mutations (normalize's centering) must not shift the served frame.
|
|
21
|
+
*
|
|
16
22
|
* @param mesh
|
|
17
23
|
* @param parentLocalID
|
|
18
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ap214_model_geometry.d.ts","sourceRoot":"","sources":["../../../src/AP214E3_2010/ap214_model_geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAqB,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAG7C;;GAEG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IAEtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAE1D;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED
|
|
1
|
+
{"version":3,"file":"ap214_model_geometry.d.ts","sourceRoot":"","sources":["../../../src/AP214E3_2010/ap214_model_geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAqB,MAAM,wBAAwB,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAG7C;;GAEG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IAEtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAE1D;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;;;;;;;OAWG;IACI,GAAG,CAAE,IAAI,EAAE,aAAa,EAAE,aAAa,CAAC,EAAG,MAAM,GAAI,IAAI;IAsBhE;;;;OAIG;IACI,MAAM,CAAE,OAAO,EAAE,MAAM;IAe9B;;OAEG;IACI,iBAAiB,IAAI,IAAI;IAgBhC;;;;;OAKG;IACI,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAIlE;;;;OAIG;IACI,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI/D;;;OAGG;IACI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,aAAa,CAAC;IAI3D;;;OAGG;IACI,qBAAqB,IAAI,MAAM;CAoBvC"}
|
|
@@ -16,10 +16,19 @@ export class AP214ModelGeometry {
|
|
|
16
16
|
/**
|
|
17
17
|
* Add a mesh to this geometry collection.
|
|
18
18
|
*
|
|
19
|
+
* Freezes the native float vertex mirror at add time, exactly like
|
|
20
|
+
* IfcModelGeometry.add (see its note): consumers read float vertices
|
|
21
|
+
* whose frame must match the scene transforms, which are authored
|
|
22
|
+
* against the geometry AS CREATED — later count-preserving native
|
|
23
|
+
* mutations (normalize's centering) must not shift the served frame.
|
|
24
|
+
*
|
|
19
25
|
* @param mesh
|
|
20
26
|
* @param parentLocalID
|
|
21
27
|
*/
|
|
22
28
|
add(mesh, parentLocalID) {
|
|
29
|
+
if (mesh.type === CanonicalMeshType.BUFFER_GEOMETRY) {
|
|
30
|
+
mesh.geometry.GetVertexData();
|
|
31
|
+
}
|
|
23
32
|
this.meshes_.set(mesh.localID, mesh);
|
|
24
33
|
if (parentLocalID !== void 0) {
|
|
25
34
|
let children = this.children_.get(parentLocalID);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ap214_streamed_open.test.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/ap214_streamed_open.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// STEP demand parity phase 1: the streamed columnar open for AP214 must
|
|
3
|
+
// reproduce the classic open exactly — same entities, same placed
|
|
4
|
+
// instances and transforms, and byte-identical GetGeometry vertex
|
|
5
|
+
// content (the served float frame must match the scene transforms; see
|
|
6
|
+
// AP214ModelGeometry.add's mirror-freeze note).
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
9
|
+
import { IfcAPI } from "./ifc_api.js";
|
|
10
|
+
const SETTINGS = { COORDINATE_TO_ORIGIN: true, USE_FAST_BOOLS: true };
|
|
11
|
+
// Fixtures with real served geometry (as1-assembly is structure-only —
|
|
12
|
+
// zero meshes even classically; ap214-multibody-part's geometry ids
|
|
13
|
+
// serve no vertex data through GetGeometry on the classic path either).
|
|
14
|
+
const FIXTURES = [
|
|
15
|
+
"data/nema-23-76mm.step",
|
|
16
|
+
"data/a-gear-with-3-inch-diameter-and-20-curved-teeth.step",
|
|
17
|
+
];
|
|
18
|
+
let api;
|
|
19
|
+
/**
|
|
20
|
+
* Capture per-entity placed instances from a model.
|
|
21
|
+
*
|
|
22
|
+
* @param modelID The open model.
|
|
23
|
+
* @return {Map} expressID -> {geometryExpressID, flatTransformation}[].
|
|
24
|
+
*/
|
|
25
|
+
function capture(modelID) {
|
|
26
|
+
const instances = new Map();
|
|
27
|
+
api.StreamAllMeshes(modelID, (mesh) => {
|
|
28
|
+
const list = instances.get(mesh.expressID) ?? [];
|
|
29
|
+
for (let where = 0; where < mesh.geometries.size(); ++where) {
|
|
30
|
+
const placed = mesh.geometries.get(where);
|
|
31
|
+
list.push({
|
|
32
|
+
geometryExpressID: placed.geometryExpressID,
|
|
33
|
+
flatTransformation: [...placed.flatTransformation],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
instances.set(mesh.expressID, list);
|
|
37
|
+
});
|
|
38
|
+
return instances;
|
|
39
|
+
}
|
|
40
|
+
beforeAll(async () => {
|
|
41
|
+
api = new IfcAPI();
|
|
42
|
+
await api.Init();
|
|
43
|
+
}, 120000);
|
|
44
|
+
describe("OpenModelStreamed on AP214 STEP input", () => {
|
|
45
|
+
test.each(FIXTURES)("streamed open matches classic exactly: %s", async (fixture) => {
|
|
46
|
+
const data = new Uint8Array(fs.readFileSync(fixture));
|
|
47
|
+
const classicID = api.OpenModel(data, SETTINGS);
|
|
48
|
+
const classic = capture(classicID);
|
|
49
|
+
expect(classic.size).toBeGreaterThan(0);
|
|
50
|
+
const streamedID = await api.OpenModelStreamed(data, SETTINGS);
|
|
51
|
+
expect(streamedID).toBeGreaterThanOrEqual(0);
|
|
52
|
+
const streamed = capture(streamedID);
|
|
53
|
+
expect(streamed.size).toBe(classic.size);
|
|
54
|
+
for (const [expressID, classicList] of classic) {
|
|
55
|
+
const streamedList = streamed.get(expressID);
|
|
56
|
+
expect(streamedList).toBeDefined();
|
|
57
|
+
expect(streamedList.length).toBe(classicList.length);
|
|
58
|
+
for (let where = 0; where < classicList.length; ++where) {
|
|
59
|
+
expect(streamedList[where].geometryExpressID)
|
|
60
|
+
.toBe(classicList[where].geometryExpressID);
|
|
61
|
+
expect(streamedList[where].flatTransformation)
|
|
62
|
+
.toEqual(classicList[where].flatTransformation);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Vertex content: the streamed open's GetGeometry must serve
|
|
66
|
+
// byte-identical floats to classic for every placed geometry.
|
|
67
|
+
const geometryIDs = new Set();
|
|
68
|
+
for (const list of classic.values()) {
|
|
69
|
+
for (const placed of list) {
|
|
70
|
+
geometryIDs.add(placed.geometryExpressID);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let compared = 0;
|
|
74
|
+
for (const geometryID of geometryIDs) {
|
|
75
|
+
const classicGeometry = api.GetGeometry(classicID, geometryID);
|
|
76
|
+
const streamedGeometry = api.GetGeometry(streamedID, geometryID);
|
|
77
|
+
const classicSize = classicGeometry.GetVertexDataSize();
|
|
78
|
+
if (classicSize === 0) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
expect(streamedGeometry.GetVertexDataSize()).toBe(classicSize);
|
|
82
|
+
expect(api.GetVertexArray(streamedGeometry.GetVertexData(), classicSize))
|
|
83
|
+
.toEqual(api.GetVertexArray(classicGeometry.GetVertexData(), classicSize));
|
|
84
|
+
++compared;
|
|
85
|
+
}
|
|
86
|
+
expect(compared).toBeGreaterThan(0);
|
|
87
|
+
// No CloseModel here: closing destroys the shared wasm processor
|
|
88
|
+
// and poisons later opens (pre-existing multi-open quirk).
|
|
89
|
+
}, 240000);
|
|
90
|
+
test.each(FIXTURES)("deferred unit pump to completion matches classic exactly: %s", async (fixture) => {
|
|
91
|
+
const data = new Uint8Array(fs.readFileSync(fixture));
|
|
92
|
+
const classicID = api.OpenModel(data, SETTINGS);
|
|
93
|
+
const classic = capture(classicID);
|
|
94
|
+
expect(classic.size).toBeGreaterThan(0);
|
|
95
|
+
const deferredID = await api.OpenModelStreamed(data, { ...SETTINGS, DEFER_GEOMETRY: true });
|
|
96
|
+
expect(deferredID).toBeGreaterThanOrEqual(0);
|
|
97
|
+
// Pump in deliberately small unit batches, accumulating delta
|
|
98
|
+
// emissions additively per entity.
|
|
99
|
+
const pumped = new Map();
|
|
100
|
+
let rounds = 0;
|
|
101
|
+
for (;;) {
|
|
102
|
+
const { extracted, remaining } = api.ExtractGeometryBatch(deferredID, 2, (mesh) => {
|
|
103
|
+
const list = pumped.get(mesh.expressID) ?? [];
|
|
104
|
+
for (let where = 0; where < mesh.geometries.size(); ++where) {
|
|
105
|
+
const placed = mesh.geometries.get(where);
|
|
106
|
+
list.push({
|
|
107
|
+
geometryExpressID: placed.geometryExpressID,
|
|
108
|
+
flatTransformation: [...placed.flatTransformation],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
pumped.set(mesh.expressID, list);
|
|
112
|
+
});
|
|
113
|
+
++rounds;
|
|
114
|
+
if (remaining === 0 && extracted === 0) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
expect(rounds).toBeGreaterThan(1);
|
|
119
|
+
expect(pumped.size).toBe(classic.size);
|
|
120
|
+
for (const [expressID, classicList] of classic) {
|
|
121
|
+
const pumpedList = pumped.get(expressID);
|
|
122
|
+
expect(pumpedList).toBeDefined();
|
|
123
|
+
expect(pumpedList.length).toBe(classicList.length);
|
|
124
|
+
// Instance sets match (order may differ across unit batches):
|
|
125
|
+
// every classic instance has an exactly-matching pumped one.
|
|
126
|
+
const unmatched = pumpedList.map((entry) => entry);
|
|
127
|
+
for (const reference of classicList) {
|
|
128
|
+
const matchIndex = unmatched.findIndex((candidate) => candidate.geometryExpressID === reference.geometryExpressID &&
|
|
129
|
+
candidate.flatTransformation.every((value, where) => Math.abs(value - reference.flatTransformation[where]) < 1e-9));
|
|
130
|
+
expect(matchIndex).toBeGreaterThanOrEqual(0);
|
|
131
|
+
unmatched.splice(matchIndex, 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// No CloseModel: closing destroys the shared wasm processor and
|
|
135
|
+
// degrades later opens (pre-existing multi-open quirk).
|
|
136
|
+
}, 240000);
|
|
137
|
+
test("StreamAllMeshes on a deferred STEP model drains the pump and matches classic", async () => {
|
|
138
|
+
const data = new Uint8Array(fs.readFileSync(FIXTURES[0]));
|
|
139
|
+
const classicID = api.OpenModel(data, SETTINGS);
|
|
140
|
+
const classic = capture(classicID);
|
|
141
|
+
const deferredID = await api.OpenModelStreamed(data, { ...SETTINGS, DEFER_GEOMETRY: true });
|
|
142
|
+
// No pump calls at all — the whole-model consumer must still get
|
|
143
|
+
// the complete mesh set (the proxy drains internally).
|
|
144
|
+
const drained = capture(deferredID);
|
|
145
|
+
expect(drained.size).toBe(classic.size);
|
|
146
|
+
for (const [expressID, classicList] of classic) {
|
|
147
|
+
expect(drained.get(expressID)?.length).toBe(classicList.length);
|
|
148
|
+
}
|
|
149
|
+
}, 240000);
|
|
150
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ifc_api_model_passthrough_factory.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/ifc_api_model_passthrough_factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAKpE;;GAEG;AACH,qBAAa,6BAA6B;IAExC;;;;;;;OAOG;WACW,IAAI,CACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,GAAG,EACf,QAAQ,CAAC,EAAE,cAAc,GAAI,sBAAsB,GAAG,SAAS;IA2EnE;;;;;;;;;;;;;;OAcG;WACiB,YAAY,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,GAAG,EACf,QAAQ,CAAC,EAAE,cAAc,GAAI,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ifc_api_model_passthrough_factory.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/ifc_api_model_passthrough_factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAKpE;;GAEG;AACH,qBAAa,6BAA6B;IAExC;;;;;;;OAOG;WACW,IAAI,CACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,GAAG,EACf,QAAQ,CAAC,EAAE,cAAc,GAAI,sBAAsB,GAAG,SAAS;IA2EnE;;;;;;;;;;;;;;OAcG;WACiB,YAAY,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,GAAG,EACf,QAAQ,CAAC,EAAE,cAAc,GAAI,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;IAkD5E;;;;;;;;;;;;OAYG;WACiB,SAAS,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,GAAG,EACf,QAAQ,CAAC,EAAE,cAAc,GAAI,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC;CA+C7E"}
|
|
@@ -95,6 +95,25 @@ export class IfcApiModelPassthroughFactory {
|
|
|
95
95
|
`falling back to classic open: ${message}`);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
// STEP demand parity: AP214 (and the AP203/AP242 fall-throughs)
|
|
99
|
+
// get the streamed columnar open — index columnar from birth, no
|
|
100
|
+
// per-record object phase — and, with DEFER_GEOMETRY, the deferred
|
|
101
|
+
// assembly-tree unit pump (phase 2), so STEP models stream
|
|
102
|
+
// progressively through ExtractGeometryBatch just like IFC.
|
|
103
|
+
if (modelFormat === ModelFormatType.AP214 ||
|
|
104
|
+
modelFormat === ModelFormatType.AP203 ||
|
|
105
|
+
modelFormat === ModelFormatType.AP242) {
|
|
106
|
+
try {
|
|
107
|
+
return settings?.DEFER_GEOMETRY === true ?
|
|
108
|
+
await IfcApiProxyAP214.createDeferred(modelID, data, wasmModule, settings) :
|
|
109
|
+
await IfcApiProxyAP214.createStreamed(modelID, data, wasmModule, settings);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
113
|
+
Logger.warning(`Streamed STEP open failed for model ${modelID}, ` +
|
|
114
|
+
`falling back to classic open: ${message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
98
117
|
return IfcApiModelPassthroughFactory.fromAsync(modelID, data, wasmModule, settings);
|
|
99
118
|
}
|
|
100
119
|
/**
|
|
@@ -17,6 +17,8 @@ import { StepHeader } from "../../step/parsing/step_parser.js";
|
|
|
17
17
|
*/
|
|
18
18
|
interface Ap214ProxyLoadState {
|
|
19
19
|
conwaywasm: ConwayGeometry;
|
|
20
|
+
/** True when opened without extraction (createDeferred). */
|
|
21
|
+
deferred?: boolean;
|
|
20
22
|
allTimeStart: number;
|
|
21
23
|
stepHeader: StepHeader;
|
|
22
24
|
model: AP214StepModel;
|
|
@@ -47,6 +49,24 @@ export declare class IfcApiProxyAP214 implements IfcApiModelPassthrough {
|
|
|
47
49
|
glmatrix.mat4
|
|
48
50
|
];
|
|
49
51
|
conwaywasm: ConwayGeometry;
|
|
52
|
+
/** The extraction behind this model (drives the deferred unit pump). */
|
|
53
|
+
private conwayGeometry_;
|
|
54
|
+
/** Was this model opened without extraction (DEFER_GEOMETRY)? */
|
|
55
|
+
private deferredMode_;
|
|
56
|
+
/** Has finishDemandExtraction run (deferred models, once)? */
|
|
57
|
+
private demandFinished_;
|
|
58
|
+
/**
|
|
59
|
+
* Deferred capture watermarks: entity localID -> how many of its
|
|
60
|
+
* placed instances (scene-walk order) have been captured — mirrors
|
|
61
|
+
* the IFC proxy's delta capture; see its demandCapturedCounts_ note.
|
|
62
|
+
*/
|
|
63
|
+
private readonly demandCapturedCounts_;
|
|
64
|
+
/**
|
|
65
|
+
* Coordination matrix the deferred capture derived. Internal
|
|
66
|
+
* multi-call memory only — getCoordinationMatrix keeps the classic
|
|
67
|
+
* identity contract (see the IFC proxy's demandCoordination_ note).
|
|
68
|
+
*/
|
|
69
|
+
private demandCoordination_?;
|
|
50
70
|
_isCoordinated: boolean;
|
|
51
71
|
linearScalingFactor: number;
|
|
52
72
|
identity: number[];
|
|
@@ -119,6 +139,52 @@ export declare class IfcApiProxyAP214 implements IfcApiModelPassthrough {
|
|
|
119
139
|
* @return {Promise<IfcApiProxyAP214>} The constructed proxy.
|
|
120
140
|
*/
|
|
121
141
|
static createAsync(modelID: number, data: Uint8Array, wasmModule: any, settings?: Loadersettings): Promise<IfcApiProxyAP214>;
|
|
142
|
+
/**
|
|
143
|
+
* Streamed columnar construction (conway extension, used by
|
|
144
|
+
* OpenModelStreamed): the data parse runs through the streaming
|
|
145
|
+
* columnar indexer over a moving window instead of the per-record
|
|
146
|
+
* object parse, so the index is columnar from birth — the AP214 twin
|
|
147
|
+
* of IfcApiProxyIfc.createStreamed (STEP demand parity phase 1).
|
|
148
|
+
* Geometry extraction stays the classic whole-model thunk-tree walk;
|
|
149
|
+
* the deferred pump and preview channel follow in later phases.
|
|
150
|
+
* Throws when the streamed parse is anything but COMPLETE — the
|
|
151
|
+
* factory falls back to the classic open.
|
|
152
|
+
*
|
|
153
|
+
* @param modelID The model ID being opened.
|
|
154
|
+
* @param data The STEP data buffer.
|
|
155
|
+
* @param wasmModule The wasm module.
|
|
156
|
+
* @param settings Loader settings (ON_PROGRESS / ON_MODEL_INFO honored).
|
|
157
|
+
* @return {Promise<IfcApiProxyAP214>} The constructed proxy.
|
|
158
|
+
*/
|
|
159
|
+
static createStreamed(modelID: number, data: Uint8Array, wasmModule: any, settings?: Loadersettings): Promise<IfcApiProxyAP214>;
|
|
160
|
+
/**
|
|
161
|
+
* Deferred streamed construction (STEP demand parity phase 2): the
|
|
162
|
+
* streamed columnar parse runs, the assembly tree is prepared, but NO
|
|
163
|
+
* geometry units execute — the batch pump (extractGeometryBatch)
|
|
164
|
+
* extracts them progressively afterwards. The AP214 twin of
|
|
165
|
+
* IfcApiProxyIfc.createDeferred.
|
|
166
|
+
*
|
|
167
|
+
* @param modelID The model ID being opened.
|
|
168
|
+
* @param data The STEP data buffer.
|
|
169
|
+
* @param wasmModule The wasm module.
|
|
170
|
+
* @param settings Loader settings (ON_PROGRESS / ON_MODEL_INFO honored).
|
|
171
|
+
* @return {Promise<IfcApiProxyAP214>} The constructed proxy.
|
|
172
|
+
*/
|
|
173
|
+
static createDeferred(modelID: number, data: Uint8Array, wasmModule: any, settings?: Loadersettings): Promise<IfcApiProxyAP214>;
|
|
174
|
+
/**
|
|
175
|
+
* Streamed twin of parseAndExtractAsync: cooperative columnar index
|
|
176
|
+
* build (periodic event-loop yields, absolute byte-cursor progress),
|
|
177
|
+
* then the model adopts the columns directly — no per-record object
|
|
178
|
+
* phase. Mirrors IfcApiProxyIfc.parseColumnarAndExtractAsync minus
|
|
179
|
+
* the deferred branch.
|
|
180
|
+
*
|
|
181
|
+
* @param modelID The model ID being opened.
|
|
182
|
+
* @param data The STEP data buffer.
|
|
183
|
+
* @param conwaywasm The conway geometry wasm wrapper.
|
|
184
|
+
* @param settings Loader settings (ON_PROGRESS / ON_MODEL_INFO honored).
|
|
185
|
+
* @return {Promise<Ap214ProxyLoadState>} Everything the constructor tail needs.
|
|
186
|
+
*/
|
|
187
|
+
private static parseColumnarAndExtractAsync;
|
|
122
188
|
/**
|
|
123
189
|
* Log + record the header parse result on the model statistics.
|
|
124
190
|
*
|
|
@@ -280,6 +346,42 @@ export declare class IfcApiProxyAP214 implements IfcApiModelPassthrough {
|
|
|
280
346
|
* @param modelID
|
|
281
347
|
* @param meshCallback
|
|
282
348
|
*/
|
|
349
|
+
/**
|
|
350
|
+
* Deferred-mode unit pump (STEP demand parity phase 2; conway
|
|
351
|
+
* extension consumed through ExtractGeometryBatch): execute the next
|
|
352
|
+
* `batchSize` assembly-tree units and emit THIS BATCH's meshes as
|
|
353
|
+
* per-entity DELTA FlatMeshes — the AP214 twin of the IFC proxy's
|
|
354
|
+
* pump, same delta contract. On a fully-extracted model this is a
|
|
355
|
+
* no-op returning remaining 0.
|
|
356
|
+
*
|
|
357
|
+
* @param batchSize Max units to execute this call (min 1).
|
|
358
|
+
* @param meshCallback Receives each newly-captured delta mesh.
|
|
359
|
+
* @return {object} `{extracted, remaining}` — units executed this
|
|
360
|
+
* call and units still pending.
|
|
361
|
+
*/
|
|
362
|
+
extractGeometryBatch(batchSize: number, meshCallback?: (mesh: FlatMesh) => void): {
|
|
363
|
+
extracted: number;
|
|
364
|
+
remaining: number;
|
|
365
|
+
};
|
|
366
|
+
/**
|
|
367
|
+
* Walk the scene and emit every not-yet-captured placed instance as
|
|
368
|
+
* per-entity DELTA FlatMeshes — the incremental core of
|
|
369
|
+
* streamAllMeshes with identical placed-geometry math (bare
|
|
370
|
+
* coordination x nativeTransform composition, occurrence paths),
|
|
371
|
+
* processed in walk order exactly once per instance. The shared
|
|
372
|
+
* meshMap accumulates each entity's FULL vector so getFlatMesh stays
|
|
373
|
+
* whole-model correct; the derived coordination matrix is remembered
|
|
374
|
+
* across calls (demandCoordination_) without leaking through
|
|
375
|
+
* getCoordinationMatrix (classic identity contract).
|
|
376
|
+
*
|
|
377
|
+
* @param meshCallback Receives one delta FlatMesh per entity that
|
|
378
|
+
* gained instances this call.
|
|
379
|
+
*/
|
|
380
|
+
private streamNewMeshes_;
|
|
381
|
+
/**
|
|
382
|
+
*
|
|
383
|
+
* @param meshCallback
|
|
384
|
+
*/
|
|
283
385
|
streamAllMeshes(meshCallback: (mesh: FlatMesh) => void): void;
|
|
284
386
|
/**
|
|
285
387
|
*
|