@mlightcad/cad-simple-viewer 1.5.9 → 1.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/cad-simple-viewer.js +8345 -7349
  2. package/dist/cad-simple-viewer.umd.cjs +37 -37
  3. package/dist/mtext-renderer-worker.js +2896 -2767
  4. package/lib/app/AcApDocManager.d.ts +17 -8
  5. package/lib/app/AcApDocManager.d.ts.map +1 -1
  6. package/lib/app/AcApDocManager.js +50 -13
  7. package/lib/app/AcApDocManager.js.map +1 -1
  8. package/lib/app/AcApFontLoader.d.ts +49 -26
  9. package/lib/app/AcApFontLoader.d.ts.map +1 -1
  10. package/lib/app/AcApFontLoader.js +245 -48
  11. package/lib/app/AcApFontLoader.js.map +1 -1
  12. package/lib/app/AcApOpenFileProfiler.d.ts +70 -17
  13. package/lib/app/AcApOpenFileProfiler.d.ts.map +1 -1
  14. package/lib/app/AcApOpenFileProfiler.js +96 -46
  15. package/lib/app/AcApOpenFileProfiler.js.map +1 -1
  16. package/lib/app/AcApOpenFileProgressController.d.ts +22 -5
  17. package/lib/app/AcApOpenFileProgressController.d.ts.map +1 -1
  18. package/lib/app/AcApOpenFileProgressController.js +76 -31
  19. package/lib/app/AcApOpenFileProgressController.js.map +1 -1
  20. package/lib/app/AcApProgress.d.ts +6 -0
  21. package/lib/app/AcApProgress.d.ts.map +1 -1
  22. package/lib/app/AcApProgress.js +11 -0
  23. package/lib/app/AcApProgress.js.map +1 -1
  24. package/lib/app/AcDbOpenDatabaseOptions.d.ts +15 -12
  25. package/lib/app/AcDbOpenDatabaseOptions.d.ts.map +1 -1
  26. package/lib/app/index.d.ts +1 -0
  27. package/lib/app/index.d.ts.map +1 -1
  28. package/lib/app/index.js +1 -0
  29. package/lib/app/index.js.map +1 -1
  30. package/lib/editor/view/AcEdSpatialQueryResult.d.ts +10 -0
  31. package/lib/editor/view/AcEdSpatialQueryResult.d.ts.map +1 -1
  32. package/lib/editor/view/AcEdSpatialQueryResult.js +36 -0
  33. package/lib/editor/view/AcEdSpatialQueryResult.js.map +1 -1
  34. package/lib/i18n/cs/command.d.ts +3 -0
  35. package/lib/i18n/cs/command.d.ts.map +1 -1
  36. package/lib/i18n/cs/command.js +3 -0
  37. package/lib/i18n/cs/command.js.map +1 -1
  38. package/lib/i18n/cs/main.d.ts +1 -0
  39. package/lib/i18n/cs/main.d.ts.map +1 -1
  40. package/lib/i18n/cs/main.js +1 -0
  41. package/lib/i18n/cs/main.js.map +1 -1
  42. package/lib/i18n/en/command.d.ts +3 -0
  43. package/lib/i18n/en/command.d.ts.map +1 -1
  44. package/lib/i18n/en/command.js +3 -0
  45. package/lib/i18n/en/command.js.map +1 -1
  46. package/lib/i18n/en/main.d.ts +1 -0
  47. package/lib/i18n/en/main.d.ts.map +1 -1
  48. package/lib/i18n/en/main.js +1 -0
  49. package/lib/i18n/en/main.js.map +1 -1
  50. package/lib/i18n/tr/command.d.ts +3 -0
  51. package/lib/i18n/tr/command.d.ts.map +1 -1
  52. package/lib/i18n/tr/command.js +3 -0
  53. package/lib/i18n/tr/command.js.map +1 -1
  54. package/lib/i18n/tr/main.d.ts +1 -0
  55. package/lib/i18n/tr/main.d.ts.map +1 -1
  56. package/lib/i18n/tr/main.js +1 -0
  57. package/lib/i18n/tr/main.js.map +1 -1
  58. package/lib/i18n/zh/command.d.ts +3 -0
  59. package/lib/i18n/zh/command.d.ts.map +1 -1
  60. package/lib/i18n/zh/command.js +3 -0
  61. package/lib/i18n/zh/command.js.map +1 -1
  62. package/lib/i18n/zh/main.d.ts +1 -0
  63. package/lib/i18n/zh/main.d.ts.map +1 -1
  64. package/lib/i18n/zh/main.js +1 -0
  65. package/lib/i18n/zh/main.js.map +1 -1
  66. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.d.ts.map +1 -1
  67. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.js +2 -2
  68. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.js.map +1 -1
  69. package/lib/spatialIndex/AcTrLinearSpatialIndex.d.ts +6 -1
  70. package/lib/spatialIndex/AcTrLinearSpatialIndex.d.ts.map +1 -1
  71. package/lib/spatialIndex/AcTrLinearSpatialIndex.js +68 -11
  72. package/lib/spatialIndex/AcTrLinearSpatialIndex.js.map +1 -1
  73. package/lib/spatialIndex/AcTrRBushSpatialIndex.d.ts.map +1 -1
  74. package/lib/spatialIndex/AcTrRBushSpatialIndex.js +58 -12
  75. package/lib/spatialIndex/AcTrRBushSpatialIndex.js.map +1 -1
  76. package/lib/view/AcTrDirectBatch.d.ts +18 -0
  77. package/lib/view/AcTrDirectBatch.d.ts.map +1 -0
  78. package/lib/view/AcTrDirectBatch.js +113 -0
  79. package/lib/view/AcTrDirectBatch.js.map +1 -0
  80. package/lib/view/AcTrLayer.d.ts +7 -1
  81. package/lib/view/AcTrLayer.d.ts.map +1 -1
  82. package/lib/view/AcTrLayer.js +29 -0
  83. package/lib/view/AcTrLayer.js.map +1 -1
  84. package/lib/view/AcTrLayout.d.ts +11 -1
  85. package/lib/view/AcTrLayout.d.ts.map +1 -1
  86. package/lib/view/AcTrLayout.js +43 -0
  87. package/lib/view/AcTrLayout.js.map +1 -1
  88. package/lib/view/AcTrProgressiveOpenFitController.d.ts +10 -6
  89. package/lib/view/AcTrProgressiveOpenFitController.d.ts.map +1 -1
  90. package/lib/view/AcTrProgressiveOpenFitController.js +35 -76
  91. package/lib/view/AcTrProgressiveOpenFitController.js.map +1 -1
  92. package/lib/view/AcTrScene.d.ts +7 -1
  93. package/lib/view/AcTrScene.d.ts.map +1 -1
  94. package/lib/view/AcTrScene.js +18 -0
  95. package/lib/view/AcTrScene.js.map +1 -1
  96. package/lib/view/AcTrView2d.d.ts +87 -12
  97. package/lib/view/AcTrView2d.d.ts.map +1 -1
  98. package/lib/view/AcTrView2d.js +501 -166
  99. package/lib/view/AcTrView2d.js.map +1 -1
  100. package/lib/view/index.d.ts +1 -0
  101. package/lib/view/index.d.ts.map +1 -1
  102. package/lib/view/index.js +1 -0
  103. package/lib/view/index.js.map +1 -1
  104. package/package.json +7 -7
@@ -96,9 +96,9 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
96
96
  }
97
97
  return to.concat(ar || Array.prototype.slice.call(from));
98
98
  };
99
- import { AcDbAttribute, AcDbBlockReference, acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, log } from '@mlightcad/data-model';
99
+ import { AcCmUiYieldGate, AcDbAttribute, AcDbBlockReference, acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, log } from '@mlightcad/data-model';
100
100
  import { AcDbSystemVariables } from '@mlightcad/data-model';
101
- import { AcTrEntity, AcTrGroup, AcTrRenderer, AcTrViewportView } from '@mlightcad/three-renderer';
101
+ import { AcTrEntity, AcTrGlyphEntity, AcTrGroup, AcTrRenderer, AcTrViewportView } from '@mlightcad/three-renderer';
102
102
  import { AcTrMatrixUtil } from '@mlightcad/three-renderer';
103
103
  import * as THREE from 'three';
104
104
  import Stats from 'three/examples/jsm/libs/stats.module';
@@ -110,6 +110,7 @@ import { isEffectiveSpatialQueryHit } from '../editor/view/AcEdSpatialQueryResul
110
110
  import { AcTrGeometryUtil } from '../util';
111
111
  import { acapRunDatabaseEdit } from '../util/AcApDatabaseEdit';
112
112
  import { AcEdViewKeyHandler } from './AcEdViewKeyHandler';
113
+ import { shouldExtendBboxForDirectEntity, tryBuildDirectEntityMeta } from './AcTrDirectBatch';
113
114
  import { AcTrEntityDisplayController } from './AcTrEntityDisplayController';
114
115
  import { assertAcTrGroupWcsBboxesConsistent, unionGroupWcsChildBoxes } from './AcTrGroupWcsBboxAssert';
115
116
  import { AcTrInheritedLayerMaterialMapper } from './AcTrInheritedLayerMaterialMapper';
@@ -190,7 +191,7 @@ var AcTrView2d = /** @class */ (function (_super) {
190
191
  * Block table record ids of layouts whose entities are currently being
191
192
  * batch-converted into the scene. Used by
192
193
  * {@link AcTrView2d.loadLayoutEntitiesIfNeeded} to guard against
193
- * re-entrant calls before the `setTimeout` callback flips
194
+ * re-entrant calls before the convert drain flips
194
195
  * `AcTrLayout.isLoaded` to `true`, which would otherwise duplicate
195
196
  * entities when the same layout tab is clicked twice in quick succession.
196
197
  */
@@ -236,10 +237,36 @@ var AcTrView2d = /** @class */ (function (_super) {
236
237
  */
237
238
  _this._convertingLayers = new Set();
238
239
  /**
239
- * When true, entity conversion during document open is deferred across
240
- * event-loop turns so geometry appears incrementally.
240
+ * When true, entity conversion during document open yields cooperatively so
241
+ * geometry paints incrementally while the open overlay is still visible.
241
242
  */
242
243
  _this._progressiveRendering = false;
244
+ /**
245
+ * Serial convert queue for progressive opens. Chunks from `entityAppended`
246
+ * enqueue here and a single drain loop runs {@link batchConvert}, so scene
247
+ * convert overlaps ENTITY flush instead of waiting for a post-open
248
+ * `setTimeout` storm after the loading overlay hides.
249
+ */
250
+ _this._convertQueue = [];
251
+ /** In-flight progressive convert drain; shared so awaiters wait for the queue. */
252
+ _this._convertDrainPromise = null;
253
+ /**
254
+ * Bumped by {@link clear} so an in-flight {@link batchConvert} abandons
255
+ * scene writes and counter updates after the view has been reset for a new
256
+ * document open.
257
+ */
258
+ _this._convertEpoch = 0;
259
+ /** Last time progressive open marked the canvas dirty for paint. */
260
+ _this._lastProgressivePaintAt = 0;
261
+ /** Mid-open WebGL paints while progressive convert was still running. */
262
+ _this._progressivePaintCount = 0;
263
+ /** Cooperative yields taken inside progressive {@link batchConvert}. */
264
+ _this._progressiveYieldCount = 0;
265
+ /**
266
+ * In-flight glyph/group geometry jobs that await fonts via asyncDraw.
267
+ * Counted separately so linework convert can continue while text waits.
268
+ */
269
+ _this._pendingGeometryJobs = 0;
243
270
  _this.animate = function () {
244
271
  var _a;
245
272
  _this._rafId = requestAnimationFrame(_this.animate);
@@ -253,12 +280,20 @@ var AcTrView2d = /** @class */ (function (_super) {
253
280
  return;
254
281
  if (deferRenderWhileLoading)
255
282
  return;
283
+ if (!_this._isDirty)
284
+ return;
285
+ if (_this._progressiveRendering && stillLoading) {
286
+ _this._progressivePaintCount++;
287
+ }
256
288
  var needsRedraw = _this._layoutViewManager.render(_this._scene);
257
289
  if (_this.internalCamera) {
258
290
  _this._css2dRenderer.render(_this._scene.internalScene, _this.internalCamera);
259
291
  }
260
292
  (_a = _this._stats) === null || _a === void 0 ? void 0 : _a.update();
261
- _this._isDirty = (_this._progressiveRendering && stillLoading) || needsRedraw;
293
+ // Do not re-dirty every frame during progressive open — paint is throttled
294
+ // from geometry batches to keep total open time down. Counter hitting 0
295
+ // still forces a final dirty in decreaseNumOfEntitiesToProcess().
296
+ _this._isDirty = needsRedraw;
262
297
  };
263
298
  _this._gripManager = new AcEdGripManager(_this);
264
299
  _this._keyHandler = new AcEdViewKeyHandler(_this);
@@ -482,6 +517,7 @@ var AcTrView2d = /** @class */ (function (_super) {
482
517
  _this._isDirty = true;
483
518
  _this.startAnimationLoop();
484
519
  _this._numOfEntitiesToProcess = 0;
520
+ _this._pendingGeometryJobs = 0;
485
521
  return _this;
486
522
  }
487
523
  AcTrView2d.prototype.getPointerSelectionAction = function (e) {
@@ -564,14 +600,15 @@ var AcTrView2d = /** @class */ (function (_super) {
564
600
  });
565
601
  Object.defineProperty(AcTrView2d.prototype, "isProcessingEntities", {
566
602
  /**
567
- * True while {@link addEntity} batch-conversion callbacks are still running.
603
+ * True while batch conversion or deferred glyph/group geometry is still
604
+ * running.
568
605
  *
569
606
  * Parsing can report 100% before this reaches zero; callers opening files
570
607
  * should wait on this (as {@link zoomToFitDrawing} does) before hiding
571
608
  * progress UI or assuming the canvas is ready.
572
609
  */
573
610
  get: function () {
574
- return this._numOfEntitiesToProcess > 0;
611
+ return this._numOfEntitiesToProcess > 0 || this._pendingGeometryJobs > 0;
575
612
  },
576
613
  enumerable: false,
577
614
  configurable: true
@@ -585,10 +622,30 @@ var AcTrView2d = /** @class */ (function (_super) {
585
622
  },
586
623
  set: function (value) {
587
624
  this._progressiveRendering = value;
625
+ this.resetProgressiveOpenStats();
626
+ },
627
+ enumerable: false,
628
+ configurable: true
629
+ });
630
+ Object.defineProperty(AcTrView2d.prototype, "progressiveOpenStats", {
631
+ /**
632
+ * Progressive-open counters for OPENPROF / palette (paints while converting,
633
+ * cooperative yields). Reset when progressive mode is enabled for an open.
634
+ */
635
+ get: function () {
636
+ return {
637
+ paintCount: this._progressivePaintCount,
638
+ yieldCount: this._progressiveYieldCount
639
+ };
588
640
  },
589
641
  enumerable: false,
590
642
  configurable: true
591
643
  });
644
+ AcTrView2d.prototype.resetProgressiveOpenStats = function () {
645
+ this._progressivePaintCount = 0;
646
+ this._progressiveYieldCount = 0;
647
+ this._lastProgressivePaintAt = 0;
648
+ };
592
649
  /**
593
650
  * Enables progressive camera framing while entities are batch-converted at
594
651
  * document open. Pair with {@link zoomToFitDrawing} for the final accurate fit.
@@ -1078,7 +1135,9 @@ var AcTrView2d = /** @class */ (function (_super) {
1078
1135
  AcTrView2d.prototype.zoomToFitDrawing = function (timeout, layoutBtrId) {
1079
1136
  var _this = this;
1080
1137
  if (timeout === void 0) { timeout = 0; }
1081
- var waiter = new AcEdConditionWaiter(function () { return _this._numOfEntitiesToProcess <= 0; }, function () {
1138
+ var waiter = new AcEdConditionWaiter(
1139
+ // Include deferred glyph/group jobs so text extents land before final fit.
1140
+ function () { return !_this.isProcessingEntities; }, function () {
1082
1141
  if (layoutBtrId && _this._externallyFramedLayouts.delete(layoutBtrId)) {
1083
1142
  _this.endProgressiveOpenFit();
1084
1143
  return;
@@ -1466,16 +1525,34 @@ var AcTrView2d = /** @class */ (function (_super) {
1466
1525
  * @param entity Input one or multiple transient entities
1467
1526
  */
1468
1527
  AcTrView2d.prototype.addTransientEntity = function (entity) {
1528
+ var _this = this;
1469
1529
  var entities = Array.isArray(entity) ? entity : [entity];
1470
- for (var i = 0; i < entities.length; ++i) {
1530
+ var epoch = this._convertEpoch;
1531
+ var _loop_2 = function (i) {
1471
1532
  var entity_1 = entities[i];
1472
- var threeEntity = this.drawEntity(entity_1, true);
1533
+ var threeEntity = this_1.drawEntity(entity_1, true);
1473
1534
  if (threeEntity) {
1474
1535
  threeEntity.objectId = entity_1.objectId;
1475
- threeEntity.syncDraw();
1476
- this._scene.addTransientEntity(threeEntity);
1477
- this._isDirty = true;
1536
+ void threeEntity
1537
+ .asyncDraw()
1538
+ .then(function () {
1539
+ // Drop stale transients started before clear()/regen invalidated the view.
1540
+ if (epoch !== _this._convertEpoch) {
1541
+ threeEntity.dispose();
1542
+ return;
1543
+ }
1544
+ _this._scene.addTransientEntity(threeEntity);
1545
+ _this._isDirty = true;
1546
+ })
1547
+ .catch(function (error) {
1548
+ log.error('[AcTrView2d] Transient entity geometry failed:', error);
1549
+ threeEntity.dispose();
1550
+ });
1478
1551
  }
1552
+ };
1553
+ var this_1 = this;
1554
+ for (var i = 0; i < entities.length; ++i) {
1555
+ _loop_2(i);
1479
1556
  }
1480
1557
  };
1481
1558
  /**
@@ -1533,25 +1610,15 @@ var AcTrView2d = /** @class */ (function (_super) {
1533
1610
  * @inheritdoc
1534
1611
  */
1535
1612
  AcTrView2d.prototype.addEntity = function (entity) {
1536
- var _this = this;
1613
+ var _a;
1537
1614
  var entities = Array.isArray(entity) ? entity : [entity];
1538
1615
  this._numOfEntitiesToProcess += entities.length;
1539
- var convert = function () { return __awaiter(_this, void 0, void 0, function () {
1540
- return __generator(this, function (_a) {
1541
- switch (_a.label) {
1542
- case 0: return [4 /*yield*/, this.batchConvert(entities)];
1543
- case 1:
1544
- _a.sent();
1545
- return [2 /*return*/];
1546
- }
1547
- });
1548
- }); };
1549
1616
  if (this._progressiveRendering) {
1550
- setTimeout(convert);
1551
- this._isDirty = true;
1617
+ (_a = this._convertQueue).push.apply(_a, __spreadArray([], __read(entities), false));
1618
+ void this.drainConvertQueue();
1552
1619
  }
1553
1620
  else {
1554
- void convert();
1621
+ void this.batchConvert(entities);
1555
1622
  }
1556
1623
  };
1557
1624
  /**
@@ -1629,12 +1696,23 @@ var AcTrView2d = /** @class */ (function (_super) {
1629
1696
  }
1630
1697
  // Reconvert through the same path as initial load so block references are
1631
1698
  // split by layer correctly and deferred MTEXT/SHAPE geometry is drawn.
1632
- void this.batchConvert(entities).then(function () {
1633
- if (selectedIds.length > 0) {
1634
- _this.highlight(selectedIds);
1635
- }
1636
- _this._gripManager.refresh();
1637
- });
1699
+ void (function () { return __awaiter(_this, void 0, void 0, function () {
1700
+ return __generator(this, function (_a) {
1701
+ switch (_a.label) {
1702
+ case 0: return [4 /*yield*/, this.batchConvert(entities)];
1703
+ case 1:
1704
+ _a.sent();
1705
+ return [4 /*yield*/, this.waitUntilDeferredGeometryIdle()];
1706
+ case 2:
1707
+ _a.sent();
1708
+ if (selectedIds.length > 0) {
1709
+ this.highlight(selectedIds);
1710
+ }
1711
+ this._gripManager.refresh();
1712
+ return [2 /*return*/];
1713
+ }
1714
+ });
1715
+ }); })();
1638
1716
  this._isDirty = true;
1639
1717
  // Not sure why texture for image entity isn't updated even if 'isDirty' flag is already set to true.
1640
1718
  // So add one timeout event to set 'isDirty' flag to true again to make it work
@@ -1743,16 +1821,17 @@ var AcTrView2d = /** @class */ (function (_super) {
1743
1821
  * **Critically, this runs through `AcEdConditionWaiter`**: at the
1744
1822
  * moment `layoutSwitched` fires, the layout's entities (including its
1745
1823
  * `AcDbViewport`s) have not yet been batch-converted into the scene
1746
- * — `loadLayoutEntitiesIfNeeded` chunked-converts via `setTimeout`.
1824
+ * — `loadLayoutEntitiesIfNeeded` chunked-converts via the progressive
1825
+ * convert queue (or direct `batchConvert`).
1747
1826
  * Without the waiter, `viewportsBoundingBox` returns undefined and
1748
1827
  * the strategy degrades into (1) zooming to garbage `limits`, or
1749
1828
  * (4) zooming to an empty scene box. The waiter polls
1750
- * `_numOfEntitiesToProcess` and only fires the heuristic once the
1751
- * conversion is done.
1829
+ * {@link isProcessingEntities} (convert queue + deferred glyph jobs)
1830
+ * and only fires the heuristic once conversion and text geometry finish.
1752
1831
  */
1753
1832
  AcTrView2d.prototype.applyInitialZoom = function (btrId, layout) {
1754
1833
  var _this = this;
1755
- var waiter = new AcEdConditionWaiter(function () { return _this._numOfEntitiesToProcess <= 0; }, function () {
1834
+ var waiter = new AcEdConditionWaiter(function () { return !_this.isProcessingEntities; }, function () {
1756
1835
  if (_this._externallyFramedLayouts.delete(btrId)) {
1757
1836
  return;
1758
1837
  }
@@ -1793,11 +1872,80 @@ var AcTrView2d = /** @class */ (function (_super) {
1793
1872
  * @inheritdoc
1794
1873
  */
1795
1874
  AcTrView2d.prototype.clear = function () {
1875
+ // Invalidate any in-flight progressive convert so it neither paints into
1876
+ // the cleared scene nor double-decrements the processing counter.
1877
+ this._convertEpoch++;
1878
+ this._convertQueue.length = 0;
1879
+ this._numOfEntitiesToProcess = 0;
1880
+ this._pendingGeometryJobs = 0;
1796
1881
  this._scene.clear();
1797
1882
  this._isDirty = true;
1798
1883
  this._missedImages.clear();
1799
1884
  this._renderer.dispose();
1800
1885
  };
1886
+ /**
1887
+ * Drains the progressive convert queue on a single serial worker so ENTITY
1888
+ * flush chunks can enqueue while conversion overlaps the loading overlay.
1889
+ *
1890
+ * Concurrent callers share the same promise; if more entities are queued
1891
+ * after a drain finishes, a follow-up drain is started.
1892
+ */
1893
+ AcTrView2d.prototype.drainConvertQueue = function () {
1894
+ return __awaiter(this, void 0, void 0, function () {
1895
+ var _this = this;
1896
+ return __generator(this, function (_a) {
1897
+ switch (_a.label) {
1898
+ case 0:
1899
+ if (!this._convertDrainPromise) return [3 /*break*/, 4];
1900
+ return [4 /*yield*/, this._convertDrainPromise];
1901
+ case 1:
1902
+ _a.sent();
1903
+ if (!(this._convertQueue.length > 0)) return [3 /*break*/, 3];
1904
+ return [4 /*yield*/, this.drainConvertQueue()];
1905
+ case 2:
1906
+ _a.sent();
1907
+ _a.label = 3;
1908
+ case 3: return [2 /*return*/];
1909
+ case 4:
1910
+ this._convertDrainPromise = (function () { return __awaiter(_this, void 0, void 0, function () {
1911
+ var batch;
1912
+ return __generator(this, function (_a) {
1913
+ switch (_a.label) {
1914
+ case 0:
1915
+ if (!(this._convertQueue.length > 0)) return [3 /*break*/, 2];
1916
+ batch = this._convertQueue.splice(0, this._convertQueue.length);
1917
+ return [4 /*yield*/, this.batchConvert(batch)];
1918
+ case 1:
1919
+ _a.sent();
1920
+ return [3 /*break*/, 0];
1921
+ case 2: return [2 /*return*/];
1922
+ }
1923
+ });
1924
+ }); })().finally(function () {
1925
+ _this._convertDrainPromise = null;
1926
+ });
1927
+ return [4 /*yield*/, this._convertDrainPromise];
1928
+ case 5:
1929
+ _a.sent();
1930
+ return [2 /*return*/];
1931
+ }
1932
+ });
1933
+ });
1934
+ };
1935
+ /**
1936
+ * Marks the canvas dirty for progressive open, throttled to avoid painting
1937
+ * on every entity (which dominated total open time).
1938
+ */
1939
+ AcTrView2d.prototype.markProgressiveDirty = function (force) {
1940
+ if (force === void 0) { force = false; }
1941
+ var now = performance.now();
1942
+ if (force ||
1943
+ now - this._lastProgressivePaintAt >=
1944
+ AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS) {
1945
+ this._isDirty = true;
1946
+ this._lastProgressivePaintAt = now;
1947
+ }
1948
+ };
1801
1949
  /**
1802
1950
  * @inheritdoc
1803
1951
  */
@@ -1886,7 +2034,7 @@ var AcTrView2d = /** @class */ (function (_super) {
1886
2034
  * on it would silently miss layouts that are pre-loaded ahead of
1887
2035
  * becoming active (e.g. background prefetch).
1888
2036
  * 2. The `_loadingLayouts` guard prevents re-entrance while the
1889
- * `setTimeout` chunked-convert callback is still in flight. Without it,
2037
+ * convert drain is still in flight. Without it,
1890
2038
  * clicking the same layout tab twice in quick succession (or
1891
2039
  * `layoutSwitched` firing twice during the async window) would iterate
1892
2040
  * the block table record again and duplicate every entity in the
@@ -1988,31 +2136,35 @@ var AcTrView2d = /** @class */ (function (_super) {
1988
2136
  this._numOfEntitiesToProcess += entities_2.length;
1989
2137
  var convert = function () { return __awaiter(_this, void 0, void 0, function () {
1990
2138
  var layout;
1991
- return __generator(this, function (_a) {
1992
- switch (_a.label) {
2139
+ var _a;
2140
+ return __generator(this, function (_b) {
2141
+ switch (_b.label) {
1993
2142
  case 0:
1994
- _a.trys.push([0, , 2, 3]);
1995
- return [4 /*yield*/, this.batchConvert(entities_2)];
2143
+ _b.trys.push([0, , 5, 6]);
2144
+ if (!this._progressiveRendering) return [3 /*break*/, 2];
2145
+ (_a = this._convertQueue).push.apply(_a, __spreadArray([], __read(entities_2), false));
2146
+ return [4 /*yield*/, this.drainConvertQueue()];
1996
2147
  case 1:
1997
- _a.sent();
2148
+ _b.sent();
2149
+ return [3 /*break*/, 4];
2150
+ case 2: return [4 /*yield*/, this.batchConvert(entities_2)];
2151
+ case 3:
2152
+ _b.sent();
2153
+ _b.label = 4;
2154
+ case 4:
1998
2155
  layout = this._scene.layouts.get(layoutBtrId);
1999
2156
  if (layout) {
2000
2157
  layout.isLoaded = true;
2001
2158
  }
2002
- return [3 /*break*/, 3];
2003
- case 2:
2159
+ return [3 /*break*/, 6];
2160
+ case 5:
2004
2161
  this._loadingLayouts.delete(layoutBtrId);
2005
2162
  return [7 /*endfinally*/];
2006
- case 3: return [2 /*return*/];
2163
+ case 6: return [2 /*return*/];
2007
2164
  }
2008
2165
  });
2009
2166
  }); };
2010
- if (this._progressiveRendering) {
2011
- setTimeout(convert);
2012
- }
2013
- else {
2014
- void convert();
2015
- }
2167
+ void convert();
2016
2168
  }
2017
2169
  catch (error) {
2018
2170
  log.error('[AcTrView2d] Error loading layout entities:', error);
@@ -2087,21 +2239,25 @@ var AcTrView2d = /** @class */ (function (_super) {
2087
2239
  return entity.worldDraw(this._renderer, delay);
2088
2240
  };
2089
2241
  /**
2090
- * Finishes geometry for a converted entity. Block groups always use
2091
- * {@link AcTrGroup.syncDraw} to finalize deferred children. Progressive mode
2092
- * defers MTEXT/SHAPE to async workers; non-progressive mode uses
2093
- * {@link AcTrEntity.syncDraw}.
2242
+ * Finishes geometry for a converted entity.
2243
+ *
2244
+ * Glyph entities and block groups use {@link AcTrEntity.asyncDraw} so
2245
+ * {@link FontManager.awaitFontsBeforeDraw} can wait for fonts without
2246
+ * relying on a full-scene regen. Other entities keep the sync finalize path.
2094
2247
  */
2095
- AcTrView2d.prototype.finishEntityGeometry = function (threeEntity, progressive) {
2248
+ AcTrView2d.prototype.finishEntityGeometry = function (threeEntity, _progressive) {
2096
2249
  return __awaiter(this, void 0, void 0, function () {
2097
2250
  return __generator(this, function (_a) {
2098
2251
  switch (_a.label) {
2099
2252
  case 0:
2100
- if (threeEntity instanceof AcTrGroup) {
2101
- threeEntity.syncDraw();
2253
+ if (!(threeEntity instanceof AcTrGroup)) return [3 /*break*/, 2];
2254
+ // Compacted INSERT templates may skip syncDraw when fonts are awaited
2255
+ // later; still walk for empty glyph shells. Skip only when there is
2256
+ // nothing left to finalize (incl. post-cache ATTRIBs).
2257
+ if (threeEntity.getSourceEntities().length === 0 &&
2258
+ !this.groupHasPendingGlyphGeometry(threeEntity)) {
2102
2259
  return [2 /*return*/];
2103
2260
  }
2104
- if (!progressive) return [3 /*break*/, 2];
2105
2261
  return [4 /*yield*/, threeEntity.asyncDraw()];
2106
2262
  case 1:
2107
2263
  _a.sent();
@@ -2110,12 +2266,82 @@ var AcTrView2d = /** @class */ (function (_super) {
2110
2266
  if (threeEntity.hasDrawableGeometry()) {
2111
2267
  return [2 /*return*/];
2112
2268
  }
2113
- threeEntity.syncDraw();
2269
+ return [4 /*yield*/, threeEntity.asyncDraw()];
2270
+ case 3:
2271
+ _a.sent();
2114
2272
  return [2 /*return*/];
2115
2273
  }
2116
2274
  });
2117
2275
  });
2118
2276
  };
2277
+ AcTrView2d.prototype.needsDeferredFontGeometry = function (threeEntity) {
2278
+ return (threeEntity instanceof AcTrGlyphEntity ||
2279
+ threeEntity instanceof AcTrGroup);
2280
+ };
2281
+ AcTrView2d.prototype.groupHasPendingGlyphGeometry = function (group) {
2282
+ var pending = false;
2283
+ group.traverse(function (child) {
2284
+ if (child instanceof AcTrGlyphEntity &&
2285
+ !child.hasDrawableGeometry()) {
2286
+ pending = true;
2287
+ }
2288
+ });
2289
+ return pending;
2290
+ };
2291
+ /**
2292
+ * Runs glyph/group geometry finalize off the main convert loop so other
2293
+ * entities keep converting while fonts download.
2294
+ */
2295
+ AcTrView2d.prototype.enqueueDeferredGeometry = function (run, epoch) {
2296
+ var _this = this;
2297
+ if (epoch !== this._convertEpoch) {
2298
+ return;
2299
+ }
2300
+ this._pendingGeometryJobs++;
2301
+ void run()
2302
+ .then(function () {
2303
+ // Convert counter often hits 0 before fonts finish; without this,
2304
+ // text added later never paints until the user pans/zooms.
2305
+ if (epoch === _this._convertEpoch) {
2306
+ _this._isDirty = true;
2307
+ }
2308
+ })
2309
+ .catch(function (error) {
2310
+ log.error('[AcTrView2d] Deferred entity geometry failed:', error);
2311
+ })
2312
+ .finally(function () {
2313
+ if (epoch === _this._convertEpoch) {
2314
+ _this._pendingGeometryJobs = Math.max(0, _this._pendingGeometryJobs - 1);
2315
+ if (_this._pendingGeometryJobs === 0) {
2316
+ _this._isDirty = true;
2317
+ }
2318
+ }
2319
+ });
2320
+ };
2321
+ /**
2322
+ * Waits until side-pool glyph/group jobs for the current convert epoch finish.
2323
+ * Used by entity updates that must highlight after text is in the scene.
2324
+ */
2325
+ AcTrView2d.prototype.waitUntilDeferredGeometryIdle = function () {
2326
+ return __awaiter(this, void 0, void 0, function () {
2327
+ var epoch;
2328
+ return __generator(this, function (_a) {
2329
+ switch (_a.label) {
2330
+ case 0:
2331
+ epoch = this._convertEpoch;
2332
+ _a.label = 1;
2333
+ case 1:
2334
+ if (!(epoch === this._convertEpoch &&
2335
+ this._pendingGeometryJobs > 0)) return [3 /*break*/, 3];
2336
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
2337
+ case 2:
2338
+ _a.sent();
2339
+ return [3 /*break*/, 1];
2340
+ case 3: return [2 /*return*/];
2341
+ }
2342
+ });
2343
+ });
2344
+ };
2119
2345
  /**
2120
2346
  * Walks the given block table record once and creates one
2121
2347
  * `AcTrViewportView` for every real `AcDbViewport` entity it finds
@@ -2163,116 +2389,206 @@ var AcTrView2d = /** @class */ (function (_super) {
2163
2389
  */
2164
2390
  AcTrView2d.prototype.batchConvert = function (entities_3) {
2165
2391
  return __awaiter(this, arguments, void 0, function (entities, options) {
2166
- var progressive, i, entity, shouldConvert, threeEntity, isExtendBbox, layoutView, viewportView, fileName, error_2;
2392
+ var epoch, progressive, yieldGate, yieldToEventLoop, _loop_3, this_2, i;
2167
2393
  var _this = this;
2168
2394
  if (options === void 0) { options = {}; }
2169
2395
  return __generator(this, function (_a) {
2170
2396
  switch (_a.label) {
2171
2397
  case 0:
2398
+ epoch = this._convertEpoch;
2172
2399
  progressive = this._progressiveRendering && !options.forExport;
2400
+ yieldGate = progressive
2401
+ ? new AcCmUiYieldGate(AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS)
2402
+ : undefined;
2403
+ yieldToEventLoop = function () {
2404
+ return new Promise(function (resolve) { return setTimeout(resolve, 0); });
2405
+ };
2406
+ _loop_3 = function (i) {
2407
+ var entity, shouldConvert, directMeta, added, threeEntity_1, isMultiLayerGroup, deferGeometry, isExtendBbox_1, commitEntity, layoutView, viewportView, fileName, error_2, didYield;
2408
+ return __generator(this, function (_b) {
2409
+ switch (_b.label) {
2410
+ case 0:
2411
+ entity = entities[i];
2412
+ _b.label = 1;
2413
+ case 1:
2414
+ _b.trys.push([1, 9, 10, 11]);
2415
+ // Document was cleared / replaced while this batch was draining.
2416
+ if (epoch !== this_2._convertEpoch) {
2417
+ return [2 /*return*/, "continue"];
2418
+ }
2419
+ // Skip the default paper-space viewport (`*Paper_Space`) entirely:
2420
+ // it is an AutoCAD-internal viewport that exists in every paper
2421
+ // layout and must not be drawn (would render a giant rectangle in
2422
+ // the paper coordinate system), nor added to the spatial index
2423
+ // (would stretch the layout's bounding box and break
2424
+ // zoomToFitDrawing), nor turned into an AcTrViewportView (would
2425
+ // setScissor over most of the canvas and squeeze the real user
2426
+ // viewports into a corner). See
2427
+ // `AcTrViewportView.isDefaultPaperSpaceViewport` for the criterion
2428
+ // and the rationale (legacy `number === 1` is unreliable across
2429
+ // parsers).
2430
+ if (entity instanceof AcDbViewport &&
2431
+ AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
2432
+ return [2 /*return*/, "continue"];
2433
+ }
2434
+ shouldConvert = options.forExport
2435
+ ? this_2._entityDisplay.shouldConvertForExport(entity)
2436
+ : this_2._entityDisplay.shouldConvert(entity);
2437
+ if (!shouldConvert) {
2438
+ return [2 /*return*/, "continue"];
2439
+ }
2440
+ directMeta = tryBuildDirectEntityMeta(entity, this_2._renderer);
2441
+ if (directMeta) {
2442
+ added = false;
2443
+ try {
2444
+ added = this_2._scene.addDirectEntity(directMeta, shouldExtendBboxForDirectEntity(entity));
2445
+ if (added) {
2446
+ this_2.applySessionHiddenObjectState(entity.objectId);
2447
+ if (progressive) {
2448
+ this_2.markProgressiveDirty();
2449
+ this_2._progressiveOpenFit.afterGeometryBatch(function () { return _this.resolveLayoutFitBox(); }, i);
2450
+ }
2451
+ }
2452
+ }
2453
+ finally {
2454
+ directMeta.geometry.dispose();
2455
+ }
2456
+ if (added) {
2457
+ return [2 /*return*/, "continue"];
2458
+ }
2459
+ // Append refused (e.g. invisible) — fall through to the legacy path.
2460
+ }
2461
+ threeEntity_1 = this_2.drawEntity(entity, false);
2462
+ // Viewports may produce no border geometry (e.g. on a no-plot layer) while
2463
+ // still needing an AcTrViewportView for model content below.
2464
+ if (!threeEntity_1 && !(entity instanceof AcDbViewport))
2465
+ return [2 /*return*/, "continue"];
2466
+ if (!threeEntity_1) return [3 /*break*/, 8];
2467
+ threeEntity_1.objectId = entity.objectId;
2468
+ threeEntity_1.ownerId = entity.ownerId;
2469
+ threeEntity_1.layerName = entity.layer;
2470
+ threeEntity_1.visible = entity.visibility !== false;
2471
+ if (threeEntity_1 instanceof AcTrGroup &&
2472
+ threeEntity_1.isOnTheSameLayer) {
2473
+ // Even when a block expands to a single layer bucket, children authored on
2474
+ // layer "0" still inherit the INSERT layer for ByLayer traits (color, etc.).
2475
+ this_2._inheritedLayerMaterialMapper.remap(threeEntity_1.children, '0', threeEntity_1.layerName);
2476
+ threeEntity_1.userData.insertLayerName = threeEntity_1.layerName;
2477
+ }
2478
+ isMultiLayerGroup = threeEntity_1 instanceof AcTrGroup &&
2479
+ !threeEntity_1.isOnTheSameLayer;
2480
+ deferGeometry = !options.forExport && this_2.needsDeferredFontGeometry(threeEntity_1);
2481
+ if (!isMultiLayerGroup) return [3 /*break*/, 5];
2482
+ if (!deferGeometry) return [3 /*break*/, 2];
2483
+ this_2.enqueueDeferredGeometry(function () {
2484
+ return _this.handleGroup(threeEntity_1, progressive, epoch);
2485
+ }, epoch);
2486
+ return [3 /*break*/, 4];
2487
+ case 2: return [4 /*yield*/, this_2.handleGroup(threeEntity_1, progressive, epoch)];
2488
+ case 3:
2489
+ _b.sent();
2490
+ _b.label = 4;
2491
+ case 4: return [3 /*break*/, 8];
2492
+ case 5:
2493
+ isExtendBbox_1 = !(entity instanceof AcDbRay || entity instanceof AcDbXline);
2494
+ commitEntity = function () { return __awaiter(_this, void 0, void 0, function () {
2495
+ var _this = this;
2496
+ return __generator(this, function (_a) {
2497
+ switch (_a.label) {
2498
+ case 0: return [4 /*yield*/, this.finishEntityGeometry(threeEntity_1, progressive)];
2499
+ case 1:
2500
+ _a.sent();
2501
+ if (epoch !== this._convertEpoch) {
2502
+ threeEntity_1.dispose();
2503
+ return [2 /*return*/];
2504
+ }
2505
+ if (threeEntity_1 instanceof AcTrGroup) {
2506
+ this.syncGroupSpatialBoundsForIndexing(threeEntity_1);
2507
+ }
2508
+ this._scene.addEntity(threeEntity_1, isExtendBbox_1);
2509
+ this.applySessionHiddenObjectState(entity.objectId);
2510
+ // Release memory occupied by this entity
2511
+ threeEntity_1.dispose();
2512
+ if (progressive) {
2513
+ this.markProgressiveDirty();
2514
+ this._progressiveOpenFit.afterGeometryBatch(function () { return _this.resolveLayoutFitBox(); }, i);
2515
+ }
2516
+ return [2 /*return*/];
2517
+ }
2518
+ });
2519
+ }); };
2520
+ if (!deferGeometry) return [3 /*break*/, 6];
2521
+ this_2.enqueueDeferredGeometry(commitEntity, epoch);
2522
+ return [3 /*break*/, 8];
2523
+ case 6: return [4 /*yield*/, commitEntity()];
2524
+ case 7:
2525
+ _b.sent();
2526
+ _b.label = 8;
2527
+ case 8:
2528
+ if (epoch !== this_2._convertEpoch) {
2529
+ return [2 /*return*/, "continue"];
2530
+ }
2531
+ if (entity instanceof AcDbViewport) {
2532
+ // Default paper-space viewport was already filtered out at the
2533
+ // top of the loop, so anything that reaches here is a real
2534
+ // user-created viewport. The redundant check below is kept as
2535
+ // a defensive guard in case a future refactor reorders the
2536
+ // early-skip — it costs ~nothing and prevents a regression.
2537
+ if (!AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
2538
+ layoutView = this_2._layoutViewManager.getAt(entity.ownerId);
2539
+ if (layoutView) {
2540
+ viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this_2._renderer);
2541
+ layoutView.addViewport(viewportView);
2542
+ }
2543
+ }
2544
+ }
2545
+ else if (entity instanceof AcDbRasterImage) {
2546
+ fileName = entity.imageFileName;
2547
+ if (fileName && !entity.image) {
2548
+ this_2._missedImages.set(entity.objectId, fileName);
2549
+ }
2550
+ }
2551
+ return [3 /*break*/, 11];
2552
+ case 9:
2553
+ error_2 = _b.sent();
2554
+ log.error("[AcTrView2d] Failed to convert entity ".concat(entity.objectId, " (").concat(entity.type, "):"), error_2);
2555
+ return [3 /*break*/, 11];
2556
+ case 10:
2557
+ // Counter was reset in clear() when the epoch advanced; do not
2558
+ // decrease again or isProcessingEntities can go negative/warn.
2559
+ if (epoch === this_2._convertEpoch) {
2560
+ this_2.decreaseNumOfEntitiesToProcess();
2561
+ }
2562
+ return [7 /*endfinally*/];
2563
+ case 11:
2564
+ if (epoch !== this_2._convertEpoch) {
2565
+ return [2 /*return*/, "continue"];
2566
+ }
2567
+ if (!yieldGate) return [3 /*break*/, 13];
2568
+ return [4 /*yield*/, yieldGate.maybeYield(yieldToEventLoop)];
2569
+ case 12:
2570
+ didYield = _b.sent();
2571
+ if (didYield) {
2572
+ this_2._progressiveYieldCount++;
2573
+ }
2574
+ _b.label = 13;
2575
+ case 13: return [2 /*return*/];
2576
+ }
2577
+ });
2578
+ };
2579
+ this_2 = this;
2173
2580
  i = 0;
2174
2581
  _a.label = 1;
2175
2582
  case 1:
2176
- if (!(i < entities.length)) return [3 /*break*/, 11];
2177
- entity = entities[i];
2178
- _a.label = 2;
2583
+ if (!(i < entities.length)) return [3 /*break*/, 4];
2584
+ return [5 /*yield**/, _loop_3(i)];
2179
2585
  case 2:
2180
- _a.trys.push([2, 8, 9, 10]);
2181
- // Skip the default paper-space viewport (`*Paper_Space`) entirely:
2182
- // it is an AutoCAD-internal viewport that exists in every paper
2183
- // layout and must not be drawn (would render a giant rectangle in
2184
- // the paper coordinate system), nor added to the spatial index
2185
- // (would stretch the layout's bounding box and break
2186
- // zoomToFitDrawing), nor turned into an AcTrViewportView (would
2187
- // setScissor over most of the canvas and squeeze the real user
2188
- // viewports into a corner). See
2189
- // `AcTrViewportView.isDefaultPaperSpaceViewport` for the criterion
2190
- // and the rationale (legacy `number === 1` is unreliable across
2191
- // parsers).
2192
- if (entity instanceof AcDbViewport &&
2193
- AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
2194
- return [3 /*break*/, 10];
2195
- }
2196
- shouldConvert = options.forExport
2197
- ? this._entityDisplay.shouldConvertForExport(entity)
2198
- : this._entityDisplay.shouldConvert(entity);
2199
- if (!shouldConvert) {
2200
- return [3 /*break*/, 10];
2201
- }
2202
- threeEntity = this.drawEntity(entity, progressive);
2203
- // Viewports may produce no border geometry (e.g. on a no-plot layer) while
2204
- // still needing an AcTrViewportView for model content below.
2205
- if (!threeEntity && !(entity instanceof AcDbViewport))
2206
- return [3 /*break*/, 10];
2207
- if (!threeEntity) return [3 /*break*/, 7];
2208
- threeEntity.objectId = entity.objectId;
2209
- threeEntity.ownerId = entity.ownerId;
2210
- threeEntity.layerName = entity.layer;
2211
- threeEntity.visible = entity.visibility !== false;
2212
- if (threeEntity instanceof AcTrGroup &&
2213
- threeEntity.isOnTheSameLayer) {
2214
- // Even when a block expands to a single layer bucket, children authored on
2215
- // layer "0" still inherit the INSERT layer for ByLayer traits (color, etc.).
2216
- this._inheritedLayerMaterialMapper.remap(threeEntity.children, '0', threeEntity.layerName);
2217
- threeEntity.userData.insertLayerName = threeEntity.layerName;
2218
- }
2219
- if (!(threeEntity instanceof AcTrGroup &&
2220
- !threeEntity.isOnTheSameLayer)) return [3 /*break*/, 4];
2221
- return [4 /*yield*/, this.handleGroup(threeEntity, progressive)];
2222
- case 3:
2223
- _a.sent();
2224
- return [3 /*break*/, 7];
2225
- case 4:
2226
- isExtendBbox = !(entity instanceof AcDbRay || entity instanceof AcDbXline);
2227
- return [4 /*yield*/, this.finishEntityGeometry(threeEntity, progressive)];
2228
- case 5:
2229
- _a.sent();
2230
- if (threeEntity instanceof AcTrGroup) {
2231
- this.syncGroupSpatialBoundsForIndexing(threeEntity);
2232
- }
2233
- this._scene.addEntity(threeEntity, isExtendBbox);
2234
- this.applySessionHiddenObjectState(entity.objectId);
2235
- // Release memory occupied by this entity
2236
- threeEntity.dispose();
2237
- if (!progressive) return [3 /*break*/, 7];
2238
- this._isDirty = true;
2239
- return [4 /*yield*/, this._progressiveOpenFit.afterGeometryBatch(function () { return _this.resolveLayoutFitBox(); }, i)];
2240
- case 6:
2241
2586
  _a.sent();
2242
- _a.label = 7;
2243
- case 7:
2244
- if (entity instanceof AcDbViewport) {
2245
- // Default paper-space viewport was already filtered out at the
2246
- // top of the loop, so anything that reaches here is a real
2247
- // user-created viewport. The redundant check below is kept as
2248
- // a defensive guard in case a future refactor reorders the
2249
- // early-skip — it costs ~nothing and prevents a regression.
2250
- if (!AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
2251
- layoutView = this._layoutViewManager.getAt(entity.ownerId);
2252
- if (layoutView) {
2253
- viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this._renderer);
2254
- layoutView.addViewport(viewportView);
2255
- }
2256
- }
2257
- }
2258
- else if (entity instanceof AcDbRasterImage) {
2259
- fileName = entity.imageFileName;
2260
- if (fileName && !entity.image) {
2261
- this._missedImages.set(entity.objectId, fileName);
2262
- }
2263
- }
2264
- return [3 /*break*/, 10];
2265
- case 8:
2266
- error_2 = _a.sent();
2267
- log.error("[AcTrView2d] Failed to convert entity ".concat(entity.objectId, " (").concat(entity.type, "):"), error_2);
2268
- return [3 /*break*/, 10];
2269
- case 9:
2270
- this.decreaseNumOfEntitiesToProcess();
2271
- return [7 /*endfinally*/];
2272
- case 10:
2587
+ _a.label = 3;
2588
+ case 3:
2273
2589
  ++i;
2274
2590
  return [3 /*break*/, 1];
2275
- case 11: return [2 /*return*/];
2591
+ case 4: return [2 /*return*/];
2276
2592
  }
2277
2593
  });
2278
2594
  });
@@ -2297,16 +2613,21 @@ var AcTrView2d = /** @class */ (function (_super) {
2297
2613
  var userData = group.userData;
2298
2614
  userData.spatialIndexChildBoxes = childBoxes;
2299
2615
  };
2300
- AcTrView2d.prototype.handleGroup = function (group, progressive) {
2301
- return __awaiter(this, void 0, void 0, function () {
2616
+ AcTrView2d.prototype.handleGroup = function (group_1, progressive_1) {
2617
+ return __awaiter(this, arguments, void 0, function (group, progressive, epoch) {
2302
2618
  var children, objectsGroupByLayer, children_1, children_1_1, child, renderContext, groupObjectId, groupLayerName, groupChildBoxes, aggregateSpatialBbox;
2303
2619
  var e_11, _a;
2304
2620
  var _this = this;
2621
+ if (epoch === void 0) { epoch = this._convertEpoch; }
2305
2622
  return __generator(this, function (_b) {
2306
2623
  switch (_b.label) {
2307
2624
  case 0: return [4 /*yield*/, this.finishEntityGeometry(group, progressive)];
2308
2625
  case 1:
2309
2626
  _b.sent();
2627
+ if (epoch !== this._convertEpoch) {
2628
+ group.dispose();
2629
+ return [2 /*return*/];
2630
+ }
2310
2631
  this.syncGroupSpatialBoundsForIndexing(group);
2311
2632
  children = group.children;
2312
2633
  objectsGroupByLayer = new Map();
@@ -2410,9 +2731,9 @@ var AcTrView2d = /** @class */ (function (_super) {
2410
2731
  entity.dispose();
2411
2732
  });
2412
2733
  group.dispose();
2413
- if (this._progressiveRendering) {
2414
- this._isDirty = true;
2415
- void this._progressiveOpenFit.afterGeometryBatch(function () {
2734
+ if (progressive) {
2735
+ this.markProgressiveDirty();
2736
+ this._progressiveOpenFit.afterGeometryBatch(function () {
2416
2737
  return _this.resolveLayoutFitBox();
2417
2738
  });
2418
2739
  }
@@ -2427,11 +2748,25 @@ var AcTrView2d = /** @class */ (function (_super) {
2427
2748
  this._numOfEntitiesToProcess = 0;
2428
2749
  log.warn('Something wrong! The number of entities to process should not be less than 0.');
2429
2750
  }
2430
- else if (this._numOfEntitiesToProcess === 0 &&
2431
- !this._progressiveRendering) {
2751
+ else if (this._numOfEntitiesToProcess === 0) {
2752
+ // Always mark dirty when the queue drains. Progressive open throttles
2753
+ // mid-open paints, so the last batch would otherwise never redraw until
2754
+ // the user pans/zooms (animate bails when !_isDirty && !stillLoading).
2432
2755
  this._isDirty = true;
2433
2756
  }
2434
2757
  };
2758
+ /**
2759
+ * Wall-time between cooperative yields during progressive open (ms).
2760
+ * Kept relatively large so convert throughput stays close to the
2761
+ * non-progressive path; smaller budgets made open 2–3× slower.
2762
+ */
2763
+ AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS = 300;
2764
+ /**
2765
+ * Minimum interval between progressive mid-open paints (ms).
2766
+ * Full-scene WebGL paints dominate open wall time on large drawings;
2767
+ * paint much less often than we yield.
2768
+ */
2769
+ AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS = 1000;
2435
2770
  return AcTrView2d;
2436
2771
  }(AcEdBaseView));
2437
2772
  export { AcTrView2d };