@mlightcad/cad-simple-viewer 1.7.4 → 1.7.6-preview.0

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 (190) hide show
  1. package/README.md +5 -0
  2. package/dist/cad-simple-viewer.js +11114 -8898
  3. package/dist/cad-simple-viewer.umd.cjs +432 -88
  4. package/dist/mtext-renderer-worker.js +2987 -2815
  5. package/find.ts +7 -0
  6. package/lib/app/AcApDocManager.d.ts +49 -1
  7. package/lib/app/AcApDocManager.d.ts.map +1 -1
  8. package/lib/app/AcApDocManager.js +150 -12
  9. package/lib/app/AcApDocManager.js.map +1 -1
  10. package/lib/app/AcApOpenDatabaseOptions.d.ts +33 -4
  11. package/lib/app/AcApOpenDatabaseOptions.d.ts.map +1 -1
  12. package/lib/app/AcApOpenFileDialog.d.ts +1 -1
  13. package/lib/app/AcApOpenFileDialog.d.ts.map +1 -1
  14. package/lib/app/AcApOpenFileDialog.js +22 -92
  15. package/lib/app/AcApOpenFileDialog.js.map +1 -1
  16. package/lib/app/AcApOpenFileProfiler.d.ts +14 -0
  17. package/lib/app/AcApOpenFileProfiler.d.ts.map +1 -1
  18. package/lib/app/AcApOpenFileProfiler.js +25 -3
  19. package/lib/app/AcApOpenFileProfiler.js.map +1 -1
  20. package/lib/app/AcApOpenFileProgressController.d.ts +17 -9
  21. package/lib/app/AcApOpenFileProgressController.d.ts.map +1 -1
  22. package/lib/app/AcApOpenFileProgressController.js +41 -20
  23. package/lib/app/AcApOpenFileProgressController.js.map +1 -1
  24. package/lib/app/AcApProgress.d.ts +5 -0
  25. package/lib/app/AcApProgress.d.ts.map +1 -1
  26. package/lib/app/AcApProgress.js +10 -0
  27. package/lib/app/AcApProgress.js.map +1 -1
  28. package/lib/app/dataSource/AcApDataSource.d.ts +97 -0
  29. package/lib/app/dataSource/AcApDataSource.d.ts.map +1 -0
  30. package/lib/app/dataSource/AcApDataSource.js +2 -0
  31. package/lib/app/dataSource/AcApDataSource.js.map +1 -0
  32. package/lib/app/dataSource/AcApDataSourceManager.d.ts +70 -0
  33. package/lib/app/dataSource/AcApDataSourceManager.d.ts.map +1 -0
  34. package/lib/app/dataSource/AcApDataSourceManager.js +84 -0
  35. package/lib/app/dataSource/AcApDataSourceManager.js.map +1 -0
  36. package/lib/app/dataSource/AcApLocalDataSource.d.ts +25 -0
  37. package/lib/app/dataSource/AcApLocalDataSource.d.ts.map +1 -0
  38. package/lib/app/dataSource/AcApLocalDataSource.js +153 -0
  39. package/lib/app/dataSource/AcApLocalDataSource.js.map +1 -0
  40. package/lib/app/dataSource/AcApUrlDataSource.d.ts +17 -0
  41. package/lib/app/dataSource/AcApUrlDataSource.d.ts.map +1 -0
  42. package/lib/app/dataSource/AcApUrlDataSource.js +214 -0
  43. package/lib/app/dataSource/AcApUrlDataSource.js.map +1 -0
  44. package/lib/app/dataSource/acapBuildDataSourceMenu.d.ts +42 -0
  45. package/lib/app/dataSource/acapBuildDataSourceMenu.d.ts.map +1 -0
  46. package/lib/app/dataSource/acapBuildDataSourceMenu.js +121 -0
  47. package/lib/app/dataSource/acapBuildDataSourceMenu.js.map +1 -0
  48. package/lib/app/dataSource/acapInvokeDataSourceMenuAction.d.ts +17 -0
  49. package/lib/app/dataSource/acapInvokeDataSourceMenuAction.d.ts.map +1 -0
  50. package/lib/app/dataSource/acapInvokeDataSourceMenuAction.js +74 -0
  51. package/lib/app/dataSource/acapInvokeDataSourceMenuAction.js.map +1 -0
  52. package/lib/app/dataSource/acapOpenCenteredPopup.d.ts +13 -0
  53. package/lib/app/dataSource/acapOpenCenteredPopup.d.ts.map +1 -0
  54. package/lib/app/dataSource/acapOpenCenteredPopup.js +35 -0
  55. package/lib/app/dataSource/acapOpenCenteredPopup.js.map +1 -0
  56. package/lib/app/dataSource/acapOpenDataSourceResult.d.ts +13 -0
  57. package/lib/app/dataSource/acapOpenDataSourceResult.d.ts.map +1 -0
  58. package/lib/app/dataSource/acapOpenDataSourceResult.js +90 -0
  59. package/lib/app/dataSource/acapOpenDataSourceResult.js.map +1 -0
  60. package/lib/app/dataSource/acapRunDataSourceMenuAction.d.ts +15 -0
  61. package/lib/app/dataSource/acapRunDataSourceMenuAction.d.ts.map +1 -0
  62. package/lib/app/dataSource/acapRunDataSourceMenuAction.js +74 -0
  63. package/lib/app/dataSource/acapRunDataSourceMenuAction.js.map +1 -0
  64. package/lib/app/dataSource/acapWithDataSourceBusy.d.ts +12 -0
  65. package/lib/app/dataSource/acapWithDataSourceBusy.d.ts.map +1 -0
  66. package/lib/app/dataSource/acapWithDataSourceBusy.js +88 -0
  67. package/lib/app/dataSource/acapWithDataSourceBusy.js.map +1 -0
  68. package/lib/app/dataSource/index.d.ts +11 -0
  69. package/lib/app/dataSource/index.d.ts.map +1 -0
  70. package/lib/app/dataSource/index.js +10 -0
  71. package/lib/app/dataSource/index.js.map +1 -0
  72. package/lib/app/index.d.ts +1 -0
  73. package/lib/app/index.d.ts.map +1 -1
  74. package/lib/app/index.js +1 -0
  75. package/lib/app/index.js.map +1 -1
  76. package/lib/app/openFileProgress.d.ts +8 -1
  77. package/lib/app/openFileProgress.d.ts.map +1 -1
  78. package/lib/app/openFileProgress.js +13 -2
  79. package/lib/app/openFileProgress.js.map +1 -1
  80. package/lib/app/syncCurrentSpaceFromTileMode.d.ts +25 -0
  81. package/lib/app/syncCurrentSpaceFromTileMode.d.ts.map +1 -0
  82. package/lib/app/syncCurrentSpaceFromTileMode.js +85 -0
  83. package/lib/app/syncCurrentSpaceFromTileMode.js.map +1 -0
  84. package/lib/editor/global/AcEdUiTheme.d.ts +10 -1
  85. package/lib/editor/global/AcEdUiTheme.d.ts.map +1 -1
  86. package/lib/editor/global/AcEdUiTheme.js +35 -2
  87. package/lib/editor/global/AcEdUiTheme.js.map +1 -1
  88. package/lib/editor/input/ui/AcEdCommandLine.d.ts.map +1 -1
  89. package/lib/editor/input/ui/AcEdCommandLine.js +7 -7
  90. package/lib/editor/input/ui/AcEdCommandLine.js.map +1 -1
  91. package/lib/find/AcApFindNavigator.d.ts +59 -0
  92. package/lib/find/AcApFindNavigator.d.ts.map +1 -0
  93. package/lib/find/AcApFindNavigator.js +145 -0
  94. package/lib/find/AcApFindNavigator.js.map +1 -0
  95. package/lib/find/findDrawing.d.ts +100 -0
  96. package/lib/find/findDrawing.d.ts.map +1 -0
  97. package/lib/find/findDrawing.js +183 -0
  98. package/lib/find/findDrawing.js.map +1 -0
  99. package/lib/find/findText.d.ts +147 -0
  100. package/lib/find/findText.d.ts.map +1 -0
  101. package/lib/find/findText.js +527 -0
  102. package/lib/find/findText.js.map +1 -0
  103. package/lib/find/index.d.ts +3 -0
  104. package/lib/find/index.d.ts.map +1 -0
  105. package/lib/find/index.js +3 -0
  106. package/lib/find/index.js.map +1 -0
  107. package/lib/i18n/ar/main.d.ts +26 -0
  108. package/lib/i18n/ar/main.d.ts.map +1 -1
  109. package/lib/i18n/ar/main.js +26 -0
  110. package/lib/i18n/ar/main.js.map +1 -1
  111. package/lib/i18n/cs/main.d.ts +26 -0
  112. package/lib/i18n/cs/main.d.ts.map +1 -1
  113. package/lib/i18n/cs/main.js +26 -0
  114. package/lib/i18n/cs/main.js.map +1 -1
  115. package/lib/i18n/en/main.d.ts +26 -0
  116. package/lib/i18n/en/main.d.ts.map +1 -1
  117. package/lib/i18n/en/main.js +26 -0
  118. package/lib/i18n/en/main.js.map +1 -1
  119. package/lib/i18n/tr/main.d.ts +26 -0
  120. package/lib/i18n/tr/main.d.ts.map +1 -1
  121. package/lib/i18n/tr/main.js +26 -0
  122. package/lib/i18n/tr/main.js.map +1 -1
  123. package/lib/i18n/zh/main.d.ts +26 -0
  124. package/lib/i18n/zh/main.d.ts.map +1 -1
  125. package/lib/i18n/zh/main.js +26 -0
  126. package/lib/i18n/zh/main.js.map +1 -1
  127. package/lib/index.d.ts +2 -0
  128. package/lib/index.d.ts.map +1 -1
  129. package/lib/index.js +2 -0
  130. package/lib/index.js.map +1 -1
  131. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.d.ts +29 -7
  132. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.d.ts.map +1 -1
  133. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.js +77 -38
  134. package/lib/spatialIndex/AcTrHierarchicalSpatialIndex.js.map +1 -1
  135. package/lib/ui/AcUiFileOpenPanel.d.ts +156 -0
  136. package/lib/ui/AcUiFileOpenPanel.d.ts.map +1 -0
  137. package/lib/ui/AcUiFileOpenPanel.js +695 -0
  138. package/lib/ui/AcUiFileOpenPanel.js.map +1 -0
  139. package/lib/ui/icons.d.ts +2 -0
  140. package/lib/ui/icons.d.ts.map +1 -1
  141. package/lib/ui/icons.js +2 -0
  142. package/lib/ui/icons.js.map +1 -1
  143. package/lib/ui/index.d.ts +1 -0
  144. package/lib/ui/index.d.ts.map +1 -1
  145. package/lib/ui/index.js +1 -0
  146. package/lib/ui/index.js.map +1 -1
  147. package/lib/util/AcApTextStyleFontPreload.d.ts +42 -0
  148. package/lib/util/AcApTextStyleFontPreload.d.ts.map +1 -0
  149. package/lib/util/AcApTextStyleFontPreload.js +149 -0
  150. package/lib/util/AcApTextStyleFontPreload.js.map +1 -0
  151. package/lib/util/index.d.ts +1 -0
  152. package/lib/util/index.d.ts.map +1 -1
  153. package/lib/util/index.js +1 -0
  154. package/lib/util/index.js.map +1 -1
  155. package/lib/view/AcTrConvertQueue.d.ts +78 -0
  156. package/lib/view/AcTrConvertQueue.d.ts.map +1 -0
  157. package/lib/view/AcTrConvertQueue.js +143 -0
  158. package/lib/view/AcTrConvertQueue.js.map +1 -0
  159. package/lib/view/AcTrGroupWcsBboxAssert.d.ts +9 -0
  160. package/lib/view/AcTrGroupWcsBboxAssert.d.ts.map +1 -1
  161. package/lib/view/AcTrGroupWcsBboxAssert.js +12 -0
  162. package/lib/view/AcTrGroupWcsBboxAssert.js.map +1 -1
  163. package/lib/view/AcTrLayer.d.ts.map +1 -1
  164. package/lib/view/AcTrLayer.js +5 -1
  165. package/lib/view/AcTrLayer.js.map +1 -1
  166. package/lib/view/AcTrLayout.d.ts +5 -0
  167. package/lib/view/AcTrLayout.d.ts.map +1 -1
  168. package/lib/view/AcTrLayout.js +26 -0
  169. package/lib/view/AcTrLayout.js.map +1 -1
  170. package/lib/view/AcTrLayoutView.d.ts.map +1 -1
  171. package/lib/view/AcTrLayoutView.js +3 -1
  172. package/lib/view/AcTrLayoutView.js.map +1 -1
  173. package/lib/view/AcTrProgressiveOpenFitController.d.ts +1 -0
  174. package/lib/view/AcTrProgressiveOpenFitController.d.ts.map +1 -1
  175. package/lib/view/AcTrProgressiveOpenFitController.js +2 -1
  176. package/lib/view/AcTrProgressiveOpenFitController.js.map +1 -1
  177. package/lib/view/AcTrScene.d.ts +7 -0
  178. package/lib/view/AcTrScene.d.ts.map +1 -1
  179. package/lib/view/AcTrScene.js +78 -48
  180. package/lib/view/AcTrScene.js.map +1 -1
  181. package/lib/view/AcTrView2d.d.ts +234 -18
  182. package/lib/view/AcTrView2d.d.ts.map +1 -1
  183. package/lib/view/AcTrView2d.js +1017 -296
  184. package/lib/view/AcTrView2d.js.map +1 -1
  185. package/package.json +17 -10
  186. package/src/find/AcApFindNavigator.ts +190 -0
  187. package/src/find/findDrawing.ts +211 -0
  188. package/src/find/findText.ts +595 -0
  189. package/src/find/index.ts +2 -0
  190. package/src/ui/icons.ts +4 -0
@@ -96,7 +96,7 @@ 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 { AcCmUiYieldGate, AcDbAttribute, AcDbBlockReference, acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, acgiForegroundColorForBackground, log } from '@mlightcad/data-model';
99
+ import { AcCmUiYieldGate, AcDbAttribute, AcDbBlockReference, acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbRenderingCache, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, acgiForegroundColorForBackground, log } from '@mlightcad/data-model';
100
100
  import { AcDbSystemVariables } from '@mlightcad/data-model';
101
101
  import { FontManager } from '@mlightcad/mtext-renderer';
102
102
  import { AcTrEntity, AcTrGlyphEntity, AcTrGroup, AcTrMTextRenderer, AcTrRenderer, AcTrViewportView, hasPendingComplexLineTypeGlyphs, setAcTrDrawOrderZAllocator } from '@mlightcad/three-renderer';
@@ -115,9 +115,11 @@ import { acedEntityIntersectsSelectionBox, acedNeedsCrossingGeometryRefine } fro
115
115
  import { isEffectiveSpatialQueryHit } from '../editor/view/AcEdSpatialQueryResult';
116
116
  import { AcTrGeometryUtil } from '../util';
117
117
  import { acapRunDatabaseEdit } from '../util/AcApDatabaseEdit';
118
+ import { collectTextStyleFontNames, planTextStyleFontPreload } from '../util/AcApTextStyleFontPreload';
118
119
  import { ACAP_READING_MODE_BACKGROUND, AcApReadingModeState } from './AcApReadingMode';
119
120
  import { trySelectReviewOverlay, trySelectReviewOverlaysByBox } from './AcEdReviewOverlayPick';
120
121
  import { AcEdViewKeyHandler } from './AcEdViewKeyHandler';
122
+ import { absorbConvertQueue, adaptConvertSliceSize, createConvertPending, hasPendingActiveOwner } from './AcTrConvertQueue';
121
123
  import { shouldExtendBboxForDirectEntity, tryBuildDirectEntityMeta } from './AcTrDirectBatch';
122
124
  import { AcTrEntityDisplayController } from './AcTrEntityDisplayController';
123
125
  import { assertAcTrGroupWcsBboxesConsistent, unionGroupWcsChildBoxes } from './AcTrGroupWcsBboxAssert';
@@ -270,6 +272,16 @@ var AcTrView2d = /** @class */ (function (_super) {
270
272
  * geometry paints incrementally while the open overlay is still visible.
271
273
  */
272
274
  _this._progressiveRendering = false;
275
+ /**
276
+ * When true, open-time convert includes off/frozen-layer entities so HTML
277
+ * export does not need a second convert pass for those entities.
278
+ */
279
+ _this._convertInvisibleLayers = false;
280
+ /**
281
+ * When false, {@link batchConvert} skips cooperative setTimeout yields.
282
+ * Headless CLI sets this false; interactive viewers leave it true.
283
+ */
284
+ _this._cooperativeYield = true;
273
285
  /**
274
286
  * Serial convert queue for progressive opens. Chunks from `entityAppended`
275
287
  * enqueue here and a single drain loop runs {@link batchConvert}, so scene
@@ -277,6 +289,13 @@ var AcTrView2d = /** @class */ (function (_super) {
277
289
  * `setTimeout` storm after the loading overlay hides.
278
290
  */
279
291
  _this._convertQueue = [];
292
+ /**
293
+ * Paper-space block table record ids whose viewport entities were converted
294
+ * before an {@link AcTrLayoutView} existed, so {@link batchConvert} could
295
+ * not create the {@link AcTrViewportView}. Repaired when that layout view
296
+ * is created.
297
+ */
298
+ _this._viewportRepairOwnerIds = new Set();
280
299
  /** In-flight progressive convert drain; shared so awaiters wait for the queue. */
281
300
  _this._convertDrainPromise = null;
282
301
  /**
@@ -285,8 +304,15 @@ var AcTrView2d = /** @class */ (function (_super) {
285
304
  * document open.
286
305
  */
287
306
  _this._convertEpoch = 0;
288
- /** Last time progressive open marked the canvas dirty for paint. */
307
+ /** Last time progressive open actually painted mid-convert. */
289
308
  _this._lastProgressivePaintAt = 0;
309
+ /**
310
+ * When true, the next progressive mid-open paint must not be throttled
311
+ * (camera moved — user pan/zoom or incremental auto-fit).
312
+ */
313
+ _this._progressivePaintUrgent = false;
314
+ /** Pause convert until this `performance.now()` after user input. */
315
+ _this._progressiveInputPauseUntil = 0;
290
316
  /** Mid-open WebGL paints while progressive convert was still running. */
291
317
  _this._progressivePaintCount = 0;
292
318
  /** Cooperative yields taken inside progressive {@link batchConvert}. */
@@ -342,6 +368,21 @@ var AcTrView2d = /** @class */ (function (_super) {
342
368
  _this._isDirty = true;
343
369
  }
344
370
  });
371
+ /**
372
+ * Promise that resolves when text-style fonts needed by deferred glyph jobs
373
+ * are ready. Shared so concurrent deferred jobs do not each await a long
374
+ * font preload.
375
+ */
376
+ _this._deferredFontsReady = null;
377
+ /** Convert-phase wall timers (ms) for OPENPERF diagnostics. */
378
+ _this._convertPhaseMs = {
379
+ finishGeometry: 0,
380
+ handleGroup: 0,
381
+ addEntity: 0,
382
+ awaitFonts: 0
383
+ };
384
+ /** Last text-style font preload plan (for OPENPERF). */
385
+ _this._fontPreloadDiag = null;
345
386
  _this.animate = function () {
346
387
  var _a;
347
388
  _this._rafId = requestAnimationFrame(_this.animate);
@@ -350,17 +391,39 @@ var AcTrView2d = /** @class */ (function (_super) {
350
391
  camera: _this.internalCamera
351
392
  });
352
393
  var stillLoading = _this._numOfEntitiesToProcess > 0;
353
- var deferRenderWhileLoading = stillLoading && !_this._progressiveRendering;
354
- if (!_this._isDirty && !_this._htmlDirty && !stillLoading)
394
+ var sceneBusy = stillLoading || _this._pendingGeometryJobs > 0;
395
+ // Headless non-progressive (`cooperativeYield` false): skip WebGL until
396
+ // idle. Export reads scene batches, not the framebuffer, and software
397
+ // WebGL + uncapped rAF spent most of open time rasterizing. Interactive
398
+ // non-progressive still paints under "Rendering drawing ..." — throttled
399
+ // below so each glyph completion does not redraw the full scene.
400
+ var deferRenderWhileLoading = !_this._progressiveRendering && !_this._cooperativeYield && sceneBusy;
401
+ if (!_this._isDirty && !_this._htmlDirty && !sceneBusy) {
355
402
  return;
403
+ }
356
404
  if (deferRenderWhileLoading)
357
405
  return;
358
406
  if (!_this._isDirty && !_this._htmlDirty)
359
407
  return;
408
+ // Mid-open throttle: progressive convert paints, and interactive
409
+ // non-progressive paints while the scene is still busy. Camera changes
410
+ // set `_progressivePaintUrgent` so pan/zoom still redraw now.
411
+ var countMidOpenPaint = _this.shouldCountMidOpenPaint(stillLoading, sceneBusy);
412
+ var throttleOpenPaint = _this._isDirty && !_this._progressivePaintUrgent && countMidOpenPaint;
413
+ if (throttleOpenPaint) {
414
+ var intervalMs = _this._progressiveRendering
415
+ ? AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS
416
+ : AcTrView2d.NON_PROGRESSIVE_OPEN_PAINT_INTERVAL_MS;
417
+ if (performance.now() - _this._lastProgressivePaintAt < intervalMs) {
418
+ return;
419
+ }
420
+ }
360
421
  var needsRedraw = false;
361
422
  if (_this._isDirty) {
362
- if (_this._progressiveRendering && stillLoading) {
423
+ if (countMidOpenPaint) {
363
424
  _this._progressivePaintCount++;
425
+ _this._lastProgressivePaintAt = performance.now();
426
+ _this._progressivePaintUrgent = false;
364
427
  }
365
428
  needsRedraw = _this._layoutViewManager.render(_this._scene);
366
429
  }
@@ -378,6 +441,20 @@ var AcTrView2d = /** @class */ (function (_super) {
378
441
  _this._htmlDirty = false;
379
442
  };
380
443
  _this._disposeCanvasTouchCallout = disposeCanvasTouchCallout;
444
+ var onProgressiveInput = function () { return _this.noteProgressiveUserInput(); };
445
+ renderer.domElement.addEventListener('pointerdown', onProgressiveInput, {
446
+ capture: true
447
+ });
448
+ renderer.domElement.addEventListener('wheel', onProgressiveInput, {
449
+ capture: true,
450
+ passive: true
451
+ });
452
+ _this._disposeProgressiveInputWatch = function () {
453
+ renderer.domElement.removeEventListener('pointerdown', onProgressiveInput, { capture: true });
454
+ renderer.domElement.removeEventListener('wheel', onProgressiveInput, {
455
+ capture: true
456
+ });
457
+ };
381
458
  _this._gripManager = new AcEdGripManager(_this);
382
459
  _this._keyHandler = new AcEdViewKeyHandler(_this);
383
460
  if (options.calculateSizeCallback) {
@@ -695,6 +772,59 @@ var AcTrView2d = /** @class */ (function (_super) {
695
772
  _this.resetDeferredGeometryQueue();
696
773
  return _this;
697
774
  }
775
+ AcTrView2d.prototype.ensureDeferredFontsReady = function () {
776
+ var _this = this;
777
+ if (!this._deferredFontsReady) {
778
+ var t0_1 = performance.now();
779
+ // Preset (fallback) + STYLE-table faces must both be ready before glyph
780
+ // bake. Linework convert does not await this promise.
781
+ var ready = Promise.all([
782
+ this.awaitPresetFontsReadySafe(),
783
+ this.awaitTextStyleFontsReady()
784
+ ]).then(function () { return undefined; }, function () { return undefined; });
785
+ this._deferredFontsReady = ready.then(function () {
786
+ _this._convertPhaseMs.awaitFonts += performance.now() - t0_1;
787
+ });
788
+ }
789
+ return this._deferredFontsReady;
790
+ };
791
+ /**
792
+ * Awaits DocManager preset preload when the singleton exists.
793
+ * No-op in unit tests that construct a view without a manager.
794
+ */
795
+ AcTrView2d.prototype.awaitPresetFontsReadySafe = function () {
796
+ return __awaiter(this, void 0, void 0, function () {
797
+ var singleton;
798
+ return __generator(this, function (_a) {
799
+ switch (_a.label) {
800
+ case 0:
801
+ singleton = AcApDocManager;
802
+ if (!singleton._instance) {
803
+ return [2 /*return*/];
804
+ }
805
+ return [4 /*yield*/, singleton._instance.awaitPresetFontsReady()];
806
+ case 1:
807
+ _a.sent();
808
+ return [2 /*return*/];
809
+ }
810
+ });
811
+ });
812
+ };
813
+ /**
814
+ * Returns and resets convert-phase wall timers collected during the last open.
815
+ */
816
+ AcTrView2d.prototype.takeConvertPhaseTimings = function () {
817
+ var snapshot = __assign(__assign({}, this._convertPhaseMs), { fontPreload: this._fontPreloadDiag
818
+ ? __assign({}, this._fontPreloadDiag) : undefined });
819
+ this._convertPhaseMs = {
820
+ finishGeometry: 0,
821
+ handleGroup: 0,
822
+ addEntity: 0,
823
+ awaitFonts: 0
824
+ };
825
+ this._fontPreloadDiag = null;
826
+ return snapshot;
827
+ };
698
828
  AcTrView2d.prototype.getPointerSelectionAction = function (e) {
699
829
  return resolvePointerSelectionAction(e);
700
830
  };
@@ -831,10 +961,9 @@ var AcTrView2d = /** @class */ (function (_super) {
831
961
  * The open-file progress overlay uses this when
832
962
  * {@link AcApOpenDatabaseOptions.progressiveRendering} is off, so
833
963
  * "Rendering drawing ..." stays up until deferred glyph jobs finish.
834
- * When progressive rendering is on, the overlay uses
835
- * {@link isConvertingEntities} and may hide while text geometry continues
836
- * in the deferred pool (pan/zoom enabled). Deprecated `waitForTextGeometry`
837
- * no longer selects this gate.
964
+ * When progressive rendering is on, the overlay hides at CONVERSION `END`
965
+ * and does not wait on this flag (pan/zoom enabled while convert continues).
966
+ * Deprecated `waitForTextGeometry` no longer selects this gate.
838
967
  */
839
968
  get: function () {
840
969
  return this._numOfEntitiesToProcess > 0 || this._pendingGeometryJobs > 0;
@@ -910,11 +1039,39 @@ var AcTrView2d = /** @class */ (function (_super) {
910
1039
  enumerable: false,
911
1040
  configurable: true
912
1041
  });
1042
+ Object.defineProperty(AcTrView2d.prototype, "convertInvisibleLayers", {
1043
+ /**
1044
+ * When true, open-time convert includes off/frozen-layer entities.
1045
+ */
1046
+ get: function () {
1047
+ return this._convertInvisibleLayers;
1048
+ },
1049
+ set: function (value) {
1050
+ this._convertInvisibleLayers = value;
1051
+ },
1052
+ enumerable: false,
1053
+ configurable: true
1054
+ });
1055
+ Object.defineProperty(AcTrView2d.prototype, "cooperativeYield", {
1056
+ /**
1057
+ * When false, entity convert skips cooperative yields and non-progressive
1058
+ * mid-open WebGL paints (headless export). Interactive viewers keep this
1059
+ * `true` so "Rendering drawing ..." can still refresh the canvas.
1060
+ */
1061
+ get: function () {
1062
+ return this._cooperativeYield;
1063
+ },
1064
+ set: function (value) {
1065
+ this._cooperativeYield = value;
1066
+ },
1067
+ enumerable: false,
1068
+ configurable: true
1069
+ });
913
1070
  Object.defineProperty(AcTrView2d.prototype, "progressiveOpenStats", {
914
1071
  /**
915
1072
  * Open-convert counters for OPENPROF / palette (cooperative yields during
916
- * convert, and mid-open paints when progressive rendering is enabled).
917
- * Reset when progressive mode is enabled for an open.
1073
+ * convert, and throttled mid-open paints). Reset when progressive mode is
1074
+ * enabled for an open.
918
1075
  */
919
1076
  get: function () {
920
1077
  return {
@@ -929,6 +1086,85 @@ var AcTrView2d = /** @class */ (function (_super) {
929
1086
  this._progressivePaintCount = 0;
930
1087
  this._progressiveYieldCount = 0;
931
1088
  this._lastProgressivePaintAt = 0;
1089
+ this._progressivePaintUrgent = false;
1090
+ this._progressiveInputPauseUntil = 0;
1091
+ };
1092
+ /**
1093
+ * True while a recent canvas pointerdown/wheel asked convert to pause.
1094
+ * Does not treat hover mousemove as a pause (that starved convert).
1095
+ */
1096
+ AcTrView2d.prototype.isProgressiveGesturePauseActive = function () {
1097
+ return performance.now() < this._progressiveInputPauseUntil;
1098
+ };
1099
+ /**
1100
+ * True when the browser reports pending discrete input (clicks, keys).
1101
+ * Continuous events (mousemove) are excluded so hover does not throttle
1102
+ * progressive convert.
1103
+ */
1104
+ AcTrView2d.prototype.isProgressiveDiscreteInputPending = function () {
1105
+ var _a;
1106
+ var scheduling = navigator.scheduling;
1107
+ return ((_a = scheduling === null || scheduling === void 0 ? void 0 : scheduling.isInputPending) === null || _a === void 0 ? void 0 : _a.call(scheduling)) === true;
1108
+ };
1109
+ /**
1110
+ * Notes user interaction during progressive convert so paints stay urgent
1111
+ * and the drain loop pauses briefly for OrbitControls.
1112
+ */
1113
+ AcTrView2d.prototype.noteProgressiveUserInput = function () {
1114
+ if (!this._progressiveRendering || this._numOfEntitiesToProcess <= 0) {
1115
+ return;
1116
+ }
1117
+ this._progressivePaintUrgent = true;
1118
+ this._isDirty = true;
1119
+ this._progressiveInputPauseUntil =
1120
+ performance.now() + AcTrView2d.PROGRESSIVE_INPUT_PAUSE_MS;
1121
+ };
1122
+ /**
1123
+ * Yields during progressive convert: honor the time budget; after
1124
+ * pointerdown/wheel pause briefly for OrbitControls; on discrete pending
1125
+ * input yield once so handlers run without extending the pause window.
1126
+ */
1127
+ AcTrView2d.prototype.progressiveYieldForInput = function (yieldGate, yieldToEventLoop) {
1128
+ return __awaiter(this, void 0, void 0, function () {
1129
+ var didYield;
1130
+ return __generator(this, function (_a) {
1131
+ switch (_a.label) {
1132
+ case 0:
1133
+ if (!this.isProgressiveGesturePauseActive()) return [3 /*break*/, 5];
1134
+ return [4 /*yield*/, yieldToEventLoop()];
1135
+ case 1:
1136
+ _a.sent();
1137
+ _a.label = 2;
1138
+ case 2:
1139
+ if (!(performance.now() < this._progressiveInputPauseUntil)) return [3 /*break*/, 4];
1140
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 16); })];
1141
+ case 3:
1142
+ _a.sent();
1143
+ return [3 /*break*/, 2];
1144
+ case 4:
1145
+ this._progressiveYieldCount++;
1146
+ return [2 /*return*/];
1147
+ case 5:
1148
+ if (!this.isProgressiveDiscreteInputPending()) return [3 /*break*/, 7];
1149
+ return [4 /*yield*/, yieldToEventLoop()];
1150
+ case 6:
1151
+ _a.sent();
1152
+ this._progressiveYieldCount++;
1153
+ return [2 /*return*/];
1154
+ case 7:
1155
+ if (!yieldGate) {
1156
+ return [2 /*return*/];
1157
+ }
1158
+ return [4 /*yield*/, yieldGate.maybeYield(yieldToEventLoop)];
1159
+ case 8:
1160
+ didYield = _a.sent();
1161
+ if (didYield) {
1162
+ this._progressiveYieldCount++;
1163
+ }
1164
+ return [2 /*return*/];
1165
+ }
1166
+ });
1167
+ });
932
1168
  };
933
1169
  /**
934
1170
  * Enables progressive camera framing while entities are batch-converted at
@@ -1375,6 +1611,31 @@ var AcTrView2d = /** @class */ (function (_super) {
1375
1611
  includeLayouts = (options === null || options === void 0 ? void 0 : options.includeLayouts) !== false;
1376
1612
  db = AcApDocManager.instance.curDocument.database;
1377
1613
  pending = [];
1614
+ // Open streams entities through `drainConvertQueue` and claims each id
1615
+ // before the scene commit (deferred glyphs / INSERT text). Headless
1616
+ // export used to scan here as soon as parse returned, while that drain
1617
+ // was still running. Claimed ids are not in the scene yet, so they
1618
+ // looked missing and `batchConvert({ forExport: true })` expanded them
1619
+ // again — serially, because export convert does not defer glyph work.
1620
+ // On a text-heavy drawing that second pass dominates wall time and
1621
+ // leaves duplicate geometry in the snapshot. Wait until the open drain
1622
+ // has claimed or committed every streamed entity, then skip those ids.
1623
+ return [4 /*yield*/, this.drainConvertQueue()
1624
+ // Paper-space tabs the user never visited exist in the layout table but
1625
+ // may be missing from the scene until first switch. HTML export needs
1626
+ // every layout's geometry, so register those BTRs before collecting.
1627
+ ];
1628
+ case 1:
1629
+ // Open streams entities through `drainConvertQueue` and claims each id
1630
+ // before the scene commit (deferred glyphs / INSERT text). Headless
1631
+ // export used to scan here as soon as parse returned, while that drain
1632
+ // was still running. Claimed ids are not in the scene yet, so they
1633
+ // looked missing and `batchConvert({ forExport: true })` expanded them
1634
+ // again — serially, because export convert does not defer glyph work.
1635
+ // On a text-heavy drawing that second pass dominates wall time and
1636
+ // leaves duplicate geometry in the snapshot. Wait until the open drain
1637
+ // has claimed or committed every streamed entity, then skip those ids.
1638
+ _j.sent();
1378
1639
  layoutTable = (_h = db.objects) === null || _h === void 0 ? void 0 : _h.layout;
1379
1640
  if (includeLayouts && (layoutTable === null || layoutTable === void 0 ? void 0 : layoutTable.newIterator)) {
1380
1641
  try {
@@ -1404,7 +1665,7 @@ var AcTrView2d = /** @class */ (function (_super) {
1404
1665
  if (!blockTableRecord) {
1405
1666
  continue;
1406
1667
  }
1407
- pending.push.apply(pending, __spreadArray([], __read(this._entityDisplay.collectMissingEntitiesForExport(blockTableRecord, function (objectId) { return _this.hasEntity(objectId); }, includeInvisibleLayers)), false));
1668
+ pending.push.apply(pending, __spreadArray([], __read(this._entityDisplay.collectMissingEntitiesForExport(blockTableRecord, function (objectId) { return _this.isEntityConvertScheduled(objectId); }, includeInvisibleLayers)), false));
1408
1669
  }
1409
1670
  }
1410
1671
  catch (e_5_1) { e_5 = { error: e_5_1 }; }
@@ -1414,14 +1675,14 @@ var AcTrView2d = /** @class */ (function (_super) {
1414
1675
  }
1415
1676
  finally { if (e_5) throw e_5.error; }
1416
1677
  }
1417
- if (!(pending.length > 0)) return [3 /*break*/, 2];
1678
+ if (!(pending.length > 0)) return [3 /*break*/, 3];
1418
1679
  this._numOfEntitiesToProcess += pending.length;
1419
1680
  return [4 /*yield*/, this.batchConvert(pending, { forExport: true })];
1420
- case 1:
1681
+ case 2:
1421
1682
  _j.sent();
1422
- _j.label = 2;
1423
- case 2: return [4 /*yield*/, this.waitUntilIdle()];
1424
- case 3:
1683
+ _j.label = 3;
1684
+ case 3: return [4 /*yield*/, this.waitUntilIdle(120000)];
1685
+ case 4:
1425
1686
  idle = _j.sent();
1426
1687
  if (!idle) {
1427
1688
  log.warn('[AcTrView2d] Timed out waiting for deferred geometry before export');
@@ -2254,6 +2515,17 @@ var AcTrView2d = /** @class */ (function (_super) {
2254
2515
  AcTrView2d.prototype.hasEntity = function (objectId) {
2255
2516
  return this._scene.hasEntity(objectId);
2256
2517
  };
2518
+ /**
2519
+ * True when an entity is already in the scene or claimed by an in-flight
2520
+ * convert (including deferred glyph commits that have not called
2521
+ * `addEntity` yet).
2522
+ */
2523
+ AcTrView2d.prototype.isEntityConvertScheduled = function (objectId) {
2524
+ var id = String(objectId !== null && objectId !== void 0 ? objectId : '');
2525
+ if (!id)
2526
+ return false;
2527
+ return this.hasEntity(objectId) || this._claimedConvertObjectIds.has(id);
2528
+ };
2257
2529
  /**
2258
2530
  * @inheritdoc
2259
2531
  */
@@ -2412,6 +2684,17 @@ var AcTrView2d = /** @class */ (function (_super) {
2412
2684
  this._initializedLayouts.add(layoutBtrId);
2413
2685
  this._externallyFramedLayouts.add(layoutBtrId);
2414
2686
  };
2687
+ /**
2688
+ * Whether the layout has already been visited (or framed by the
2689
+ * application layer), i.e. a switch to it will not run the first-visit
2690
+ * auto zoom. Lets callers that frame a layout themselves (FIND) call
2691
+ * {@link markLayoutAsInitialized} only when it is still needed.
2692
+ *
2693
+ * @param layoutBtrId Input the block table record id of the layout
2694
+ */
2695
+ AcTrView2d.prototype.isLayoutInitialized = function (layoutBtrId) {
2696
+ return this._initializedLayouts.has(layoutBtrId);
2697
+ };
2415
2698
  /**
2416
2699
  * Returns the 2D box of drawable geometry in the active layout.
2417
2700
  *
@@ -2513,6 +2796,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2513
2796
  this._convertEpoch++;
2514
2797
  this.clearFontLoadedRedrawTimer();
2515
2798
  this._convertQueue.length = 0;
2799
+ this._viewportRepairOwnerIds.clear();
2516
2800
  this._numOfEntitiesToProcess = 0;
2517
2801
  this._claimedConvertObjectIds.clear();
2518
2802
  this._entityConvertGeneration.clear();
@@ -2527,6 +2811,8 @@ var AcTrView2d = /** @class */ (function (_super) {
2527
2811
  this._externallyFramedLayouts.clear();
2528
2812
  this._loadingLayouts.clear();
2529
2813
  this._renderer.dispose();
2814
+ // Drop block templates when the scene is torn down (next open / close).
2815
+ AcDbRenderingCache.instance.clear();
2530
2816
  eventBus.emit('missed-data-changed', {});
2531
2817
  };
2532
2818
  /**
@@ -2557,6 +2843,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2557
2843
  this.clearFontLoadedRedrawTimer();
2558
2844
  this.cancelOpenLineworkFrame();
2559
2845
  this._convertQueue.length = 0;
2846
+ this._viewportRepairOwnerIds.clear();
2560
2847
  this._numOfEntitiesToProcess = 0;
2561
2848
  this._claimedConvertObjectIds.clear();
2562
2849
  this._entityConvertGeneration.clear();
@@ -2589,6 +2876,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2589
2876
  this.clearFontLoadedRedrawTimer();
2590
2877
  this.cancelOpenLineworkFrame();
2591
2878
  this._convertQueue.length = 0;
2879
+ this._viewportRepairOwnerIds.clear();
2592
2880
  this._numOfEntitiesToProcess = 0;
2593
2881
  this._claimedConvertObjectIds.clear();
2594
2882
  this._entityConvertGeneration.clear();
@@ -2663,21 +2951,7 @@ var AcTrView2d = /** @class */ (function (_super) {
2663
2951
  _a.label = 3;
2664
2952
  case 3: return [2 /*return*/];
2665
2953
  case 4:
2666
- this._convertDrainPromise = (function () { return __awaiter(_this, void 0, void 0, function () {
2667
- var batch;
2668
- return __generator(this, function (_a) {
2669
- switch (_a.label) {
2670
- case 0:
2671
- if (!(this._convertQueue.length > 0)) return [3 /*break*/, 2];
2672
- batch = this._convertQueue.splice(0, this._convertQueue.length);
2673
- return [4 /*yield*/, this.batchConvert(batch)];
2674
- case 1:
2675
- _a.sent();
2676
- return [3 /*break*/, 0];
2677
- case 2: return [2 /*return*/];
2678
- }
2679
- });
2680
- }); })().finally(function () {
2954
+ this._convertDrainPromise = this.runConvertDrain().finally(function () {
2681
2955
  _this._convertDrainPromise = null;
2682
2956
  });
2683
2957
  return [4 /*yield*/, this._convertDrainPromise];
@@ -2688,9 +2962,188 @@ var AcTrView2d = /** @class */ (function (_super) {
2688
2962
  });
2689
2963
  });
2690
2964
  };
2965
+ /**
2966
+ * Converts the queued entities in slices, active layout first.
2967
+ *
2968
+ * `drainConvertQueue` used to hand the whole queue to one
2969
+ * {@link batchConvert}. Model-space block references queued ahead of the
2970
+ * sheet then blocked the sheet's frame and viewports for the entire batch,
2971
+ * and a layout activated during that batch stayed starved until it
2972
+ * finished. Slices return here so a newly active layout is ordered ahead
2973
+ * of the remainder. Paper-active opens use a modest slice budget so the
2974
+ * sheet can paint soon; model-active opens match the inner
2975
+ * {@link batchConvert} yield cadence and grow slices for throughput so
2976
+ * Model-first drawings are not slowed by fine outer slicing. Headless
2977
+ * opens (`cooperativeYield === false`) still convert in one batch after
2978
+ * that reorder.
2979
+ */
2980
+ AcTrView2d.prototype.runConvertDrain = function () {
2981
+ return __awaiter(this, void 0, void 0, function () {
2982
+ var epoch, interactive, slice, lastYieldAt, state, queued, activeNow, activeChanged, end, startedAt, took, modelActive, modelYieldBudget, budget;
2983
+ return __generator(this, function (_a) {
2984
+ switch (_a.label) {
2985
+ case 0:
2986
+ epoch = this._convertEpoch;
2987
+ interactive = this._cooperativeYield;
2988
+ slice = !interactive
2989
+ ? Number.POSITIVE_INFINITY
2990
+ : this.activeLayoutBtrId === this.modelSpaceBtrId
2991
+ ? AcTrView2d.CONVERT_SLICE_MAX
2992
+ : 1;
2993
+ lastYieldAt = performance.now();
2994
+ state = createConvertPending(this._convertQueue.splice(0, this._convertQueue.length), this.activeLayoutBtrId);
2995
+ this.repairViewportViewsFor(state.activeOwnerId);
2996
+ _a.label = 1;
2997
+ case 1:
2998
+ if (!(state.cursor < state.pending.length ||
2999
+ this._convertQueue.length > 0)) return [3 /*break*/, 12];
3000
+ if (this._convertEpoch !== epoch)
3001
+ return [2 /*return*/];
3002
+ queued = this._convertQueue.splice(0, this._convertQueue.length);
3003
+ activeNow = this.activeLayoutBtrId;
3004
+ activeChanged = activeNow !== state.activeOwnerId;
3005
+ state = absorbConvertQueue(state, queued, activeNow);
3006
+ if (activeChanged) {
3007
+ this.repairViewportViewsFor(activeNow);
3008
+ // Drop back to a small slice so the newly active layout's own
3009
+ // entities are not buried inside a leftover large model-space bite.
3010
+ if (interactive)
3011
+ slice = 1;
3012
+ }
3013
+ this.flushViewportViewRepairs();
3014
+ if (state.cursor >= state.pending.length)
3015
+ return [3 /*break*/, 12];
3016
+ end = Math.min(state.pending.length, state.cursor + slice);
3017
+ startedAt = performance.now();
3018
+ return [4 /*yield*/, this.batchConvert(state.pending.slice(state.cursor, end))];
3019
+ case 2:
3020
+ _a.sent();
3021
+ state.cursor = end;
3022
+ if (this._convertEpoch !== epoch)
3023
+ return [2 /*return*/];
3024
+ if (!interactive)
3025
+ return [3 /*break*/, 1];
3026
+ took = performance.now() - startedAt;
3027
+ modelActive = state.activeOwnerId === this.modelSpaceBtrId;
3028
+ modelYieldBudget = this._progressiveRendering
3029
+ ? AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS
3030
+ : AcTrView2d.OPEN_CONVERT_YIELD_BUDGET_MS;
3031
+ budget = modelActive
3032
+ ? modelYieldBudget
3033
+ : AcTrView2d.CONVERT_SLICE_BUDGET_MS;
3034
+ slice = adaptConvertSliceSize({
3035
+ slice: slice,
3036
+ tookMs: took,
3037
+ budgetMs: budget,
3038
+ maxSlice: AcTrView2d.CONVERT_SLICE_MAX,
3039
+ // Fine 2× growth only while a paper sheet still needs converting.
3040
+ // Model-active (or paper already drained) prefers 4× throughput.
3041
+ activeLayoutPending: !modelActive && hasPendingActiveOwner(state)
3042
+ });
3043
+ if (!(this._progressiveRendering &&
3044
+ this.isProgressiveGesturePauseActive())) return [3 /*break*/, 7];
3045
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
3046
+ case 3:
3047
+ _a.sent();
3048
+ _a.label = 4;
3049
+ case 4:
3050
+ if (!(performance.now() < this._progressiveInputPauseUntil)) return [3 /*break*/, 6];
3051
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 16); })];
3052
+ case 5:
3053
+ _a.sent();
3054
+ return [3 /*break*/, 4];
3055
+ case 6:
3056
+ lastYieldAt = performance.now();
3057
+ if (this._convertEpoch !== epoch)
3058
+ return [2 /*return*/];
3059
+ return [3 /*break*/, 11];
3060
+ case 7:
3061
+ if (!(this._progressiveRendering &&
3062
+ this.isProgressiveDiscreteInputPending())) return [3 /*break*/, 9];
3063
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
3064
+ case 8:
3065
+ _a.sent();
3066
+ lastYieldAt = performance.now();
3067
+ if (this._convertEpoch !== epoch)
3068
+ return [2 /*return*/];
3069
+ return [3 /*break*/, 11];
3070
+ case 9:
3071
+ if (!(performance.now() - lastYieldAt >= budget)) return [3 /*break*/, 11];
3072
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
3073
+ case 10:
3074
+ _a.sent();
3075
+ lastYieldAt = performance.now();
3076
+ if (this._convertEpoch !== epoch)
3077
+ return [2 /*return*/];
3078
+ _a.label = 11;
3079
+ case 11: return [3 /*break*/, 1];
3080
+ case 12:
3081
+ this.repairViewportViewsFor(this.activeLayoutBtrId);
3082
+ this.flushViewportViewRepairs();
3083
+ return [2 /*return*/];
3084
+ }
3085
+ });
3086
+ });
3087
+ };
3088
+ /**
3089
+ * Creates missing {@link AcTrViewportView}s for one paper layout.
3090
+ *
3091
+ * Safe to call before the layout's entities have been committed to the
3092
+ * scene: the database block table record already holds the viewports.
3093
+ * Model space is skipped so a large model is not walked on every slice.
3094
+ *
3095
+ * @param layoutBtrId - Block table record id of the layout to repair.
3096
+ */
3097
+ AcTrView2d.prototype.repairViewportViewsFor = function (layoutBtrId) {
3098
+ var _a;
3099
+ if (!layoutBtrId || layoutBtrId === this.modelSpaceBtrId)
3100
+ return;
3101
+ var layoutView = this._layoutViewManager.getAt(layoutBtrId);
3102
+ if (!layoutView || layoutView.viewportCount > 0) {
3103
+ if (layoutView && layoutView.viewportCount > 0) {
3104
+ this._viewportRepairOwnerIds.delete(layoutBtrId);
3105
+ }
3106
+ return;
3107
+ }
3108
+ try {
3109
+ var db = (_a = AcApDocManager.instance.curDocument) === null || _a === void 0 ? void 0 : _a.database;
3110
+ var blockTableRecord = db === null || db === void 0 ? void 0 : db.tables.blockTable.getIdAt(layoutBtrId);
3111
+ if (!blockTableRecord)
3112
+ return;
3113
+ var before = layoutView.viewportCount;
3114
+ this.ensureViewportViews(blockTableRecord, layoutView);
3115
+ if (layoutView.viewportCount > before) {
3116
+ this._isDirty = true;
3117
+ }
3118
+ // The block table record was readable. Drop the id even when every
3119
+ // viewport was the default paper-space one, so a model-sized record
3120
+ // is not walked again on the next slice.
3121
+ this._viewportRepairOwnerIds.delete(layoutBtrId);
3122
+ }
3123
+ catch (error) {
3124
+ log.error('[AcTrView2d] Error repairing viewport views:', error);
3125
+ }
3126
+ };
3127
+ /**
3128
+ * Retries viewport-view creation for owners recorded while their layout
3129
+ * view did not exist yet.
3130
+ */
3131
+ AcTrView2d.prototype.flushViewportViewRepairs = function () {
3132
+ if (this._viewportRepairOwnerIds.size === 0)
3133
+ return;
3134
+ var ownerIds = Array.from(this._viewportRepairOwnerIds);
3135
+ for (var i = 0; i < ownerIds.length; i++) {
3136
+ this.repairViewportViewsFor(ownerIds[i]);
3137
+ }
3138
+ };
2691
3139
  /**
2692
3140
  * Marks the canvas dirty for progressive open, throttled to avoid painting
2693
3141
  * on every entity (which dominated total open time).
3142
+ *
3143
+ * Does not stamp {@link _lastProgressivePaintAt} — that updates only when
3144
+ * {@link animate} actually paints. Does not set
3145
+ * {@link _progressivePaintUrgent}; camera moves do that so pan/zoom stay
3146
+ * immediate while convert-driven paints remain rare.
2694
3147
  */
2695
3148
  AcTrView2d.prototype.markProgressiveDirty = function (force) {
2696
3149
  if (force === void 0) { force = false; }
@@ -2699,9 +3152,28 @@ var AcTrView2d = /** @class */ (function (_super) {
2699
3152
  now - this._lastProgressivePaintAt >=
2700
3153
  AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS) {
2701
3154
  this._isDirty = true;
2702
- this._lastProgressivePaintAt = now;
2703
3155
  }
2704
3156
  };
3157
+ /**
3158
+ * Interactive non-progressive open: dirty the canvas so {@link animate} can
3159
+ * refresh under "Rendering drawing ...". Headless (`cooperativeYield`
3160
+ * false) leaves this unset; the pool-drain / convert-end dirty is enough.
3161
+ * {@link animate} applies {@link NON_PROGRESSIVE_OPEN_PAINT_INTERVAL_MS}.
3162
+ */
3163
+ AcTrView2d.prototype.markInteractiveOpenDirty = function () {
3164
+ if (this._progressiveRendering || !this._cooperativeYield) {
3165
+ return;
3166
+ }
3167
+ this._isDirty = true;
3168
+ };
3169
+ /**
3170
+ * True when this frame's WebGL paint should count toward OPENPROF mid-open
3171
+ * paints and use the open-time throttle (unless the camera marked urgent).
3172
+ */
3173
+ AcTrView2d.prototype.shouldCountMidOpenPaint = function (stillLoading, sceneBusy) {
3174
+ return ((this._progressiveRendering && stillLoading) ||
3175
+ (!this._progressiveRendering && this._cooperativeYield && sceneBusy));
3176
+ };
2705
3177
  /**
2706
3178
  * @inheritdoc
2707
3179
  */
@@ -2757,9 +3229,11 @@ var AcTrView2d = /** @class */ (function (_super) {
2757
3229
  * view teardown). Safe to call more than once.
2758
3230
  */
2759
3231
  AcTrView2d.prototype.dispose = function () {
2760
- var _a;
3232
+ var _a, _b;
2761
3233
  (_a = this._disposeCanvasTouchCallout) === null || _a === void 0 ? void 0 : _a.call(this);
2762
3234
  this._disposeCanvasTouchCallout = undefined;
3235
+ (_b = this._disposeProgressiveInputWatch) === null || _b === void 0 ? void 0 : _b.call(this);
3236
+ this._disposeProgressiveInputWatch = undefined;
2763
3237
  this.clearFontLoadedRedrawTimer();
2764
3238
  this.cancelOpenLineworkFrame();
2765
3239
  this.stopAnimationLoop();
@@ -2827,11 +3301,19 @@ var AcTrView2d = /** @class */ (function (_super) {
2827
3301
  layoutView.events.viewChanged.addEventListener(function () {
2828
3302
  _this._progressiveOpenFit.onLayoutViewChanged();
2829
3303
  _this._isDirty = true;
3304
+ // Bypass mid-open paint throttle so pan/zoom (and incremental fit)
3305
+ // remain visible while convert continues.
3306
+ if (_this._progressiveRendering && _this._numOfEntitiesToProcess > 0) {
3307
+ _this._progressivePaintUrgent = true;
3308
+ }
2830
3309
  _this.events.viewChanged.dispatch();
2831
3310
  _this.clearHover();
2832
3311
  });
2833
3312
  this._layoutViewManager.add(layoutView);
2834
3313
  }
3314
+ // Viewport entities may already have been converted while this view was
3315
+ // missing. Creating it is the moment those AcTrViewportViews can exist.
3316
+ this.flushViewportViewRepairs();
2835
3317
  return layoutView;
2836
3318
  };
2837
3319
  /**
@@ -2867,41 +3349,20 @@ var AcTrView2d = /** @class */ (function (_super) {
2867
3349
  var existingLayout = this._scene.layouts.get(layoutBtrId);
2868
3350
  if (existingLayout && existingLayout.isLoaded) {
2869
3351
  // Streamed layouts flip `isLoaded` from `addEntity` before scene
2870
- // commits finish. Still repair the viewport-view race when entities
2871
- // are already in the scene but AcTrViewportView creation was skipped.
2872
- var layoutView_1 = this._layoutViewManager.getAt(layoutBtrId);
2873
- if (existingLayout.entityCount > 0 &&
2874
- layoutView_1 &&
2875
- layoutView_1.viewportCount === 0) {
2876
- this.ensureViewportViews(blockTableRecord, layoutView_1);
2877
- }
3352
+ // commits finish (`entityCount` still 0). Viewport views are read
3353
+ // from the block table record, so repair them even while the sheet's
3354
+ // own entities are still queued behind model space.
3355
+ this.repairViewportViewsFor(layoutBtrId);
2878
3356
  return;
2879
3357
  }
2880
3358
  if (this._loadingLayouts.has(layoutBtrId)) {
2881
3359
  return;
2882
3360
  }
2883
- // Ensure `AcTrViewportView`s exist for every real `AcDbViewport`
2884
- // in this layout when the layout's entities were already streamed
2885
- // in by the document parser. There is a race in the parser-driven
2886
- // load path: `addLayout(layout)` creates the `AcTrLayoutView`,
2887
- // but the parser may dispatch the AcDbViewport entities before
2888
- // that happens. When that races, `batchConvert`'s viewport
2889
- // handler does `_layoutViewManager.getAt(entity.ownerId)`, gets
2890
- // `undefined`, and **silently skips creating the
2891
- // AcTrViewportView**. The reload path below used to mask this by
2892
- // re-running batchConvert after the layoutView existed, but the
2893
- // entityCount-skip optimization that follows removes that
2894
- // side-effect, so we do the viewport-view-only pass explicitly
2895
- // here. Skipped when the layout is empty — in that case the
2896
- // batchConvert path below will create the viewport views directly
2897
- // as it processes each entity.
2898
- var layoutView = this._layoutViewManager.getAt(layoutBtrId);
2899
- if (existingLayout &&
2900
- existingLayout.entityCount > 0 &&
2901
- layoutView &&
2902
- layoutView.viewportCount === 0) {
2903
- this.ensureViewportViews(blockTableRecord, layoutView);
2904
- }
3361
+ // Same repair as the isLoaded path: the layout view can exist before
3362
+ // any of its entities have been committed to the scene. Viewport
3363
+ // entities converted before `addLayout` created that view are recorded
3364
+ // in `_viewportRepairOwnerIds` and attached here.
3365
+ this.repairViewportViewsFor(layoutBtrId);
2905
3366
  // Model space (and any other layout pre-populated by the document
2906
3367
  // parser at open time) lands here without `isLoaded` ever having
2907
3368
  // been flipped — the initial entity stream goes through
@@ -3069,81 +3530,71 @@ var AcTrView2d = /** @class */ (function (_super) {
3069
3530
  *
3070
3531
  * Call when conversion stage `STYLE` ends so the download overlaps later
3071
3532
  * parse stages and linework convert. Does not block the caller — glyph
3072
- * finalize awaits {@link awaitTextStyleFontsReady} instead.
3533
+ * finalize awaits {@link awaitTextStyleFontsReady} (and the open-start
3534
+ * preset gate) instead.
3535
+ *
3536
+ * Drawing-independent preset faces are loaded separately via
3537
+ * {@link AcApDocManager.ensurePresetFontsForOpen} at open start.
3073
3538
  *
3074
3539
  * @param database - Database whose text style table has just been filled.
3075
3540
  */
3076
3541
  AcTrView2d.prototype.startTextStyleFontPreload = function (database) {
3077
- var e_12, _a;
3078
- var _b;
3542
+ var _a, _b;
3079
3543
  var epoch = this._convertEpoch;
3080
3544
  if (this._textStyleFontPreloadEpoch === epoch &&
3081
3545
  this._textStyleFontPreloadPromise) {
3082
3546
  return;
3083
3547
  }
3084
3548
  this._textStyleFontPreloadEpoch = epoch;
3085
- var names = [];
3086
- try {
3087
- var table = database.tables.textStyleTable;
3088
- names = __spreadArray([], __read(((_b = table.fonts) !== null && _b !== void 0 ? _b : [])), false);
3089
- // `fonts` is DXF group 3/4 file names only. TrueType styles store the
3090
- // face on `font` / `extendedFont` (仿宋, SimHei).
3091
- if (table.newIterator) {
3092
- try {
3093
- for (var _c = __values(table.newIterator()), _d = _c.next(); !_d.done; _d = _c.next()) {
3094
- var record = _d.value;
3095
- var style = record.textStyle;
3096
- if (style === null || style === void 0 ? void 0 : style.font)
3097
- names.push(style.font);
3098
- if (style === null || style === void 0 ? void 0 : style.bigFont)
3099
- names.push(style.bigFont);
3100
- if (style === null || style === void 0 ? void 0 : style.extendedFont)
3101
- names.push(style.extendedFont);
3102
- }
3103
- }
3104
- catch (e_12_1) { e_12 = { error: e_12_1 }; }
3105
- finally {
3106
- try {
3107
- if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
3108
- }
3109
- finally { if (e_12) throw e_12.error; }
3110
- }
3111
- }
3112
- }
3113
- catch (_e) {
3114
- names = [];
3115
- }
3116
- // Style fonts alone are not enough: awaitFontsBeforeDraw only *awaits*
3117
- // content/style faces and kicks default/symbol fallbacks in the background.
3118
- // Drawings with empty primary font files (font falls back to the STYLE name)
3119
- // need those fallbacks loaded before the first glyph bake, otherwise Latin
3120
- // (and often all) text is permanently baked as '?'.
3549
+ // Worker mode: do not parse STYLE faces on the main thread (would dual-
3550
+ // parse mesh with workers). Each worker draw awaits its own content/style
3551
+ // faces via awaitFontsBeforeDraw. Preset fallbacks are gated separately
3552
+ // by ensurePresetFontsForOpen (scope: all).
3553
+ //
3554
+ // Main mode: gate deferred glyph jobs on style-table faces only. Unused
3555
+ // preset faces were already started at open — do not re-request here.
3556
+ var styleFonts = collectTextStyleFontNames(database);
3121
3557
  var fallbackFonts = FontManager.instance.getFontsToLoad();
3122
- var preloadNames = __spreadArray([], __read(new Set(__spreadArray(__spreadArray([], __read(names), false), __read(fallbackFonts), false))), false);
3123
- if (preloadNames.length === 0) {
3558
+ var firstDefault = __spreadArray([], __read(FontManager.instance.defaultFonts), false)[0];
3559
+ var plan = planTextStyleFontPreload(styleFonts, fallbackFonts, {
3560
+ firstDefaultFont: firstDefault
3561
+ });
3562
+ var mtextRenderer = AcTrMTextRenderer.getInstance();
3563
+ var parseSite = mtextRenderer.getRenderMode() === 'worker' ? 'worker' : 'main';
3564
+ var singleton = AcApDocManager;
3565
+ var preset = ((_b = (_a = singleton._instance) === null || _a === void 0 ? void 0 : _a.lastPresetFontsForOpen) === null || _b === void 0 ? void 0 : _b.length)
3566
+ ? __spreadArray([], __read(singleton._instance.lastPresetFontsForOpen), false) : __spreadArray([], __read(fallbackFonts), false);
3567
+ this._fontPreloadDiag = {
3568
+ critical: __spreadArray([], __read(plan.critical), false),
3569
+ workerWarm: [],
3570
+ background: [],
3571
+ preset: preset,
3572
+ workerSkipped: 0,
3573
+ parseSite: parseSite
3574
+ };
3575
+ if (parseSite === 'worker') {
3124
3576
  this._textStyleFontPreloadPromise = Promise.resolve();
3125
3577
  return;
3126
3578
  }
3127
- var mtextRenderer = AcTrMTextRenderer.getInstance();
3128
- // Same deadline as AcApDocManager.installFontFileLoadTimeout. Covers both
3129
- // main-thread requestFonts and worker-pool loadFonts so a stalled CDN
3130
- // cannot pin deferred glyph jobs / the open overlay indefinitely.
3579
+ // Same deadline as AcApDocManager.installFontFileLoadTimeout.
3131
3580
  var preloadTimeoutMs = 30000;
3132
- var preload = Promise.all([
3133
- FontManager.instance.requestFonts(preloadNames),
3134
- // Worker isolates have their own FontManager; main-thread requestFonts
3135
- // alone does not populate them before asyncRenderMText.
3136
- mtextRenderer.loadFonts(fallbackFonts)
3137
- ]);
3138
- this._textStyleFontPreloadPromise = Promise.race([
3139
- preload,
3140
- new Promise(function (_, reject) {
3141
- setTimeout(function () {
3142
- reject(new Error("Text-style font preload timed out after ".concat(preloadTimeoutMs, "ms")));
3143
- }, preloadTimeoutMs);
3144
- })
3145
- ]).then(function () { return undefined; }, function () {
3146
- // Glyph draw still falls back via FontManager defaults / '?'.
3581
+ if (plan.critical.length === 0) {
3582
+ this._textStyleFontPreloadPromise = Promise.resolve();
3583
+ return;
3584
+ }
3585
+ var preload = FontManager.instance.requestFonts(plan.critical);
3586
+ this._textStyleFontPreloadPromise = new Promise(function (resolve) {
3587
+ var timer = setTimeout(function () {
3588
+ // Glyph draw still falls back via FontManager defaults / '?'.
3589
+ resolve();
3590
+ }, preloadTimeoutMs);
3591
+ preload.then(function () {
3592
+ clearTimeout(timer);
3593
+ resolve();
3594
+ }, function () {
3595
+ clearTimeout(timer);
3596
+ resolve();
3597
+ });
3147
3598
  });
3148
3599
  };
3149
3600
  /**
@@ -3183,45 +3634,54 @@ var AcTrView2d = /** @class */ (function (_super) {
3183
3634
  */
3184
3635
  AcTrView2d.prototype.finishEntityGeometry = function (threeEntity, _progressive) {
3185
3636
  return __awaiter(this, void 0, void 0, function () {
3186
- var needsGlyphDraw;
3637
+ var t0, isGroup, groupPending, needsGlyphDraw;
3187
3638
  return __generator(this, function (_a) {
3188
3639
  switch (_a.label) {
3189
3640
  case 0:
3190
- needsGlyphDraw = (threeEntity instanceof AcTrGroup &&
3191
- this.groupHasPendingGlyphGeometry(threeEntity)) ||
3641
+ t0 = performance.now();
3642
+ _a.label = 1;
3643
+ case 1:
3644
+ _a.trys.push([1, , 9, 10]);
3645
+ isGroup = threeEntity instanceof AcTrGroup;
3646
+ groupPending = isGroup && this.groupHasPendingGlyphGeometry(threeEntity);
3647
+ needsGlyphDraw = groupPending ||
3192
3648
  hasPendingComplexLineTypeGlyphs(threeEntity) ||
3193
3649
  (threeEntity instanceof AcTrGlyphEntity &&
3194
3650
  !threeEntity.hasDrawableGeometry());
3195
- if (!needsGlyphDraw) return [3 /*break*/, 2];
3196
- return [4 /*yield*/, this.awaitTextStyleFontsReady()];
3197
- case 1:
3198
- _a.sent();
3199
- _a.label = 2;
3651
+ if (!needsGlyphDraw) return [3 /*break*/, 3];
3652
+ return [4 /*yield*/, this.ensureDeferredFontsReady()];
3200
3653
  case 2:
3201
- if (!(threeEntity instanceof AcTrGroup)) return [3 /*break*/, 4];
3654
+ _a.sent();
3655
+ _a.label = 3;
3656
+ case 3:
3657
+ if (!isGroup) return [3 /*break*/, 5];
3202
3658
  // Linework-only INSERTs (no empty glyph shells) skip asyncDraw; spatial
3203
3659
  // boxes are refreshed by syncGroupSpatialBoundsForIndexing after commit.
3204
- if (!this.groupHasPendingGlyphGeometry(threeEntity)) {
3660
+ if (!groupPending) {
3205
3661
  return [2 /*return*/];
3206
3662
  }
3207
3663
  return [4 /*yield*/, threeEntity.asyncDraw()];
3208
- case 3:
3664
+ case 4:
3209
3665
  _a.sent();
3210
3666
  return [2 /*return*/];
3211
- case 4:
3212
- if (!hasPendingComplexLineTypeGlyphs(threeEntity)) return [3 /*break*/, 6];
3213
- return [4 /*yield*/, threeEntity.asyncDraw()];
3214
3667
  case 5:
3668
+ if (!hasPendingComplexLineTypeGlyphs(threeEntity)) return [3 /*break*/, 7];
3669
+ return [4 /*yield*/, threeEntity.asyncDraw()];
3670
+ case 6:
3215
3671
  _a.sent();
3216
3672
  return [2 /*return*/];
3217
- case 6:
3673
+ case 7:
3218
3674
  if (threeEntity.hasDrawableGeometry()) {
3219
3675
  return [2 /*return*/];
3220
3676
  }
3221
3677
  return [4 /*yield*/, threeEntity.asyncDraw()];
3222
- case 7:
3678
+ case 8:
3223
3679
  _a.sent();
3224
- return [2 /*return*/];
3680
+ return [3 /*break*/, 10];
3681
+ case 9:
3682
+ this._convertPhaseMs.finishGeometry += performance.now() - t0;
3683
+ return [7 /*endfinally*/];
3684
+ case 10: return [2 /*return*/];
3225
3685
  }
3226
3686
  });
3227
3687
  });
@@ -3271,8 +3731,8 @@ var AcTrView2d = /** @class */ (function (_super) {
3271
3731
  */
3272
3732
  AcTrView2d.prototype.redrawGlyphEntitiesAfterFontLoad = function (epoch, fontName) {
3273
3733
  return __awaiter(this, void 0, void 0, function () {
3274
- var redrawEpoch, glyphs, _a, _b, layout, glyphs_1, glyphs_1_1, glyph, e_13_1, defaults, normalized_1, msSinceIdle, db;
3275
- var e_14, _c, e_13, _d;
3734
+ var redrawEpoch, glyphs, _a, _b, layout, glyphs_1, glyphs_1_1, glyph, e_12_1, defaults, normalized_1, msSinceIdle, db;
3735
+ var e_13, _c, e_12, _d;
3276
3736
  var _e, _f;
3277
3737
  return __generator(this, function (_g) {
3278
3738
  switch (_g.label) {
@@ -3294,12 +3754,12 @@ var AcTrView2d = /** @class */ (function (_super) {
3294
3754
  });
3295
3755
  }
3296
3756
  }
3297
- catch (e_14_1) { e_14 = { error: e_14_1 }; }
3757
+ catch (e_13_1) { e_13 = { error: e_13_1 }; }
3298
3758
  finally {
3299
3759
  try {
3300
3760
  if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
3301
3761
  }
3302
- finally { if (e_14) throw e_14.error; }
3762
+ finally { if (e_13) throw e_13.error; }
3303
3763
  }
3304
3764
  _g.label = 1;
3305
3765
  case 1:
@@ -3322,14 +3782,14 @@ var AcTrView2d = /** @class */ (function (_super) {
3322
3782
  return [3 /*break*/, 2];
3323
3783
  case 5: return [3 /*break*/, 8];
3324
3784
  case 6:
3325
- e_13_1 = _g.sent();
3326
- e_13 = { error: e_13_1 };
3785
+ e_12_1 = _g.sent();
3786
+ e_12 = { error: e_12_1 };
3327
3787
  return [3 /*break*/, 8];
3328
3788
  case 7:
3329
3789
  try {
3330
3790
  if (glyphs_1_1 && !glyphs_1_1.done && (_d = glyphs_1.return)) _d.call(glyphs_1);
3331
3791
  }
3332
- finally { if (e_13) throw e_13.error; }
3792
+ finally { if (e_12) throw e_12.error; }
3333
3793
  return [7 /*endfinally*/];
3334
3794
  case 8:
3335
3795
  if (glyphs.length > 0) {
@@ -3380,13 +3840,73 @@ var AcTrView2d = /** @class */ (function (_super) {
3380
3840
  });
3381
3841
  };
3382
3842
  AcTrView2d.prototype.groupHasPendingGlyphGeometry = function (group) {
3383
- var pending = false;
3384
- group.traverse(function (child) {
3385
- if (child instanceof AcTrGlyphEntity && !child.hasDrawableGeometry()) {
3386
- pending = true;
3843
+ var e_14, _a;
3844
+ try {
3845
+ // Prefer O(block-def shells). A full THREE.traverse over a mega-block
3846
+ // (100k+ flattened mesh leaves) was dominating scene-convert wall time.
3847
+ for (var _b = __values(group.getSourceEntities()), _c = _b.next(); !_c.done; _c = _b.next()) {
3848
+ var entity = _c.value;
3849
+ if (entity instanceof AcTrGlyphEntity && !entity.hasDrawableGeometry()) {
3850
+ return true;
3851
+ }
3387
3852
  }
3388
- });
3389
- return pending;
3853
+ }
3854
+ catch (e_14_1) { e_14 = { error: e_14_1 }; }
3855
+ finally {
3856
+ try {
3857
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
3858
+ }
3859
+ finally { if (e_14) throw e_14.error; }
3860
+ }
3861
+ return AcTrView2d.directChildrenHavePendingGlyphs(group);
3862
+ };
3863
+ /**
3864
+ * Walks only {@link AcTrEntity} containers for empty glyph shells.
3865
+ *
3866
+ * Skips raw mesh/line leaves under a flattened {@link AcTrGroup} so checking
3867
+ * a whole-floor INSERT is O(glyph shells), not O(all drawable leaves).
3868
+ */
3869
+ AcTrView2d.directChildrenHavePendingGlyphs = function (root) {
3870
+ var e_15, _a;
3871
+ var children = root.children;
3872
+ for (var i = 0; i < children.length; i++) {
3873
+ var child = children[i];
3874
+ if (child instanceof AcTrGlyphEntity) {
3875
+ if (!child.hasDrawableGeometry()) {
3876
+ return true;
3877
+ }
3878
+ continue;
3879
+ }
3880
+ if (child instanceof AcTrGroup) {
3881
+ try {
3882
+ for (var _b = (e_15 = void 0, __values(child.getSourceEntities())), _c = _b.next(); !_c.done; _c = _b.next()) {
3883
+ var entity = _c.value;
3884
+ if (entity instanceof AcTrGlyphEntity &&
3885
+ !entity.hasDrawableGeometry()) {
3886
+ return true;
3887
+ }
3888
+ }
3889
+ }
3890
+ catch (e_15_1) { e_15 = { error: e_15_1 }; }
3891
+ finally {
3892
+ try {
3893
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
3894
+ }
3895
+ finally { if (e_15) throw e_15.error; }
3896
+ }
3897
+ if (AcTrView2d.directChildrenHavePendingGlyphs(child)) {
3898
+ return true;
3899
+ }
3900
+ continue;
3901
+ }
3902
+ if (child instanceof AcTrEntity) {
3903
+ // Nested non-group entity that may still host deferred glyph children.
3904
+ if (AcTrView2d.directChildrenHavePendingGlyphs(child)) {
3905
+ return true;
3906
+ }
3907
+ }
3908
+ }
3909
+ return false;
3390
3910
  };
3391
3911
  /**
3392
3912
  * Runs glyph/group geometry finalize off the main convert loop so other
@@ -3420,9 +3940,13 @@ var AcTrView2d = /** @class */ (function (_super) {
3420
3940
  void job
3421
3941
  .run()
3422
3942
  .then(function () {
3423
- // Convert counter often hits 0 before fonts finish; without this,
3424
- // text added later never paints until the user pans/zooms.
3425
- if (job.epoch === _this._convertEpoch) {
3943
+ // Progressive: dirty every glyph (animate throttles paints).
3944
+ // Interactive non-progressive: same, under a longer open-paint
3945
+ // interval so "Rendering drawing ..." still shows progress.
3946
+ // Headless non-progressive: skip — the pool-drain dirty below
3947
+ // queues the single frame export/scripts need.
3948
+ if (job.epoch === _this._convertEpoch &&
3949
+ (_this._progressiveRendering || _this._cooperativeYield)) {
3426
3950
  _this._isDirty = true;
3427
3951
  }
3428
3952
  })
@@ -3456,6 +3980,8 @@ var AcTrView2d = /** @class */ (function (_super) {
3456
3980
  this._pendingGeometryJobs = 0;
3457
3981
  this._textStyleFontPreloadPromise = null;
3458
3982
  this._textStyleFontPreloadEpoch = -1;
3983
+ this._deferredFontsReady = null;
3984
+ this._fontPreloadDiag = null;
3459
3985
  };
3460
3986
  /**
3461
3987
  * Waits until side-pool glyph/group jobs for the current convert epoch finish.
@@ -3480,6 +4006,31 @@ var AcTrView2d = /** @class */ (function (_super) {
3480
4006
  });
3481
4007
  });
3482
4008
  };
4009
+ /**
4010
+ * Adds an {@link AcTrViewportView} for a real paper-space viewport.
4011
+ *
4012
+ * When the layout view does not exist yet, the owner id is remembered and
4013
+ * {@link flushViewportViewRepairs} creates the view once the layout view
4014
+ * does. Adding twice for the same viewport id replaces the previous view.
4015
+ *
4016
+ * @param entity - Viewport entity, already past the default-viewport filter
4017
+ * at the caller's discretion. Checked again here.
4018
+ */
4019
+ AcTrView2d.prototype.attachViewportView = function (entity) {
4020
+ if (AcTrViewportView.isDefaultPaperSpaceViewport(entity))
4021
+ return;
4022
+ var ownerId = entity.ownerId;
4023
+ var layoutView = ownerId
4024
+ ? this._layoutViewManager.getAt(ownerId)
4025
+ : undefined;
4026
+ if (!layoutView) {
4027
+ if (ownerId)
4028
+ this._viewportRepairOwnerIds.add(ownerId);
4029
+ return;
4030
+ }
4031
+ layoutView.addViewport(new AcTrViewportView(layoutView, this.toGiViewportWithFrozenLayers(entity), this._renderer));
4032
+ this._isDirty = true;
4033
+ };
3483
4034
  /**
3484
4035
  * Walks the given block table record once and creates one
3485
4036
  * `AcTrViewportView` for every real `AcDbViewport` entity it finds
@@ -3499,7 +4050,7 @@ var AcTrView2d = /** @class */ (function (_super) {
3499
4050
  * paper layouts.
3500
4051
  */
3501
4052
  AcTrView2d.prototype.ensureViewportViews = function (blockTableRecord, layoutView) {
3502
- var e_15, _a;
4053
+ var e_16, _a;
3503
4054
  var iterator = blockTableRecord.newIterator();
3504
4055
  try {
3505
4056
  for (var iterator_2 = __values(iterator), iterator_2_1 = iterator_2.next(); !iterator_2_1.done; iterator_2_1 = iterator_2.next()) {
@@ -3508,18 +4059,56 @@ var AcTrView2d = /** @class */ (function (_super) {
3508
4059
  continue;
3509
4060
  if (AcTrViewportView.isDefaultPaperSpaceViewport(entity))
3510
4061
  continue;
3511
- var viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this._renderer);
4062
+ var viewportView = new AcTrViewportView(layoutView, this.toGiViewportWithFrozenLayers(entity), this._renderer);
3512
4063
  layoutView.addViewport(viewportView);
3513
4064
  }
3514
4065
  }
3515
- catch (e_15_1) { e_15 = { error: e_15_1 }; }
4066
+ catch (e_16_1) { e_16 = { error: e_16_1 }; }
3516
4067
  finally {
3517
4068
  try {
3518
4069
  if (iterator_2_1 && !iterator_2_1.done && (_a = iterator_2.return)) _a.call(iterator_2);
3519
4070
  }
3520
- finally { if (e_15) throw e_15.error; }
4071
+ finally { if (e_16) throw e_16.error; }
3521
4072
  }
3522
4073
  };
4074
+ /**
4075
+ * Builds an {@link AcGiViewport} and ensures VPLAYER frozen layers are
4076
+ * present as resolved names. Older data-model builds read group 331 into
4077
+ * {@link AcDbViewport.frozenLayerIds} but omit them from `toGiViewport()`.
4078
+ */
4079
+ AcTrView2d.prototype.toGiViewportWithFrozenLayers = function (entity) {
4080
+ var e_17, _a;
4081
+ var _b;
4082
+ var gi = entity.toGiViewport();
4083
+ if (gi.frozenLayers && gi.frozenLayers.length > 0) {
4084
+ return gi;
4085
+ }
4086
+ var ids = entity.frozenLayerIds;
4087
+ if (!(ids === null || ids === void 0 ? void 0 : ids.length))
4088
+ return gi;
4089
+ var names = [];
4090
+ var layerTable = (_b = entity.database) === null || _b === void 0 ? void 0 : _b.tables.layerTable;
4091
+ if (layerTable) {
4092
+ try {
4093
+ for (var ids_1 = __values(ids), ids_1_1 = ids_1.next(); !ids_1_1.done; ids_1_1 = ids_1.next()) {
4094
+ var id = ids_1_1.value;
4095
+ var layer = layerTable.getIdAt(id);
4096
+ if (layer)
4097
+ names.push(layer.name);
4098
+ }
4099
+ }
4100
+ catch (e_17_1) { e_17 = { error: e_17_1 }; }
4101
+ finally {
4102
+ try {
4103
+ if (ids_1_1 && !ids_1_1.done && (_a = ids_1.return)) _a.call(ids_1);
4104
+ }
4105
+ finally { if (e_17) throw e_17.error; }
4106
+ }
4107
+ }
4108
+ gi.frozenLayerIds = __spreadArray([], __read(ids), false);
4109
+ gi.frozenLayers = names;
4110
+ return gi;
4111
+ };
3523
4112
  /**
3524
4113
  * Converts the specified database entities to three entities
3525
4114
  * @param entities - The database entities
@@ -3527,12 +4116,12 @@ var AcTrView2d = /** @class */ (function (_super) {
3527
4116
  */
3528
4117
  AcTrView2d.prototype.batchConvert = function (entities_3) {
3529
4118
  return __awaiter(this, arguments, void 0, function (entities, options) {
3530
- var epoch, db, progressive, yieldGate, yieldToEventLoop, _loop_4, this_3, i;
4119
+ var epoch, db, progressive, yieldBudgetMs, yieldGate, yieldToEventLoop, _loop_4, this_3, i;
3531
4120
  var _this = this;
3532
- var _a, _b, _c;
4121
+ var _a, _b, _c, _d;
3533
4122
  if (options === void 0) { options = {}; }
3534
- return __generator(this, function (_d) {
3535
- switch (_d.label) {
4123
+ return __generator(this, function (_e) {
4124
+ switch (_e.label) {
3536
4125
  case 0:
3537
4126
  epoch = this._convertEpoch;
3538
4127
  // Fallback: if STYLE-stage preload never started (e.g. non-conversion open
@@ -3545,21 +4134,24 @@ var AcTrView2d = /** @class */ (function (_super) {
3545
4134
  }
3546
4135
  }
3547
4136
  progressive = this._progressiveRendering && !options.forExport;
3548
- yieldGate = options.forExport
4137
+ yieldBudgetMs = progressive
4138
+ ? AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS
4139
+ : AcTrView2d.OPEN_CONVERT_YIELD_BUDGET_MS;
4140
+ yieldGate = options.forExport || !this._cooperativeYield
3549
4141
  ? undefined
3550
- : new AcCmUiYieldGate(AcTrView2d.OPEN_CONVERT_YIELD_BUDGET_MS);
4142
+ : new AcCmUiYieldGate(yieldBudgetMs);
3551
4143
  yieldToEventLoop = function () {
3552
4144
  return new Promise(function (resolve) { return setTimeout(resolve, 0); });
3553
4145
  };
3554
4146
  _loop_4 = function (i) {
3555
- var entity, shouldConvert, objectId_1, convertGen_1, directMeta, added, superseded, threeEntity_1, isMultiLayerGroup, deferGeometry, isExtendBbox_1, commitEntity, layoutView, viewportView, fileName, error_3, didYield;
3556
- return __generator(this, function (_e) {
3557
- switch (_e.label) {
4147
+ var entity, shouldConvert, objectId_1, convertGen_1, directMeta, added, superseded, threeEntity_1, isMultiLayerGroup, deferGeometry, isExtendBbox_1, commitEntity, fileName, error_3, failedId, didYield;
4148
+ return __generator(this, function (_f) {
4149
+ switch (_f.label) {
3558
4150
  case 0:
3559
4151
  entity = entities[i];
3560
- _e.label = 1;
4152
+ _f.label = 1;
3561
4153
  case 1:
3562
- _e.trys.push([1, 9, 10, 11]);
4154
+ _f.trys.push([1, 9, 10, 11]);
3563
4155
  // Document was cleared / replaced while this batch was draining.
3564
4156
  if (epoch !== this_3._convertEpoch) {
3565
4157
  return [2 /*return*/, "continue"];
@@ -3579,7 +4171,7 @@ var AcTrView2d = /** @class */ (function (_super) {
3579
4171
  AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
3580
4172
  return [2 /*return*/, "continue"];
3581
4173
  }
3582
- shouldConvert = options.forExport
4174
+ shouldConvert = options.forExport || this_3._convertInvisibleLayers
3583
4175
  ? this_3._entityDisplay.shouldConvertForExport(entity)
3584
4176
  : this_3._entityDisplay.shouldConvert(entity);
3585
4177
  if (!shouldConvert) {
@@ -3587,16 +4179,20 @@ var AcTrView2d = /** @class */ (function (_super) {
3587
4179
  }
3588
4180
  objectId_1 = String((_a = entity.objectId) !== null && _a !== void 0 ? _a : '');
3589
4181
  convertGen_1 = 0;
3590
- if (!options.forExport) {
3591
- if (objectId_1 &&
3592
- (this_3.hasEntity(objectId_1) ||
3593
- this_3._claimedConvertObjectIds.has(objectId_1))) {
3594
- return [2 /*return*/, "continue"];
3595
- }
3596
- if (objectId_1) {
3597
- this_3._claimedConvertObjectIds.add(objectId_1);
3598
- convertGen_1 = (_b = this_3._entityConvertGeneration.get(objectId_1)) !== null && _b !== void 0 ? _b : 0;
4182
+ if (objectId_1 &&
4183
+ (this_3.hasEntity(objectId_1) ||
4184
+ this_3._claimedConvertObjectIds.has(objectId_1))) {
4185
+ // The border may already be in the scene from a pass that ran
4186
+ // before the layout view existed. Still attach the viewport
4187
+ // view; a second border is not drawn because we continue below.
4188
+ if (entity instanceof AcDbViewport) {
4189
+ this_3.attachViewportView(entity);
3599
4190
  }
4191
+ return [2 /*return*/, "continue"];
4192
+ }
4193
+ if (objectId_1) {
4194
+ this_3._claimedConvertObjectIds.add(objectId_1);
4195
+ convertGen_1 = (_b = this_3._entityConvertGeneration.get(objectId_1)) !== null && _b !== void 0 ? _b : 0;
3600
4196
  }
3601
4197
  directMeta = tryBuildDirectEntityMeta(entity, this_3._renderer);
3602
4198
  if (directMeta) {
@@ -3615,6 +4211,9 @@ var AcTrView2d = /** @class */ (function (_super) {
3615
4211
  this_3.markProgressiveDirty();
3616
4212
  this_3._progressiveOpenFit.afterGeometryBatch(function () { return _this.getDrawingExtents(); }, i);
3617
4213
  }
4214
+ else {
4215
+ this_3.markInteractiveOpenDirty();
4216
+ }
3618
4217
  }
3619
4218
  }
3620
4219
  }
@@ -3629,8 +4228,14 @@ var AcTrView2d = /** @class */ (function (_super) {
3629
4228
  threeEntity_1 = this_3.drawEntity(entity, false);
3630
4229
  // Viewports may produce no border geometry (e.g. on a no-plot layer) while
3631
4230
  // still needing an AcTrViewportView for model content below.
3632
- if (!threeEntity_1 && !(entity instanceof AcDbViewport))
4231
+ if (!threeEntity_1 && !(entity instanceof AcDbViewport)) {
4232
+ // Release the claim so a later export pass can retry; otherwise
4233
+ // `forExport` claim checks would skip this id forever.
4234
+ if (objectId_1) {
4235
+ this_3._claimedConvertObjectIds.delete(objectId_1);
4236
+ }
3633
4237
  return [2 /*return*/, "continue"];
4238
+ }
3634
4239
  if (!threeEntity_1) return [3 /*break*/, 8];
3635
4240
  threeEntity_1.objectId = entity.objectId;
3636
4241
  threeEntity_1.ownerId = entity.ownerId;
@@ -3655,12 +4260,13 @@ var AcTrView2d = /** @class */ (function (_super) {
3655
4260
  return [3 /*break*/, 4];
3656
4261
  case 2: return [4 /*yield*/, this_3.handleGroup(threeEntity_1, progressive, epoch, convertGen_1)];
3657
4262
  case 3:
3658
- _e.sent();
3659
- _e.label = 4;
4263
+ _f.sent();
4264
+ _f.label = 4;
3660
4265
  case 4: return [3 /*break*/, 8];
3661
4266
  case 5:
3662
4267
  isExtendBbox_1 = !(entity instanceof AcDbRay || entity instanceof AcDbXline);
3663
4268
  commitEntity = function () { return __awaiter(_this, void 0, void 0, function () {
4269
+ var tAdd;
3664
4270
  var _this = this;
3665
4271
  var _a;
3666
4272
  return __generator(this, function (_b) {
@@ -3684,7 +4290,9 @@ var AcTrView2d = /** @class */ (function (_super) {
3684
4290
  this._inheritedLayerMaterialMapper.remap(threeEntity_1.children, '0', threeEntity_1.layerName);
3685
4291
  }
3686
4292
  }
4293
+ tAdd = performance.now();
3687
4294
  this._scene.addEntity(threeEntity_1, isExtendBbox_1);
4295
+ this._convertPhaseMs.addEntity += performance.now() - tAdd;
3688
4296
  this.applySessionHiddenObjectState(entity.objectId);
3689
4297
  // Release memory occupied by this entity
3690
4298
  threeEntity_1.dispose();
@@ -3692,6 +4300,9 @@ var AcTrView2d = /** @class */ (function (_super) {
3692
4300
  this.markProgressiveDirty();
3693
4301
  this._progressiveOpenFit.afterGeometryBatch(function () { return _this.getDrawingExtents(); }, i);
3694
4302
  }
4303
+ else {
4304
+ this.markInteractiveOpenDirty();
4305
+ }
3695
4306
  return [2 /*return*/];
3696
4307
  }
3697
4308
  });
@@ -3701,8 +4312,8 @@ var AcTrView2d = /** @class */ (function (_super) {
3701
4312
  return [3 /*break*/, 8];
3702
4313
  case 6: return [4 /*yield*/, commitEntity()];
3703
4314
  case 7:
3704
- _e.sent();
3705
- _e.label = 8;
4315
+ _f.sent();
4316
+ _f.label = 8;
3706
4317
  case 8:
3707
4318
  if (epoch !== this_3._convertEpoch) {
3708
4319
  return [2 /*return*/, "continue"];
@@ -3713,13 +4324,7 @@ var AcTrView2d = /** @class */ (function (_super) {
3713
4324
  // user-created viewport. The redundant check below is kept as
3714
4325
  // a defensive guard in case a future refactor reorders the
3715
4326
  // early-skip — it costs ~nothing and prevents a regression.
3716
- if (!AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
3717
- layoutView = this_3._layoutViewManager.getAt(entity.ownerId);
3718
- if (layoutView) {
3719
- viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this_3._renderer);
3720
- layoutView.addViewport(viewportView);
3721
- }
3722
- }
4327
+ this_3.attachViewportView(entity);
3723
4328
  }
3724
4329
  else if (entity instanceof AcDbRasterImage) {
3725
4330
  fileName = entity.imageFileName;
@@ -3732,7 +4337,11 @@ var AcTrView2d = /** @class */ (function (_super) {
3732
4337
  }
3733
4338
  return [3 /*break*/, 11];
3734
4339
  case 9:
3735
- error_3 = _e.sent();
4340
+ error_3 = _f.sent();
4341
+ failedId = String((_d = entity.objectId) !== null && _d !== void 0 ? _d : '');
4342
+ if (failedId) {
4343
+ this_3._claimedConvertObjectIds.delete(failedId);
4344
+ }
3736
4345
  log.error("[AcTrView2d] Failed to convert entity ".concat(entity.objectId, " (").concat(entity.type, "):"), error_3);
3737
4346
  return [3 /*break*/, 11];
3738
4347
  case 10:
@@ -3746,27 +4355,37 @@ var AcTrView2d = /** @class */ (function (_super) {
3746
4355
  if (epoch !== this_3._convertEpoch) {
3747
4356
  return [2 /*return*/, "continue"];
3748
4357
  }
3749
- if (!yieldGate) return [3 /*break*/, 13];
3750
- return [4 /*yield*/, yieldGate.maybeYield(yieldToEventLoop)];
4358
+ if (!progressive) return [3 /*break*/, 13];
4359
+ // Prefer input-aware yields so pan/zoom are not starved after the
4360
+ // overlay is gone; paint throttling stays in markProgressiveDirty.
4361
+ return [4 /*yield*/, this_3.progressiveYieldForInput(yieldGate, yieldToEventLoop)];
3751
4362
  case 12:
3752
- didYield = _e.sent();
4363
+ // Prefer input-aware yields so pan/zoom are not starved after the
4364
+ // overlay is gone; paint throttling stays in markProgressiveDirty.
4365
+ _f.sent();
4366
+ return [3 /*break*/, 15];
4367
+ case 13:
4368
+ if (!yieldGate) return [3 /*break*/, 15];
4369
+ return [4 /*yield*/, yieldGate.maybeYield(yieldToEventLoop)];
4370
+ case 14:
4371
+ didYield = _f.sent();
3753
4372
  if (didYield) {
3754
4373
  this_3._progressiveYieldCount++;
3755
4374
  }
3756
- _e.label = 13;
3757
- case 13: return [2 /*return*/];
4375
+ _f.label = 15;
4376
+ case 15: return [2 /*return*/];
3758
4377
  }
3759
4378
  });
3760
4379
  };
3761
4380
  this_3 = this;
3762
4381
  i = 0;
3763
- _d.label = 1;
4382
+ _e.label = 1;
3764
4383
  case 1:
3765
4384
  if (!(i < entities.length)) return [3 /*break*/, 4];
3766
4385
  return [5 /*yield**/, _loop_4(i)];
3767
4386
  case 2:
3768
- _d.sent();
3769
- _d.label = 3;
4387
+ _e.sent();
4388
+ _e.label = 3;
3770
4389
  case 3:
3771
4390
  ++i;
3772
4391
  return [3 /*break*/, 1];
@@ -3780,6 +4399,12 @@ var AcTrView2d = /** @class */ (function (_super) {
3780
4399
  * with their union before spatial-index registration.
3781
4400
  */
3782
4401
  AcTrView2d.prototype.syncGroupSpatialBoundsForIndexing = function (group) {
4402
+ var lazy = group.peekLazyChildBoxes();
4403
+ if (lazy && lazy.boxes.length > 0) {
4404
+ var userData_1 = group.userData;
4405
+ userData_1.lazySpatialChildren = lazy;
4406
+ return;
4407
+ }
3783
4408
  group.refreshWcsChildBoxesFromChildren();
3784
4409
  if (group.wcsChildBoxes.length === 0) {
3785
4410
  return;
@@ -3797,40 +4422,45 @@ var AcTrView2d = /** @class */ (function (_super) {
3797
4422
  };
3798
4423
  AcTrView2d.prototype.handleGroup = function (group_1, progressive_1) {
3799
4424
  return __awaiter(this, arguments, void 0, function (group, progressive, epoch, convertGen) {
3800
- var objectId, children, objectsGroupByLayer, children_1, children_1_1, child, renderContext, groupObjectId, groupLayerName, groupChildBoxes, aggregateSpatialBbox, registeredChildIndex;
3801
- var e_16, _a;
4425
+ var t0, objectId, children, objectsGroupByLayer_2, children_1, children_1_1, child, renderContext, groupObjectId, groupLayerName, lazyChildren, groupChildBoxes, aggregateSpatialBbox, registeredChildIndex, yieldToEventLoop, lastGroupYieldAt, _loop_5, this_4, objectsGroupByLayer_1, objectsGroupByLayer_1_1, _a, layerName, objects, e_18_1;
4426
+ var e_19, _b, e_18, _c;
3802
4427
  var _this = this;
3803
- var _b, _c;
4428
+ var _d, _e;
3804
4429
  if (epoch === void 0) { epoch = this._convertEpoch; }
3805
4430
  if (convertGen === void 0) { convertGen = 0; }
3806
- return __generator(this, function (_d) {
3807
- switch (_d.label) {
3808
- case 0: return [4 /*yield*/, this.finishEntityGeometry(group, progressive)];
4431
+ return __generator(this, function (_f) {
4432
+ switch (_f.label) {
4433
+ case 0:
4434
+ t0 = performance.now();
4435
+ _f.label = 1;
3809
4436
  case 1:
3810
- _d.sent();
4437
+ _f.trys.push([1, , 11, 12]);
4438
+ return [4 /*yield*/, this.finishEntityGeometry(group, progressive)];
4439
+ case 2:
4440
+ _f.sent();
3811
4441
  if (epoch !== this._convertEpoch) {
3812
4442
  group.dispose();
3813
4443
  return [2 /*return*/];
3814
4444
  }
3815
- objectId = String((_b = group.objectId) !== null && _b !== void 0 ? _b : '');
4445
+ objectId = String((_d = group.objectId) !== null && _d !== void 0 ? _d : '');
3816
4446
  if (objectId &&
3817
- ((_c = this._entityConvertGeneration.get(objectId)) !== null && _c !== void 0 ? _c : 0) !== convertGen) {
4447
+ ((_e = this._entityConvertGeneration.get(objectId)) !== null && _e !== void 0 ? _e : 0) !== convertGen) {
3818
4448
  group.dispose();
3819
4449
  return [2 /*return*/];
3820
4450
  }
3821
4451
  this.syncGroupSpatialBoundsForIndexing(group);
3822
4452
  children = group.children;
3823
- objectsGroupByLayer = new Map();
4453
+ objectsGroupByLayer_2 = new Map();
3824
4454
  children.forEach(function (child) {
3825
4455
  var _a;
3826
4456
  if (child.visible === false) {
3827
4457
  return;
3828
4458
  }
3829
4459
  var layerName = child.userData.layerName;
3830
- if (!objectsGroupByLayer.has(layerName)) {
3831
- objectsGroupByLayer.set(layerName, []);
4460
+ if (!objectsGroupByLayer_2.has(layerName)) {
4461
+ objectsGroupByLayer_2.set(layerName, []);
3832
4462
  }
3833
- (_a = objectsGroupByLayer.get(layerName)) === null || _a === void 0 ? void 0 : _a.push(child);
4463
+ (_a = objectsGroupByLayer_2.get(layerName)) === null || _a === void 0 ? void 0 : _a.push(child);
3834
4464
  });
3835
4465
  // Important:
3836
4466
  // Sometimes one group may contain huge amount of objects (> 100,000). So it is important
@@ -3848,87 +4478,136 @@ var AcTrView2d = /** @class */ (function (_super) {
3848
4478
  child.parent = null;
3849
4479
  }
3850
4480
  }
3851
- catch (e_16_1) { e_16 = { error: e_16_1 }; }
4481
+ catch (e_19_1) { e_19 = { error: e_19_1 }; }
3852
4482
  finally {
3853
4483
  try {
3854
- if (children_1_1 && !children_1_1.done && (_a = children_1.return)) _a.call(children_1);
4484
+ if (children_1_1 && !children_1_1.done && (_b = children_1.return)) _b.call(children_1);
3855
4485
  }
3856
- finally { if (e_16) throw e_16.error; }
4486
+ finally { if (e_19) throw e_19.error; }
3857
4487
  }
3858
4488
  renderContext = group.renderContext;
3859
4489
  groupObjectId = group.objectId;
3860
4490
  groupLayerName = group.layerName;
3861
- // AcDbRenderingCache.draw (and similar paths such as AcDbTable) already call
3862
- // applyMatrix on the group, which updates wcsBbbox and wcsChildBoxes to WCS.
3863
- // Do not multiply group.matrix here — that would double-transform spatial bounds.
3864
- if (process.env.NODE_ENV !== 'production') {
4491
+ lazyChildren = group.userData.lazySpatialChildren;
4492
+ if (process.env.NODE_ENV !== 'production' && !lazyChildren) {
3865
4493
  assertAcTrGroupWcsBboxesConsistent(group);
3866
4494
  }
3867
- groupChildBoxes = group.wcsChildBoxes.map(function (box) { return ({
3868
- minX: box.minX,
3869
- minY: box.minY,
3870
- maxX: box.maxX,
3871
- maxY: box.maxY,
3872
- id: box.id
3873
- }); });
3874
- aggregateSpatialBbox = groupChildBoxes.length > 0
3875
- ? unionGroupWcsChildBoxes(group)
3876
- : group.wcsBbox.clone();
4495
+ groupChildBoxes = lazyChildren
4496
+ ? []
4497
+ : group.wcsChildBoxes.map(function (box) { return ({
4498
+ minX: box.minX,
4499
+ minY: box.minY,
4500
+ maxX: box.maxX,
4501
+ maxY: box.maxY,
4502
+ id: box.id
4503
+ }); });
4504
+ aggregateSpatialBbox = lazyChildren
4505
+ ? group.wcsBbox.clone()
4506
+ : groupChildBoxes.length > 0
4507
+ ? unionGroupWcsChildBoxes(group)
4508
+ : group.wcsBbox.clone();
3877
4509
  if (groupChildBoxes.length > 0) {
3878
4510
  group.wcsBbox = aggregateSpatialBbox.clone();
3879
4511
  }
3880
4512
  registeredChildIndex = false;
3881
- objectsGroupByLayer.forEach(function (objects, layerName) {
3882
- // Nested layer-0 may already be resolved to an inner INSERT layer during
3883
- // flatten. Remaining "0" buckets inherit this (outermost) INSERT layer.
3884
- var effectiveLayerName = layerName === '0' ? groupLayerName : layerName;
3885
- // Material remap must still treat authored layer-0 drawables as layer-0
3886
- // ByLayer even when nest resolution already rewrote layerName.
3887
- var sourceLayerForMaterials = objects.some(function (object) {
3888
- var _a;
3889
- var data = object.userData;
3890
- return ((_a = data.authoredLayerName) !== null && _a !== void 0 ? _a : layerName) === '0';
3891
- })
3892
- ? '0'
3893
- : layerName;
3894
- // Keep runtime layer metadata/material cache aligned with the inherited layer so
3895
- // later layer style edits (color, linetype, lineweight, transparency) target this
3896
- // object set correctly.
3897
- _this._inheritedLayerMaterialMapper.remap(objects, sourceLayerForMaterials, effectiveLayerName);
3898
- // One INSERT can expand to children from multiple layers. Here we create one
3899
- // render entity per layer bucket but preserve the INSERT object id for all
3900
- // buckets, so selection/highlight still maps back to the same database object.
3901
- // Within each layer bucket, the object id remains unique in scene indexing.
3902
- var entity = new AcTrEntity(renderContext);
3903
- // Copy the INSERT matrix exactly — applyMatrix4 decomposes and drops
3904
- // reflections from mirrored block scales.
3905
- entity.matrix.copy(group.matrix);
3906
- entity.matrixAutoUpdate = false;
3907
- entity.matrixWorldNeedsUpdate = true;
3908
- entity.objectId = groupObjectId;
3909
- entity.ownerId = group.ownerId;
3910
- // If block-definition entities are on layer "0", this bucket now uses the layer
3911
- // of the block reference itself (effectiveLayerName).
3912
- entity.layerName = effectiveLayerName;
3913
- entity.userData.insertLayerName = groupLayerName;
3914
- entity.wcsBbox = aggregateSpatialBbox.clone();
3915
- var entityUserData = entity.userData;
3916
- if (!registeredChildIndex && groupChildBoxes.length > 0) {
3917
- entityUserData.spatialIndexChildBoxes = groupChildBoxes;
3918
- registeredChildIndex = true;
3919
- }
3920
- // Important:
3921
- // DO NOT USE spread operator when adding objects because it may be one very large array
3922
- // and can result in maximum call stack size exceeded
3923
- for (var i = 0; i < objects.length; i++) {
3924
- entity.add(objects[i]);
3925
- }
3926
- entity.updateMatrixWorld(true);
3927
- _this._layerAppearance.refreshTextMaterialsInObjectTree(entity);
3928
- _this._scene.addEntity(entity, true);
3929
- _this.applySessionHiddenObjectState(groupObjectId);
3930
- entity.dispose();
3931
- });
4513
+ yieldToEventLoop = function () {
4514
+ return new Promise(function (resolve) { return setTimeout(resolve, 0); });
4515
+ };
4516
+ lastGroupYieldAt = progressive ? performance.now() : 0;
4517
+ _loop_5 = function (layerName, objects) {
4518
+ var effectiveLayerName, sourceLayerForMaterials, entity, entityUserData, i, tAdd;
4519
+ return __generator(this, function (_g) {
4520
+ switch (_g.label) {
4521
+ case 0:
4522
+ effectiveLayerName = layerName === '0' ? groupLayerName : layerName;
4523
+ sourceLayerForMaterials = objects.some(function (object) {
4524
+ var _a;
4525
+ var data = object.userData;
4526
+ return ((_a = data.authoredLayerName) !== null && _a !== void 0 ? _a : layerName) === '0';
4527
+ })
4528
+ ? '0'
4529
+ : layerName;
4530
+ // Keep runtime layer metadata/material cache aligned with the inherited layer so
4531
+ // later layer style edits (color, linetype, lineweight, transparency) target this
4532
+ // object set correctly.
4533
+ this_4._inheritedLayerMaterialMapper.remap(objects, sourceLayerForMaterials, effectiveLayerName);
4534
+ entity = new AcTrEntity(renderContext);
4535
+ // Copy the INSERT matrix exactly — applyMatrix4 decomposes and drops
4536
+ // reflections from mirrored block scales.
4537
+ entity.matrix.copy(group.matrix);
4538
+ entity.matrixAutoUpdate = false;
4539
+ entity.matrixWorldNeedsUpdate = true;
4540
+ entity.objectId = groupObjectId;
4541
+ entity.ownerId = group.ownerId;
4542
+ // If block-definition entities are on layer "0", this bucket now uses the layer
4543
+ // of the block reference itself (effectiveLayerName).
4544
+ entity.layerName = effectiveLayerName;
4545
+ entity.userData.insertLayerName = groupLayerName;
4546
+ entity.wcsBbox = aggregateSpatialBbox.clone();
4547
+ entityUserData = entity.userData;
4548
+ if (!registeredChildIndex && lazyChildren) {
4549
+ ;
4550
+ entity.userData.lazySpatialChildren = lazyChildren;
4551
+ registeredChildIndex = true;
4552
+ }
4553
+ else if (!registeredChildIndex && groupChildBoxes.length > 0) {
4554
+ entityUserData.spatialIndexChildBoxes = groupChildBoxes;
4555
+ registeredChildIndex = true;
4556
+ }
4557
+ // Important:
4558
+ // DO NOT USE spread operator when adding objects because it may be one very large array
4559
+ // and can result in maximum call stack size exceeded
4560
+ for (i = 0; i < objects.length; i++) {
4561
+ entity.add(objects[i]);
4562
+ }
4563
+ entity.updateMatrixWorld(true);
4564
+ this_4._layerAppearance.refreshTextMaterialsInObjectTree(entity);
4565
+ tAdd = performance.now();
4566
+ this_4._scene.addEntity(entity, true);
4567
+ this_4._convertPhaseMs.addEntity += performance.now() - tAdd;
4568
+ this_4.applySessionHiddenObjectState(groupObjectId);
4569
+ entity.dispose();
4570
+ if (!(progressive &&
4571
+ epoch === this_4._convertEpoch &&
4572
+ performance.now() - lastGroupYieldAt >=
4573
+ AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS)) return [3 /*break*/, 2];
4574
+ return [4 /*yield*/, yieldToEventLoop()];
4575
+ case 1:
4576
+ _g.sent();
4577
+ lastGroupYieldAt = performance.now();
4578
+ _g.label = 2;
4579
+ case 2: return [2 /*return*/];
4580
+ }
4581
+ });
4582
+ };
4583
+ this_4 = this;
4584
+ _f.label = 3;
4585
+ case 3:
4586
+ _f.trys.push([3, 8, 9, 10]);
4587
+ objectsGroupByLayer_1 = __values(objectsGroupByLayer_2), objectsGroupByLayer_1_1 = objectsGroupByLayer_1.next();
4588
+ _f.label = 4;
4589
+ case 4:
4590
+ if (!!objectsGroupByLayer_1_1.done) return [3 /*break*/, 7];
4591
+ _a = __read(objectsGroupByLayer_1_1.value, 2), layerName = _a[0], objects = _a[1];
4592
+ return [5 /*yield**/, _loop_5(layerName, objects)];
4593
+ case 5:
4594
+ _f.sent();
4595
+ _f.label = 6;
4596
+ case 6:
4597
+ objectsGroupByLayer_1_1 = objectsGroupByLayer_1.next();
4598
+ return [3 /*break*/, 4];
4599
+ case 7: return [3 /*break*/, 10];
4600
+ case 8:
4601
+ e_18_1 = _f.sent();
4602
+ e_18 = { error: e_18_1 };
4603
+ return [3 /*break*/, 10];
4604
+ case 9:
4605
+ try {
4606
+ if (objectsGroupByLayer_1_1 && !objectsGroupByLayer_1_1.done && (_c = objectsGroupByLayer_1.return)) _c.call(objectsGroupByLayer_1);
4607
+ }
4608
+ finally { if (e_18) throw e_18.error; }
4609
+ return [7 /*endfinally*/];
4610
+ case 10:
3932
4611
  group.dispose();
3933
4612
  if (progressive) {
3934
4613
  this.markProgressiveDirty();
@@ -3936,7 +4615,14 @@ var AcTrView2d = /** @class */ (function (_super) {
3936
4615
  return _this.getDrawingExtents();
3937
4616
  });
3938
4617
  }
3939
- return [2 /*return*/];
4618
+ else {
4619
+ this.markInteractiveOpenDirty();
4620
+ }
4621
+ return [3 /*break*/, 12];
4622
+ case 11:
4623
+ this._convertPhaseMs.handleGroup += performance.now() - t0;
4624
+ return [7 /*endfinally*/];
4625
+ case 12: return [2 /*return*/];
3940
4626
  }
3941
4627
  });
3942
4628
  });
@@ -3983,30 +4669,65 @@ var AcTrView2d = /** @class */ (function (_super) {
3983
4669
  AcTrView2d.prototype.stampEntityProcessingIdle = function () {
3984
4670
  if (this._numOfEntitiesToProcess === 0 && this._pendingGeometryJobs === 0) {
3985
4671
  this._entityProcessingIdleAt = performance.now();
4672
+ // Release block templates after INSERT convert/glyph work finishes.
4673
+ // Clearing at converter END was too early (convert still draining);
4674
+ // keeping templates until the next open kept baked shells in memory and
4675
+ // OOMed large drawings.
4676
+ AcDbRenderingCache.instance.clear();
3986
4677
  }
3987
4678
  };
3988
4679
  /**
3989
- * Wall-time between cooperative yields during scene convert (ms).
3990
- * Used for both progressive and non-progressive opens so large drawings
3991
- * (e.g. multi-sheet architectural DWGs) cannot freeze the main thread long
3992
- * enough for Chromium to show "Page Unresponsive" while the overlay still
3993
- * reads "Rendering drawing ...". Kept relatively large so convert
3994
- * throughput stays high; smaller budgets made open 2–3× slower.
4680
+ * Wall-time between cooperative yields during scene convert (ms) when
4681
+ * progressive rendering is off. Overlay still blocks input, so a larger
4682
+ * budget keeps throughput high while avoiding Chromium "Page Unresponsive".
3995
4683
  */
3996
4684
  AcTrView2d.OPEN_CONVERT_YIELD_BUDGET_MS = 300;
3997
4685
  /**
3998
- * Minimum interval between progressive mid-open paints (ms).
3999
- * Full-scene WebGL paints dominate open wall time on large drawings;
4000
- * paint much less often than we yield. Independent of convert yields —
4001
- * non-progressive opens still yield without mid-open WebGL paints.
4686
+ * Yield cadence during progressive open (ms). Overlay stays hidden; keep
4687
+ * slices near one frame so pan/zoom handlers can run. Discrete
4688
+ * `navigator.scheduling.isInputPending` yields once so handlers run;
4689
+ * pointerdown/wheel set a short pause so OrbitControls can catch up.
4690
+ */
4691
+ AcTrView2d.PROGRESSIVE_OPEN_YIELD_BUDGET_MS = 32;
4692
+ /** Extra idle window after canvas pointerdown/wheel so gestures stay fluid. */
4693
+ AcTrView2d.PROGRESSIVE_INPUT_PAUSE_MS = 120;
4694
+ /**
4695
+ * Minimum interval between progressive mid-open paints driven by convert
4696
+ * (ms). Full-scene WebGL paints dominate open wall time; user camera
4697
+ * changes bypass this throttle via {@link _progressivePaintUrgent}.
4698
+ */
4699
+ AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS = 2500;
4700
+ /**
4701
+ * Minimum interval between interactive non-progressive mid-open paints (ms)
4702
+ * while convert / deferred glyphs run under "Rendering drawing ...".
4703
+ * Longer than {@link PROGRESSIVE_OPEN_PAINT_INTERVAL_MS}: the overlay is
4704
+ * semi-transparent (`rgba(0,0,0,0.45)`), but each paint still redraws the
4705
+ * full scene and used to fire on every glyph completion. Headless
4706
+ * (`cooperativeYield` false) skips these paints entirely.
4002
4707
  */
4003
- AcTrView2d.PROGRESSIVE_OPEN_PAINT_INTERVAL_MS = 1000;
4708
+ AcTrView2d.NON_PROGRESSIVE_OPEN_PAINT_INTERVAL_MS = 4000;
4004
4709
  /**
4005
4710
  * Max concurrent deferred glyph/INSERT geometry finalizers. Large drawings
4006
4711
  * enqueue thousands of jobs; keep this modest to limit peak JS heap during
4007
4712
  * open while retaining reasonable text throughput after convert finishes.
4008
4713
  */
4009
4714
  AcTrView2d.DEFERRED_GEOMETRY_CONCURRENCY = 8;
4715
+ /**
4716
+ * Target wall time of one interactive convert slice (ms) while a paper
4717
+ * layout is active.
4718
+ *
4719
+ * Slices return to the drain so a layout activated mid-open can jump ahead
4720
+ * of a long model-space batch. Kept well above one frame (≈50–100 ms) so
4721
+ * the fixed cost of each slice — yield, paint, queue absorb — does not
4722
+ * dominate total load time. The 300ms yield inside {@link batchConvert}
4723
+ * still applies within a slice.
4724
+ */
4725
+ AcTrView2d.CONVERT_SLICE_BUDGET_MS = 64;
4726
+ /**
4727
+ * Upper bound on entities per slice. Heavy block references stay at 1
4728
+ * because a slice that overruns the time budget is halved.
4729
+ */
4730
+ AcTrView2d.CONVERT_SLICE_MAX = 512;
4010
4731
  return AcTrView2d;
4011
4732
  }(AcEdBaseView));
4012
4733
  export { AcTrView2d };