@bldrs-ai/conway 1.543.1513 → 1.549.1515
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 +122 -16
- package/compiled/examples/cli-step-bundled.cjs +1 -1
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +33 -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 +80 -20
- package/compiled/src/ifc/ifc_geometry_extraction.d.ts +55 -0
- package/compiled/src/ifc/ifc_geometry_extraction.d.ts.map +1 -1
- package/compiled/src/ifc/ifc_geometry_extraction.js +170 -20
- package/compiled/src/ifc/ifc_rel_aggregate_extraction.test.d.ts +2 -0
- package/compiled/src/ifc/ifc_rel_aggregate_extraction.test.d.ts.map +1 -0
- package/compiled/src/ifc/ifc_rel_aggregate_extraction.test.js +115 -0
- package/compiled/src/scripts/bless_perf_snapshot.test.d.ts +2 -0
- package/compiled/src/scripts/bless_perf_snapshot.test.d.ts.map +1 -0
- package/compiled/src/scripts/bless_perf_snapshot.test.js +278 -0
- package/compiled/src/scripts/perf_csv_quoting.test.d.ts +2 -0
- package/compiled/src/scripts/perf_csv_quoting.test.d.ts.map +1 -0
- package/compiled/src/scripts/perf_csv_quoting.test.js +300 -0
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { CanonicalMeshType } from "../core/canonical_mesh.js";
|
|
|
8
8
|
import { yieldToEventLoop } from "../core/progress.js";
|
|
9
9
|
import { ObjectPool } from "../core/native_pool.js";
|
|
10
10
|
import { ExtractResult } from "../core/shared_constants.js";
|
|
11
|
-
import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcDirection, IfcExtrudedAreaSolid, IfcGrid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, IfcVirtualGridIntersection, } from "./ifc4_gen/index.js";
|
|
11
|
+
import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcDirection, IfcExtrudedAreaSolid, IfcGrid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcObjectDefinition, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, IfcVirtualGridIntersection, } from "./ifc4_gen/index.js";
|
|
12
12
|
import EntityTypesIfc from "./ifc4_gen/entity_types_ifc.gen.js";
|
|
13
13
|
import { IfcSceneBuilder } from "./ifc_scene_builder.js";
|
|
14
14
|
import Logger from "../logging/logger.js";
|
|
@@ -5462,10 +5462,28 @@ export class IfcGeometryExtraction {
|
|
|
5462
5462
|
if (relating !== null && relating !== void 0) {
|
|
5463
5463
|
await this.ensureResidentForProductExtract(relating.localID, pinned, leafSpans);
|
|
5464
5464
|
}
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5465
|
+
// By express ID rather than through `relAggregate.RelatedObjects`, for
|
|
5466
|
+
// the reason relatedObjectExpressIDs_ documents: the getter memoizes
|
|
5467
|
+
// the whole related-product array onto the relationship, and this
|
|
5468
|
+
// prefetch runs immediately before the extract that must not retain
|
|
5469
|
+
// it (conway#549 §4). Falls back to the getter for a list holding a
|
|
5470
|
+
// non-reference entry, so the two paths agree on what is in the list.
|
|
5471
|
+
const relatedExpressIDs = this.relatedObjectExpressIDs_(relAggregate);
|
|
5472
|
+
if (relatedExpressIDs === void 0) {
|
|
5473
|
+
const related = relAggregate.RelatedObjects;
|
|
5474
|
+
if (related !== null && related !== void 0) {
|
|
5475
|
+
for (const product of related) {
|
|
5476
|
+
await this.ensureResidentForProductExtract(product.localID, pinned, leafSpans);
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
else {
|
|
5481
|
+
for (const relatedExpressID of relatedExpressIDs) {
|
|
5482
|
+
const relatedLocalID = this.model.resolveExpressID(relatedExpressID);
|
|
5483
|
+
if (relatedLocalID === void 0) {
|
|
5484
|
+
continue;
|
|
5485
|
+
}
|
|
5486
|
+
await this.ensureResidentForProductExtract(relatedLocalID, pinned, leafSpans);
|
|
5469
5487
|
}
|
|
5470
5488
|
}
|
|
5471
5489
|
return pinned;
|
|
@@ -5494,6 +5512,46 @@ export class IfcGeometryExtraction {
|
|
|
5494
5512
|
this.extractRelAggregateGeometry(relAggregate);
|
|
5495
5513
|
}
|
|
5496
5514
|
}
|
|
5515
|
+
/**
|
|
5516
|
+
* Express IDs of one IfcRelAggregates' RelatedObjects, read straight off
|
|
5517
|
+
* the relationship's own record.
|
|
5518
|
+
*
|
|
5519
|
+
* Deliberately NOT `relAggregate.RelatedObjects`: that generated getter
|
|
5520
|
+
* memoizes the materialised entity array onto the relationship as
|
|
5521
|
+
* `RelatedObjects_` (IfcRelAggregates.gen.ts), so every related product
|
|
5522
|
+
* stays reachable for as long as the relationship does — and each product
|
|
5523
|
+
* in turn memoizes the tessellation subtree its own getters walk
|
|
5524
|
+
* (IfcPolyLoop.Polygon_, IfcCartesianPoint.Coordinates_, ...). The
|
|
5525
|
+
* per-product graphs therefore SUM instead of overlapping. On SKYLARK250,
|
|
5526
|
+
* whose 1,992 products are all aggregate targets, that is 2.7 GB of a
|
|
5527
|
+
* 5.4 GB peak (conway#549 §4). Resolving one product at a time from IDs
|
|
5528
|
+
* lets each graph die with its iteration, exactly as the ordinary product
|
|
5529
|
+
* loop's iterator already does.
|
|
5530
|
+
*
|
|
5531
|
+
* The scan mirrors the getter's own parse — `forEachReferenceInField`
|
|
5532
|
+
* runs the same optional/array-token walk over the same field
|
|
5533
|
+
* coordinates — so it sees the same entries in the same order.
|
|
5534
|
+
*
|
|
5535
|
+
* @param relAggregate The relationship to read.
|
|
5536
|
+
* @return {number[] | undefined} The related objects' express IDs in
|
|
5537
|
+
* record order, or undefined when the list holds an entry that is not a
|
|
5538
|
+
* `#N` reference — an inline entity, `$`, or junk. Only the generated
|
|
5539
|
+
* getter can resolve (or reject) those, so the caller falls back to it
|
|
5540
|
+
* rather than guessing.
|
|
5541
|
+
*/
|
|
5542
|
+
relatedObjectExpressIDs_(relAggregate) {
|
|
5543
|
+
const expressIDs = [];
|
|
5544
|
+
let allReferences = true;
|
|
5545
|
+
relAggregate.forEachReferenceInField(REL_AGGREGATES_RELATED_OFFSET, REL_AGGREGATES_RELATED_BASE, REL_AGGREGATES_RELATED_DEPTH, (expressID) => {
|
|
5546
|
+
if (expressID === void 0) {
|
|
5547
|
+
allReferences = false;
|
|
5548
|
+
return false;
|
|
5549
|
+
}
|
|
5550
|
+
expressIDs.push(expressID);
|
|
5551
|
+
return true;
|
|
5552
|
+
});
|
|
5553
|
+
return allReferences ? expressIDs : void 0;
|
|
5554
|
+
}
|
|
5497
5555
|
/**
|
|
5498
5556
|
* Extract one IfcRelAggregates' related products with the relating
|
|
5499
5557
|
* object's ("master") rel-voids — the shared per-item body of the
|
|
@@ -5505,23 +5563,87 @@ export class IfcGeometryExtraction {
|
|
|
5505
5563
|
* @param relAggregate The rel-aggregates relationship to process.
|
|
5506
5564
|
*/
|
|
5507
5565
|
extractRelAggregateGeometry(relAggregate) {
|
|
5566
|
+
const stepper = this.extractRelAggregateGeometryIncremental(relAggregate);
|
|
5567
|
+
let step = stepper.next();
|
|
5568
|
+
while (step.done !== true) {
|
|
5569
|
+
step = stepper.next();
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
/**
|
|
5573
|
+
* Incremental twin of {@link extractRelAggregateGeometry}: identical
|
|
5574
|
+
* extraction, suspending after each related product so a driver can
|
|
5575
|
+
* report progress and yield to the event loop *inside* the relationship.
|
|
5576
|
+
*
|
|
5577
|
+
* Without this the whole rel-aggregates pass is a single unyielding call.
|
|
5578
|
+
* On SKYLARK250 all 2,002 of the cooperative walk's suspension points
|
|
5579
|
+
* fire in 16 ms — every product is an aggregate target, so the product
|
|
5580
|
+
* loop skips them all — and the remaining ~60 s of real work runs inside
|
|
5581
|
+
* two `extractRelAggregateGeometry` calls with no suspension point at
|
|
5582
|
+
* all. That is the browser-tab hang in conway#549 §7, and it is
|
|
5583
|
+
* independent of the memory fix: a small model with one big aggregate
|
|
5584
|
+
* freezes the same way.
|
|
5585
|
+
*
|
|
5586
|
+
* Suspending here cannot show a caller a half-built product: the split is
|
|
5587
|
+
* BETWEEN products, and each product's scene content is complete before
|
|
5588
|
+
* the yield. Nor does it reorder emission — the products are visited in
|
|
5589
|
+
* record order either way. Aggregate targets are skipped by the product
|
|
5590
|
+
* pass (see aggregateTargetLocalIDs) and by the pump's product worklist,
|
|
5591
|
+
* so a consumer that streams meshes mid-relationship sees the
|
|
5592
|
+
* not-yet-reached products as absent rather than as uncut placeholders.
|
|
5593
|
+
*
|
|
5594
|
+
* @param relAggregate The rel-aggregates relationship to process.
|
|
5595
|
+
* @yields {number} The number of related products extracted so far from
|
|
5596
|
+
* THIS relationship, after each one.
|
|
5597
|
+
*/
|
|
5598
|
+
*extractRelAggregateGeometryIncremental(relAggregate) {
|
|
5599
|
+
// Held outside the try so the finally can free it on the abandonment
|
|
5600
|
+
// path too: a driver that stops consuming (stepper.return()) unwinds
|
|
5601
|
+
// through here, and the vector is native memory the GC cannot reclaim.
|
|
5602
|
+
let masterRelVoids;
|
|
5508
5603
|
try {
|
|
5509
5604
|
// Note, this is required because the relating object
|
|
5510
5605
|
// can be where the rel-void is applied, so we
|
|
5511
5606
|
// used this as a top level over-ride for a rel-void.
|
|
5512
5607
|
// for all objects in the aggregate - CS
|
|
5513
5608
|
const relatingObject = relAggregate.RelatingObject;
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5609
|
+
masterRelVoids =
|
|
5610
|
+
relatingObject instanceof IfcProduct ?
|
|
5611
|
+
this.extractRelVoids(relatingObject) :
|
|
5612
|
+
void 0;
|
|
5613
|
+
// Read before the first suspension point, deliberately: the scan
|
|
5614
|
+
// reads the relationship's own record bytes, and a windowed source
|
|
5615
|
+
// can page that record out while a later product is extracted.
|
|
5616
|
+
const relatedExpressIDs = this.relatedObjectExpressIDs_(relAggregate);
|
|
5617
|
+
let extractedCount = 0;
|
|
5618
|
+
if (relatedExpressIDs === void 0) {
|
|
5619
|
+
// Non-reference entry in the list — fall back to the generated
|
|
5620
|
+
// getter, the only thing that can resolve an inline entity (and the
|
|
5621
|
+
// only thing that decides an entry is unresolvable). Rare enough
|
|
5622
|
+
// that keeping its retention is the right trade against duplicating
|
|
5623
|
+
// inline resolution here.
|
|
5624
|
+
for (const productRepresentation of relAggregate.RelatedObjects) {
|
|
5625
|
+
if (productRepresentation instanceof IfcProduct) {
|
|
5626
|
+
this.extractProductGeometry(productRepresentation, masterRelVoids);
|
|
5627
|
+
yield ++extractedCount;
|
|
5628
|
+
}
|
|
5521
5629
|
}
|
|
5522
5630
|
}
|
|
5523
|
-
|
|
5524
|
-
|
|
5631
|
+
else {
|
|
5632
|
+
for (const relatedExpressID of relatedExpressIDs) {
|
|
5633
|
+
const relatedObject = this.model.getTypedElementByExpressID(relatedExpressID, IfcObjectDefinition);
|
|
5634
|
+
if (relatedObject === void 0) {
|
|
5635
|
+
// What the getter does with the same entry: extractBufferElement
|
|
5636
|
+
// returns undefined for a reference that resolves to nothing (or
|
|
5637
|
+
// to something that is not an IfcObjectDefinition) and the getter
|
|
5638
|
+
// throws this exact message. The catch below then skips the whole
|
|
5639
|
+
// relationship, permissively, as it always has.
|
|
5640
|
+
throw new Error("Value in STEP was incorrectly typed");
|
|
5641
|
+
}
|
|
5642
|
+
if (relatedObject instanceof IfcProduct) {
|
|
5643
|
+
this.extractProductGeometry(relatedObject, masterRelVoids);
|
|
5644
|
+
yield ++extractedCount;
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5525
5647
|
}
|
|
5526
5648
|
}
|
|
5527
5649
|
catch (ex) {
|
|
@@ -5537,6 +5659,11 @@ export class IfcGeometryExtraction {
|
|
|
5537
5659
|
Logger.error("Unknown exception processing IfcRelAssociateMaterials.");
|
|
5538
5660
|
}
|
|
5539
5661
|
}
|
|
5662
|
+
finally {
|
|
5663
|
+
if (masterRelVoids !== void 0) {
|
|
5664
|
+
masterRelVoids[0].delete();
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5540
5667
|
}
|
|
5541
5668
|
/**
|
|
5542
5669
|
* Extract one product's geometry into the scene: placement, material /
|
|
@@ -5781,16 +5908,24 @@ export class IfcGeometryExtraction {
|
|
|
5781
5908
|
}
|
|
5782
5909
|
this.prepareExtractionMaps_();
|
|
5783
5910
|
const products = this.model.types(IfcProduct);
|
|
5784
|
-
// Prefix-sum counts (no iteration/materialization) — see typeCount.
|
|
5785
|
-
// Slight over-count from multi-mapped elements just leaves the bar
|
|
5786
|
-
// shy of 100% until endPhase; fine for progress.
|
|
5787
|
-
const totalItems = this.model.typeCount(IfcProduct) + this.model.typeCount(IfcRelAggregates);
|
|
5788
|
-
let completedItems = 0;
|
|
5789
5911
|
// Products the aggregates pass re-extracts are SKIPPED here and
|
|
5790
5912
|
// extracted exactly once, below, with the master rel-voids — one
|
|
5791
5913
|
// scene instance per placement and no content replacement after
|
|
5792
5914
|
// emission (see aggregateTargetLocalIDs).
|
|
5793
5915
|
const aggregateTargets = this.aggregateTargetLocalIDs();
|
|
5916
|
+
// Prefix-sum counts (no iteration/materialization) — see typeCount.
|
|
5917
|
+
// Slight over-count from multi-mapped elements just leaves the bar
|
|
5918
|
+
// shy of 100% until endPhase; fine for progress.
|
|
5919
|
+
//
|
|
5920
|
+
// Aggregate targets are counted TWICE on purpose — once for the tick
|
|
5921
|
+
// the product loop spends skipping each of them, once for the tick
|
|
5922
|
+
// the rel-aggregates pass spends extracting it. On a model whose
|
|
5923
|
+
// products are all aggregate targets that is the difference between
|
|
5924
|
+
// a bar that reaches 100% in the first 16 ms and then sits there for
|
|
5925
|
+
// the entire extraction, and one that tracks the work.
|
|
5926
|
+
let totalItems = this.model.typeCount(IfcProduct) + this.model.typeCount(IfcRelAggregates) +
|
|
5927
|
+
aggregateTargets.size;
|
|
5928
|
+
let completedItems = 0;
|
|
5794
5929
|
for (const product of products) {
|
|
5795
5930
|
yield [++completedItems, totalItems];
|
|
5796
5931
|
if (aggregateTargets.has(product.localID)) {
|
|
@@ -5825,7 +5960,22 @@ export class IfcGeometryExtraction {
|
|
|
5825
5960
|
const relAggregates = this.model.types(IfcRelAggregates);
|
|
5826
5961
|
for (const relAggregate of relAggregates) {
|
|
5827
5962
|
yield [++completedItems, totalItems];
|
|
5828
|
-
|
|
5963
|
+
// Step the relationship rather than running it in one call: on an
|
|
5964
|
+
// aggregate-structured model this pass IS the extraction, and
|
|
5965
|
+
// without a suspension point inside it the cooperative driver
|
|
5966
|
+
// awaits nothing for the whole of it (conway#549 §7). for-of, not a
|
|
5967
|
+
// manual next() loop, so abandoning this generator closes the inner
|
|
5968
|
+
// one and frees its master rel-voids vector.
|
|
5969
|
+
const aggregateBase = completedItems;
|
|
5970
|
+
for (const extractedInAggregate of this.extractRelAggregateGeometryIncremental(relAggregate)) {
|
|
5971
|
+
completedItems = aggregateBase + extractedInAggregate;
|
|
5972
|
+
// The total counts one item per product and one per relationship,
|
|
5973
|
+
// so a product reached through more than one relationship can
|
|
5974
|
+
// push completed past it. Widen rather than let the bar exceed
|
|
5975
|
+
// 100% or go backwards.
|
|
5976
|
+
totalItems = Math.max(totalItems, completedItems);
|
|
5977
|
+
yield [completedItems, totalItems];
|
|
5978
|
+
}
|
|
5829
5979
|
}
|
|
5830
5980
|
if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
|
|
5831
5981
|
this.model.geometry.deleteTemporaries();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ifc_rel_aggregate_extraction.test.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_rel_aggregate_extraction.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
3
|
+
import { ConwayGeometry } from "../../dependencies/conway-geom/index.js";
|
|
4
|
+
import ParsingBuffer from "../parsing/parsing_buffer.js";
|
|
5
|
+
import { IfcGeometryExtraction } from "./ifc_geometry_extraction.js";
|
|
6
|
+
import IfcStepParser from "./ifc_step_parser.js";
|
|
7
|
+
import { IfcProduct, IfcRelAggregates } from "./ifc4_gen/index.js";
|
|
8
|
+
const conwayGeometry = new ConwayGeometry();
|
|
9
|
+
/** Wasm init plus two full extractions per test; CI runners are slow. */
|
|
10
|
+
// eslint-disable-next-line no-magic-numbers
|
|
11
|
+
const TEST_TIMEOUT_MS = 120 * 1000;
|
|
12
|
+
/** Fixtures that carry at least one IfcRelAggregates. */
|
|
13
|
+
const AGGREGATE_FIXTURES = [
|
|
14
|
+
"data/index.ifc",
|
|
15
|
+
"data/aggregate_master_voids.ifc",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Parse a fixture into a fresh model.
|
|
19
|
+
*
|
|
20
|
+
* @param fixturePath The IFC file to parse.
|
|
21
|
+
* @return {IfcStepModel} The parsed model.
|
|
22
|
+
*/
|
|
23
|
+
function parseFixtureModel(fixturePath) {
|
|
24
|
+
const parser = IfcStepParser.Instance;
|
|
25
|
+
const bufferInput = new ParsingBuffer(fs.readFileSync(fixturePath));
|
|
26
|
+
parser.parseHeader(bufferInput);
|
|
27
|
+
const model = parser.parseDataToModel(bufferInput)[1];
|
|
28
|
+
expect(model).toBeDefined();
|
|
29
|
+
return model;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Count the IfcProducts the rel-aggregates pass will extract — the related
|
|
33
|
+
* objects of every IfcRelAggregates, which is one extraction each.
|
|
34
|
+
*
|
|
35
|
+
* @param model The model to count over.
|
|
36
|
+
* @return {number} The number of related products.
|
|
37
|
+
*/
|
|
38
|
+
function relatedProductCount(model) {
|
|
39
|
+
let count = 0;
|
|
40
|
+
for (const relAggregate of model.types(IfcRelAggregates)) {
|
|
41
|
+
for (const relatedObject of relAggregate.RelatedObjects) {
|
|
42
|
+
if (relatedObject instanceof IfcProduct) {
|
|
43
|
+
++count;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
await conwayGeometry.initialize();
|
|
51
|
+
});
|
|
52
|
+
describe("rel-aggregates extraction (conway#549)", () => {
|
|
53
|
+
test("suspends once per related product, not once per relationship", () => {
|
|
54
|
+
// The tab-hang half of #549. The whole-model walk skips every aggregate
|
|
55
|
+
// target in its product loop and does the real work in the
|
|
56
|
+
// rel-aggregates pass, so a pass that suspends only between
|
|
57
|
+
// RELATIONSHIPS gives a cooperative driver nothing to yield on: on
|
|
58
|
+
// SKYLARK250 all 2,002 suspension points fire in 16 ms and the
|
|
59
|
+
// remaining ~60 s runs inside two calls with none at all.
|
|
60
|
+
//
|
|
61
|
+
// Counted rather than merely "more than before", so this fails on a
|
|
62
|
+
// pass that suspends per relationship AND on one that suspends at some
|
|
63
|
+
// other granularity.
|
|
64
|
+
for (const fixturePath of AGGREGATE_FIXTURES) {
|
|
65
|
+
const model = parseFixtureModel(fixturePath);
|
|
66
|
+
const products = model.typeCount(IfcProduct);
|
|
67
|
+
const relationships = model.typeCount(IfcRelAggregates);
|
|
68
|
+
const relatedProducts = relatedProductCount(model);
|
|
69
|
+
expect(relationships).toBeGreaterThan(0);
|
|
70
|
+
expect(relatedProducts).toBeGreaterThan(0);
|
|
71
|
+
const extraction = new IfcGeometryExtraction(conwayGeometry, model);
|
|
72
|
+
const ticks = [];
|
|
73
|
+
extraction.extractIFCGeometryData((completed, total) => ticks.push([completed, total]));
|
|
74
|
+
expect(ticks.length)
|
|
75
|
+
.toBe(products + relationships + relatedProducts);
|
|
76
|
+
// Still monotonic, still bounded by the total it reports.
|
|
77
|
+
for (let where = 1; where < ticks.length; ++where) {
|
|
78
|
+
expect(ticks[where][0]).toBeGreaterThan(ticks[where - 1][0]);
|
|
79
|
+
expect(ticks[where][0]).toBeLessThanOrEqual(ticks[where][1]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}, TEST_TIMEOUT_MS);
|
|
83
|
+
test("does not leave the related-product array memoized on the relationship", () => {
|
|
84
|
+
// The memory half of #549. `RelatedObjects` memoizes the
|
|
85
|
+
// materialised entity array onto the relationship as
|
|
86
|
+
// `RelatedObjects_`, and each of those products in turn memoizes the
|
|
87
|
+
// tessellation subtree its getters walk — so a pass that reads the
|
|
88
|
+
// getter keeps the whole model's face/loop/point graph alive for the
|
|
89
|
+
// length of the relationship instead of one product at a time. On
|
|
90
|
+
// SKYLARK250 that is 2.7 GB of a 5.4 GB peak.
|
|
91
|
+
//
|
|
92
|
+
// White-box on the generated field name deliberately: the retention
|
|
93
|
+
// IS the field, and there is no black-box way to observe reachability
|
|
94
|
+
// from a test. If codegen renames it, this test stops testing
|
|
95
|
+
// anything, so it also asserts the field exists to be set.
|
|
96
|
+
for (const fixturePath of AGGREGATE_FIXTURES) {
|
|
97
|
+
const model = parseFixtureModel(fixturePath);
|
|
98
|
+
const extraction = new IfcGeometryExtraction(conwayGeometry, model);
|
|
99
|
+
extraction.extractIFCGeometryData();
|
|
100
|
+
let checked = 0;
|
|
101
|
+
for (const relAggregate of model.types(IfcRelAggregates)) {
|
|
102
|
+
const memo = relAggregate.RelatedObjects_;
|
|
103
|
+
expect(memo).toBeUndefined();
|
|
104
|
+
// Reading the getter must still populate it — i.e. the field is
|
|
105
|
+
// the one the getter memoizes into, so the assertion above is
|
|
106
|
+
// meaningful rather than vacuous.
|
|
107
|
+
expect(relAggregate.RelatedObjects).toBeDefined();
|
|
108
|
+
expect(relAggregate.RelatedObjects_)
|
|
109
|
+
.toBeDefined();
|
|
110
|
+
++checked;
|
|
111
|
+
}
|
|
112
|
+
expect(checked).toBeGreaterThan(0);
|
|
113
|
+
}
|
|
114
|
+
}, TEST_TIMEOUT_MS);
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bless_perf_snapshot.test.d.ts","sourceRoot":"","sources":["../../../src/scripts/bless_perf_snapshot.test.ts"],"names":[],"mappings":""}
|