@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.
- package/compiled/examples/browser-bundled.cjs +1 -1
- package/compiled/examples/cli-bundled.cjs +1 -1
- package/compiled/examples/cli-step-bundled.cjs +175 -21
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts +69 -0
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts.map +1 -1
- package/compiled/src/AP214E3_2010/ap214_geometry_extraction.js +187 -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 +190 -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_preview_channel.test.js +2 -2
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts +105 -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 +360 -0
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +2 -2
- package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +111 -38
- package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/streamed_preview_channel.js +176 -106
- 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,147 @@ 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
|
+
* Advance the unit cursor WITHOUT executing (parse-time preview
|
|
3297
|
+
* generations resume a global ordinal on a fresh prefix extraction —
|
|
3298
|
+
* earlier units were already emitted from earlier generations).
|
|
3299
|
+
*
|
|
3300
|
+
* @param count Units to skip.
|
|
3301
|
+
*/
|
|
3302
|
+
skipDemandUnits(count) {
|
|
3303
|
+
const units = this.demandUnits_;
|
|
3304
|
+
if (units === void 0) {
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
this.demandCursor_ = Math.min(this.demandCursor_ + Math.max(count, 0), units.length);
|
|
3308
|
+
}
|
|
3309
|
+
/**
|
|
3310
|
+
* Execute the next `count` demand units (STEP demand parity phase 2)
|
|
3311
|
+
* — the per-unit pump behind the shim's ExtractGeometryBatch for
|
|
3312
|
+
* AP214 models. Staged face tessellation is finalized after every
|
|
3313
|
+
* batch so captured geometry is complete and readable.
|
|
3314
|
+
*
|
|
3315
|
+
* @param count Max units to execute this call (min 1).
|
|
3316
|
+
* @return {number} Units actually executed.
|
|
3317
|
+
*/
|
|
3318
|
+
extractDemandUnitBatch(count) {
|
|
3319
|
+
const units = this.demandUnits_;
|
|
3320
|
+
if (units === void 0) {
|
|
3321
|
+
return 0;
|
|
3322
|
+
}
|
|
3323
|
+
const end = Math.min(this.demandCursor_ + Math.max(count, 1), units.length);
|
|
3324
|
+
let executed = 0;
|
|
3325
|
+
for (; this.demandCursor_ < end; ++this.demandCursor_) {
|
|
3326
|
+
try {
|
|
3327
|
+
units[this.demandCursor_]();
|
|
3328
|
+
++executed;
|
|
3222
3329
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3330
|
+
catch (ex) {
|
|
3331
|
+
if (ex instanceof Error) {
|
|
3332
|
+
Logger.error(`Error processing demand unit: \n\t${ex.name}\n\t${ex.message}`);
|
|
3333
|
+
}
|
|
3334
|
+
else {
|
|
3335
|
+
Logger.error(`Unknown exception processing demand unit (${ex})`);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
// Deferred face tessellation must land before geometry is read.
|
|
3340
|
+
this.finalizeStagedFaces();
|
|
3341
|
+
return executed;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Complete a demand extraction after every unit has run: final staged
|
|
3345
|
+
* tessellation, temporaries cleanup, point-buffer release and
|
|
3346
|
+
* memoization restore — the tail the whole-model walk runs.
|
|
3347
|
+
*
|
|
3348
|
+
* @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
|
|
3349
|
+
* The completed extraction result.
|
|
3350
|
+
*/
|
|
3351
|
+
finishDemandExtraction() {
|
|
3352
|
+
this.finalizeStagedFaces();
|
|
3353
|
+
if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
|
|
3354
|
+
this.model.geometry.deleteTemporaries();
|
|
3355
|
+
}
|
|
3356
|
+
if (this.pointBuffer?.pointer) {
|
|
3357
|
+
this.wasmModule._free(this.pointBuffer.pointer);
|
|
3358
|
+
this.pointBuffer = null;
|
|
3359
|
+
}
|
|
3360
|
+
if (this.demandMemoizationRestore_ !== void 0) {
|
|
3361
|
+
this.model.elementMemoization = this.demandMemoizationRestore_;
|
|
3362
|
+
this.demandMemoizationRestore_ = void 0;
|
|
3363
|
+
}
|
|
3364
|
+
return [ExtractResult.COMPLETE, this.scene, this.productShapeMap];
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Whole-model extraction: prepare + run every demand unit + finish —
|
|
3368
|
+
* a single code path with the per-unit pump, so pumped and classic
|
|
3369
|
+
* extractions are identical by construction.
|
|
3370
|
+
*
|
|
3371
|
+
* @param logTime Unused (kept for call compatibility).
|
|
3372
|
+
* @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
|
|
3373
|
+
* The completed extraction result.
|
|
3374
|
+
*/
|
|
3375
|
+
// eslint-disable-next-line no-unused-vars
|
|
3376
|
+
extractAP214GeometryData(logTime = false) {
|
|
3377
|
+
try {
|
|
3378
|
+
this.prepareDemandExtraction();
|
|
3379
|
+
while (this.demandCursor_ < this.demandUnitCount) {
|
|
3380
|
+
this.extractDemandUnitBatch(this.demandUnitCount);
|
|
3228
3381
|
}
|
|
3229
|
-
return
|
|
3382
|
+
return this.finishDemandExtraction();
|
|
3230
3383
|
}
|
|
3231
3384
|
finally {
|
|
3232
3385
|
// Ensure no staged face jobs outlive extraction (their target
|
|
3233
3386
|
// geometry may be freed once the model is invalidated), even if
|
|
3234
3387
|
// extraction exits through an exception.
|
|
3235
3388
|
this.finalizeStagedFaces();
|
|
3236
|
-
this.
|
|
3389
|
+
if (this.demandMemoizationRestore_ !== void 0) {
|
|
3390
|
+
this.model.elementMemoization = this.demandMemoizationRestore_;
|
|
3391
|
+
this.demandMemoizationRestore_ = void 0;
|
|
3392
|
+
}
|
|
3237
3393
|
}
|
|
3238
3394
|
}
|
|
3239
3395
|
/**
|
|
@@ -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,190 @@
|
|
|
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("ON_PREVIEW_MESH emits classic-parity payloads for STEP (phase 3)", async () => {
|
|
138
|
+
// The gear parses in one cooperative slice, so the timer-driven
|
|
139
|
+
// channel never fires during a real open here — drive the channel
|
|
140
|
+
// directly over a finished sink (prefix == whole file), like the
|
|
141
|
+
// IFC channel test: a full drain must reproduce the classic
|
|
142
|
+
// instance set with the AP214 capture math (bare composition).
|
|
143
|
+
const fs2 = await import("fs");
|
|
144
|
+
const { ConwayGeometry } = await import("../../../dependencies/conway-geom/index.js");
|
|
145
|
+
const { buildIndexStreaming } = await import("../../step/parsing/streaming_index_builder.js");
|
|
146
|
+
const { ColumnarIndexSink } = await import("../../step/parsing/columnar_index.js");
|
|
147
|
+
const { BufferByteSource } = await import("../../step/parsing/byte_source.js");
|
|
148
|
+
const AP214StepParser = (await import("../../AP214E3_2010/ap214_step_parser.js")).default;
|
|
149
|
+
const { ap214PreviewAdapter, StreamedPreviewChannel } = await import("./streamed_preview_channel.js");
|
|
150
|
+
const data = new Uint8Array(fs2.readFileSync(FIXTURES[1]));
|
|
151
|
+
const classicID = api.OpenModel(data, SETTINGS);
|
|
152
|
+
const classic = capture(classicID);
|
|
153
|
+
let classicTotal = 0;
|
|
154
|
+
for (const list of classic.values()) {
|
|
155
|
+
classicTotal += list.length;
|
|
156
|
+
}
|
|
157
|
+
expect(classicTotal).toBeGreaterThan(0);
|
|
158
|
+
const conwayGeometry = new ConwayGeometry();
|
|
159
|
+
expect(await conwayGeometry.initialize()).toBe(true);
|
|
160
|
+
const sink = new ColumnarIndexSink();
|
|
161
|
+
const { result } = buildIndexStreaming(new BufferByteSource(data), AP214StepParser.Instance, 1024 * 1024, void 0, sink);
|
|
162
|
+
expect(result).toBe(0);
|
|
163
|
+
const payloads = [];
|
|
164
|
+
const channel = new StreamedPreviewChannel(data, conwayGeometry, sink, ap214PreviewAdapter(), true, (mesh) => payloads.push(mesh), void 0, void 0, 1);
|
|
165
|
+
channel.drainForTest();
|
|
166
|
+
expect(payloads.length).toBe(classicTotal);
|
|
167
|
+
for (const payload of payloads) {
|
|
168
|
+
const candidates = classic.get(payload.expressID);
|
|
169
|
+
expect(candidates).toBeDefined();
|
|
170
|
+
const matchIndex = candidates.findIndex((candidate) => candidate.geometryExpressID === payload.geometryExpressID &&
|
|
171
|
+
candidate.flatTransformation.every((value, where) => Math.abs(value - payload.flatTransformation[where]) < 0.000001));
|
|
172
|
+
expect(matchIndex).toBeGreaterThanOrEqual(0);
|
|
173
|
+
}
|
|
174
|
+
const carriers = payloads.filter((payload) => payload.vertexData !== void 0);
|
|
175
|
+
expect(carriers.length).toBeGreaterThan(0);
|
|
176
|
+
}, 240000);
|
|
177
|
+
test("StreamAllMeshes on a deferred STEP model drains the pump and matches classic", async () => {
|
|
178
|
+
const data = new Uint8Array(fs.readFileSync(FIXTURES[0]));
|
|
179
|
+
const classicID = api.OpenModel(data, SETTINGS);
|
|
180
|
+
const classic = capture(classicID);
|
|
181
|
+
const deferredID = await api.OpenModelStreamed(data, { ...SETTINGS, DEFER_GEOMETRY: true });
|
|
182
|
+
// No pump calls at all — the whole-model consumer must still get
|
|
183
|
+
// the complete mesh set (the proxy drains internally).
|
|
184
|
+
const drained = capture(deferredID);
|
|
185
|
+
expect(drained.size).toBe(classic.size);
|
|
186
|
+
for (const [expressID, classicList] of classic) {
|
|
187
|
+
expect(drained.get(expressID)?.length).toBe(classicList.length);
|
|
188
|
+
}
|
|
189
|
+
}, 240000);
|
|
190
|
+
});
|
|
@@ -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
|
/**
|
|
@@ -14,7 +14,7 @@ import { ColumnarIndexSink, } from "../../step/parsing/columnar_index.js";
|
|
|
14
14
|
import { ParseResult } from "../../step/parsing/step_parser.js";
|
|
15
15
|
import { buildIndexStreaming } from "../../step/parsing/streaming_index_builder.js";
|
|
16
16
|
import { IfcAPI } from "./ifc_api.js";
|
|
17
|
-
import { StreamedPreviewChannel, } from "./streamed_preview_channel.js";
|
|
17
|
+
import { ifcPreviewAdapter, StreamedPreviewChannel, } from "./streamed_preview_channel.js";
|
|
18
18
|
const SETTINGS = { COORDINATE_TO_ORIGIN: true, USE_FAST_BOOLS: true };
|
|
19
19
|
const POOL = 1024 * 1024;
|
|
20
20
|
let api;
|
|
@@ -115,7 +115,7 @@ describe("StreamedPreviewChannel", () => {
|
|
|
115
115
|
// drain must be a complete, classic-parity extraction.
|
|
116
116
|
const sink = buildSink();
|
|
117
117
|
const payloads = [];
|
|
118
|
-
const channel = new StreamedPreviewChannel(data, conwayGeometry, sink, true, (mesh) => payloads.push(mesh), void 0, void 0, 1);
|
|
118
|
+
const channel = new StreamedPreviewChannel(data, conwayGeometry, sink, ifcPreviewAdapter(), true, (mesh) => payloads.push(mesh), void 0, void 0, 1);
|
|
119
119
|
channel.drainForTest();
|
|
120
120
|
expect(payloads.length).toBe(classicTotal);
|
|
121
121
|
// Every payload matches a classic instance of the same entity and
|