@mlightcad/cad-simple-viewer 1.5.0 → 1.5.2

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 (242) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +25678 -48997
  3. package/dist/index.umd.cjs +571 -603
  4. package/dist/libredwg-parser-worker.js +3955 -7422
  5. package/dist/mtext-renderer-worker.js +2727 -2539
  6. package/lib/app/AcApAnnotation.js +4 -4
  7. package/lib/app/AcApAnnotation.js.map +1 -1
  8. package/lib/app/AcApDocManager.d.ts +56 -2
  9. package/lib/app/AcApDocManager.d.ts.map +1 -1
  10. package/lib/app/AcApDocManager.js +182 -31
  11. package/lib/app/AcApDocManager.js.map +1 -1
  12. package/lib/app/index.d.ts +1 -0
  13. package/lib/app/index.d.ts.map +1 -1
  14. package/lib/app/index.js +1 -0
  15. package/lib/app/index.js.map +1 -1
  16. package/lib/app/openFileProgress.d.ts +10 -0
  17. package/lib/app/openFileProgress.d.ts.map +1 -0
  18. package/lib/app/openFileProgress.js +20 -0
  19. package/lib/app/openFileProgress.js.map +1 -0
  20. package/lib/command/AcApSwitchBgCmd.d.ts +3 -0
  21. package/lib/command/AcApSwitchBgCmd.d.ts.map +1 -1
  22. package/lib/command/AcApSwitchBgCmd.js +14 -7
  23. package/lib/command/AcApSwitchBgCmd.js.map +1 -1
  24. package/lib/command/AcApZoomCmd.d.ts.map +1 -1
  25. package/lib/command/AcApZoomCmd.js +3 -3
  26. package/lib/command/AcApZoomCmd.js.map +1 -1
  27. package/lib/command/convert/index.d.ts +0 -1
  28. package/lib/command/convert/index.d.ts.map +1 -1
  29. package/lib/command/convert/index.js +0 -1
  30. package/lib/command/convert/index.js.map +1 -1
  31. package/lib/command/draw/AcApArcCmd.d.ts.map +1 -1
  32. package/lib/command/draw/AcApArcCmd.js +47 -20
  33. package/lib/command/draw/AcApArcCmd.js.map +1 -1
  34. package/lib/command/draw/AcApCircleCmd.d.ts.map +1 -1
  35. package/lib/command/draw/AcApCircleCmd.js +5 -6
  36. package/lib/command/draw/AcApCircleCmd.js.map +1 -1
  37. package/lib/command/draw/AcApEllipseCmd.d.ts.map +1 -1
  38. package/lib/command/draw/AcApEllipseCmd.js +4 -6
  39. package/lib/command/draw/AcApEllipseCmd.js.map +1 -1
  40. package/lib/command/draw/AcApMLineCmd.d.ts.map +1 -1
  41. package/lib/command/draw/AcApMLineCmd.js +4 -7
  42. package/lib/command/draw/AcApMLineCmd.js.map +1 -1
  43. package/lib/command/draw/AcApMTextCmd.d.ts.map +1 -1
  44. package/lib/command/draw/AcApMTextCmd.js +3 -1
  45. package/lib/command/draw/AcApMTextCmd.js.map +1 -1
  46. package/lib/command/draw/AcApPolygonCmd.d.ts.map +1 -1
  47. package/lib/command/draw/AcApPolygonCmd.js +4 -5
  48. package/lib/command/draw/AcApPolygonCmd.js.map +1 -1
  49. package/lib/command/draw/AcApPolylineCmd.d.ts +49 -0
  50. package/lib/command/draw/AcApPolylineCmd.d.ts.map +1 -1
  51. package/lib/command/draw/AcApPolylineCmd.js +200 -18
  52. package/lib/command/draw/AcApPolylineCmd.js.map +1 -1
  53. package/lib/command/draw/AcApRayCmd.d.ts.map +1 -1
  54. package/lib/command/draw/AcApRayCmd.js +2 -3
  55. package/lib/command/draw/AcApRayCmd.js.map +1 -1
  56. package/lib/command/draw/AcApRectCmd.d.ts.map +1 -1
  57. package/lib/command/draw/AcApRectCmd.js +10 -10
  58. package/lib/command/draw/AcApRectCmd.js.map +1 -1
  59. package/lib/command/draw/AcApSplineCmd.d.ts.map +1 -1
  60. package/lib/command/draw/AcApSplineCmd.js +3 -6
  61. package/lib/command/draw/AcApSplineCmd.js.map +1 -1
  62. package/lib/command/draw/AcApXLineCmd.d.ts.map +1 -1
  63. package/lib/command/draw/AcApXLineCmd.js +2 -3
  64. package/lib/command/draw/AcApXLineCmd.js.map +1 -1
  65. package/lib/command/measure/AcApClearMeasurementsCmd.d.ts +11 -0
  66. package/lib/command/measure/AcApClearMeasurementsCmd.d.ts.map +1 -1
  67. package/lib/command/measure/AcApClearMeasurementsCmd.js +15 -4
  68. package/lib/command/measure/AcApClearMeasurementsCmd.js.map +1 -1
  69. package/lib/command/measure/AcApMeasureAngleCmd.d.ts.map +1 -1
  70. package/lib/command/measure/AcApMeasureAngleCmd.js +15 -6
  71. package/lib/command/measure/AcApMeasureAngleCmd.js.map +1 -1
  72. package/lib/command/measure/AcApMeasureArcCmd.d.ts.map +1 -1
  73. package/lib/command/measure/AcApMeasureArcCmd.js +11 -4
  74. package/lib/command/measure/AcApMeasureArcCmd.js.map +1 -1
  75. package/lib/command/measure/AcApMeasureAreaCmd.d.ts.map +1 -1
  76. package/lib/command/measure/AcApMeasureAreaCmd.js +11 -4
  77. package/lib/command/measure/AcApMeasureAreaCmd.js.map +1 -1
  78. package/lib/command/measure/AcApMeasureDistanceCmd.d.ts +3 -2
  79. package/lib/command/measure/AcApMeasureDistanceCmd.d.ts.map +1 -1
  80. package/lib/command/measure/AcApMeasureDistanceCmd.js +14 -6
  81. package/lib/command/measure/AcApMeasureDistanceCmd.js.map +1 -1
  82. package/lib/command/modify/AcApOffsetCmd.d.ts +30 -0
  83. package/lib/command/modify/AcApOffsetCmd.d.ts.map +1 -0
  84. package/lib/command/modify/AcApOffsetCmd.js +425 -0
  85. package/lib/command/modify/AcApOffsetCmd.js.map +1 -0
  86. package/lib/command/modify/AcApRotateCmd.d.ts.map +1 -1
  87. package/lib/command/modify/AcApRotateCmd.js +3 -4
  88. package/lib/command/modify/AcApRotateCmd.js.map +1 -1
  89. package/lib/command/modify/index.d.ts +1 -0
  90. package/lib/command/modify/index.d.ts.map +1 -1
  91. package/lib/command/modify/index.js +1 -0
  92. package/lib/command/modify/index.js.map +1 -1
  93. package/lib/command/review/AcApRevCircleCmd.d.ts.map +1 -1
  94. package/lib/command/review/AcApRevCircleCmd.js +4 -1
  95. package/lib/command/review/AcApRevCircleCmd.js.map +1 -1
  96. package/lib/editor/global/AcEdUiColor.d.ts +183 -0
  97. package/lib/editor/global/AcEdUiColor.d.ts.map +1 -0
  98. package/lib/editor/global/AcEdUiColor.js +224 -0
  99. package/lib/editor/global/AcEdUiColor.js.map +1 -0
  100. package/lib/editor/global/AcEdUiTheme.d.ts +0 -1
  101. package/lib/editor/global/AcEdUiTheme.d.ts.map +1 -1
  102. package/lib/editor/global/AcEdUiTheme.js +0 -5
  103. package/lib/editor/global/AcEdUiTheme.js.map +1 -1
  104. package/lib/editor/global/eventBus.d.ts +3 -0
  105. package/lib/editor/global/eventBus.d.ts.map +1 -1
  106. package/lib/editor/global/eventBus.js.map +1 -1
  107. package/lib/editor/global/index.d.ts +1 -0
  108. package/lib/editor/global/index.d.ts.map +1 -1
  109. package/lib/editor/global/index.js +1 -0
  110. package/lib/editor/global/index.js.map +1 -1
  111. package/lib/editor/input/AcEdCursorManager.d.ts +4 -0
  112. package/lib/editor/input/AcEdCursorManager.d.ts.map +1 -1
  113. package/lib/editor/input/AcEdCursorManager.js +11 -8
  114. package/lib/editor/input/AcEdCursorManager.js.map +1 -1
  115. package/lib/editor/input/AcEdOrthoMode.d.ts +11 -0
  116. package/lib/editor/input/AcEdOrthoMode.d.ts.map +1 -0
  117. package/lib/editor/input/AcEdOrthoMode.js +22 -0
  118. package/lib/editor/input/AcEdOrthoMode.js.map +1 -0
  119. package/lib/editor/input/AcEdPolarTracking.d.ts +107 -0
  120. package/lib/editor/input/AcEdPolarTracking.d.ts.map +1 -0
  121. package/lib/editor/input/AcEdPolarTracking.js +398 -0
  122. package/lib/editor/input/AcEdPolarTracking.js.map +1 -0
  123. package/lib/editor/input/AcEdSelectionFilter.js +7 -6
  124. package/lib/editor/input/AcEdSelectionFilter.js.map +1 -1
  125. package/lib/editor/input/AcEditor.d.ts +4 -0
  126. package/lib/editor/input/AcEditor.d.ts.map +1 -1
  127. package/lib/editor/input/AcEditor.js +6 -0
  128. package/lib/editor/input/AcEditor.js.map +1 -1
  129. package/lib/editor/input/index.d.ts +2 -0
  130. package/lib/editor/input/index.d.ts.map +1 -1
  131. package/lib/editor/input/index.js +2 -0
  132. package/lib/editor/input/index.js.map +1 -1
  133. package/lib/editor/input/marker/AcEdMarker.d.ts.map +1 -1
  134. package/lib/editor/input/marker/AcEdMarker.js +3 -2
  135. package/lib/editor/input/marker/AcEdMarker.js.map +1 -1
  136. package/lib/editor/input/marker/AcEdOSnapMarkerManager.d.ts +4 -0
  137. package/lib/editor/input/marker/AcEdOSnapMarkerManager.d.ts.map +1 -1
  138. package/lib/editor/input/marker/AcEdOSnapMarkerManager.js +11 -0
  139. package/lib/editor/input/marker/AcEdOSnapMarkerManager.js.map +1 -1
  140. package/lib/editor/input/prompt/AcEdPromptDistanceOptions.d.ts.map +1 -1
  141. package/lib/editor/input/prompt/AcEdPromptDistanceOptions.js +1 -1
  142. package/lib/editor/input/prompt/AcEdPromptDistanceOptions.js.map +1 -1
  143. package/lib/editor/input/ui/AcEdCommandLine.d.ts.map +1 -1
  144. package/lib/editor/input/ui/AcEdCommandLine.js +1 -1
  145. package/lib/editor/input/ui/AcEdCommandLine.js.map +1 -1
  146. package/lib/editor/input/ui/AcEdFloatingInput.d.ts +13 -0
  147. package/lib/editor/input/ui/AcEdFloatingInput.d.ts.map +1 -1
  148. package/lib/editor/input/ui/AcEdFloatingInput.js +48 -3
  149. package/lib/editor/input/ui/AcEdFloatingInput.js.map +1 -1
  150. package/lib/editor/input/ui/AcEdFloatingInputTypes.d.ts +5 -0
  151. package/lib/editor/input/ui/AcEdFloatingInputTypes.d.ts.map +1 -1
  152. package/lib/editor/input/ui/AcEdFloatingMessage.js +1 -1
  153. package/lib/editor/input/ui/AcEdFloatingMessage.js.map +1 -1
  154. package/lib/editor/input/ui/AcEdInputManager.d.ts +25 -6
  155. package/lib/editor/input/ui/AcEdInputManager.d.ts.map +1 -1
  156. package/lib/editor/input/ui/AcEdInputManager.js +164 -63
  157. package/lib/editor/input/ui/AcEdInputManager.js.map +1 -1
  158. package/lib/editor/input/ui/AcEdMTextEditor.d.ts +67 -2
  159. package/lib/editor/input/ui/AcEdMTextEditor.d.ts.map +1 -1
  160. package/lib/editor/input/ui/AcEdMTextEditor.js +213 -21
  161. package/lib/editor/input/ui/AcEdMTextEditor.js.map +1 -1
  162. package/lib/editor/input/ui/AcEdRubberBand.d.ts +8 -0
  163. package/lib/editor/input/ui/AcEdRubberBand.d.ts.map +1 -1
  164. package/lib/editor/input/ui/AcEdRubberBand.js +62 -37
  165. package/lib/editor/input/ui/AcEdRubberBand.js.map +1 -1
  166. package/lib/i18n/en/command.d.ts +71 -11
  167. package/lib/i18n/en/command.d.ts.map +1 -1
  168. package/lib/i18n/en/command.js +74 -14
  169. package/lib/i18n/en/command.js.map +1 -1
  170. package/lib/i18n/en/jig.d.ts +8 -0
  171. package/lib/i18n/en/jig.d.ts.map +1 -1
  172. package/lib/i18n/en/jig.js +8 -0
  173. package/lib/i18n/en/jig.js.map +1 -1
  174. package/lib/i18n/zh/command.d.ts +71 -11
  175. package/lib/i18n/zh/command.d.ts.map +1 -1
  176. package/lib/i18n/zh/command.js +74 -14
  177. package/lib/i18n/zh/command.js.map +1 -1
  178. package/lib/i18n/zh/jig.d.ts +8 -0
  179. package/lib/i18n/zh/jig.d.ts.map +1 -1
  180. package/lib/i18n/zh/jig.js +8 -0
  181. package/lib/i18n/zh/jig.js.map +1 -1
  182. package/lib/index.d.ts +2 -1
  183. package/lib/index.d.ts.map +1 -1
  184. package/lib/index.js +2 -1
  185. package/lib/index.js.map +1 -1
  186. package/lib/plugin/AcApLazyPluginRegistration.d.ts +28 -0
  187. package/lib/plugin/AcApLazyPluginRegistration.d.ts.map +1 -0
  188. package/lib/plugin/AcApLazyPluginRegistration.js +2 -0
  189. package/lib/plugin/AcApLazyPluginRegistration.js.map +1 -0
  190. package/lib/plugin/AcApPluginManager.d.ts +38 -0
  191. package/lib/plugin/AcApPluginManager.d.ts.map +1 -1
  192. package/lib/plugin/AcApPluginManager.js +147 -22
  193. package/lib/plugin/AcApPluginManager.js.map +1 -1
  194. package/lib/plugin/index.d.ts +1 -0
  195. package/lib/plugin/index.d.ts.map +1 -1
  196. package/lib/plugin/index.js +1 -0
  197. package/lib/plugin/index.js.map +1 -1
  198. package/lib/util/AcApExportFileNameUtil.d.ts +22 -0
  199. package/lib/util/AcApExportFileNameUtil.d.ts.map +1 -0
  200. package/lib/util/AcApExportFileNameUtil.js +49 -0
  201. package/lib/util/AcApExportFileNameUtil.js.map +1 -0
  202. package/lib/util/AcApFontUtil.d.ts +79 -0
  203. package/lib/util/AcApFontUtil.d.ts.map +1 -0
  204. package/lib/util/AcApFontUtil.js +173 -0
  205. package/lib/util/AcApFontUtil.js.map +1 -0
  206. package/lib/util/index.d.ts +3 -0
  207. package/lib/util/index.d.ts.map +1 -1
  208. package/lib/util/index.js +3 -0
  209. package/lib/util/index.js.map +1 -1
  210. package/lib/util/yieldToMain.d.ts +5 -0
  211. package/lib/util/yieldToMain.d.ts.map +1 -0
  212. package/lib/util/yieldToMain.js +11 -0
  213. package/lib/util/yieldToMain.js.map +1 -0
  214. package/lib/view/AcTrLayoutView.d.ts +13 -1
  215. package/lib/view/AcTrLayoutView.d.ts.map +1 -1
  216. package/lib/view/AcTrLayoutView.js +20 -1
  217. package/lib/view/AcTrLayoutView.js.map +1 -1
  218. package/lib/view/AcTrLayoutViewManager.d.ts +3 -1
  219. package/lib/view/AcTrLayoutViewManager.d.ts.map +1 -1
  220. package/lib/view/AcTrLayoutViewManager.js +4 -2
  221. package/lib/view/AcTrLayoutViewManager.js.map +1 -1
  222. package/lib/view/AcTrScene.d.ts +38 -6
  223. package/lib/view/AcTrScene.d.ts.map +1 -1
  224. package/lib/view/AcTrScene.js +57 -30
  225. package/lib/view/AcTrScene.js.map +1 -1
  226. package/lib/view/AcTrView2d.d.ts +184 -1
  227. package/lib/view/AcTrView2d.d.ts.map +1 -1
  228. package/lib/view/AcTrView2d.js +657 -104
  229. package/lib/view/AcTrView2d.js.map +1 -1
  230. package/lib/view/index.d.ts +1 -0
  231. package/lib/view/index.d.ts.map +1 -1
  232. package/lib/view/index.js +1 -0
  233. package/lib/view/index.js.map +1 -1
  234. package/package.json +12 -9
  235. package/lib/command/convert/AcApConvertToSvgCmd.d.ts +0 -32
  236. package/lib/command/convert/AcApConvertToSvgCmd.d.ts.map +0 -1
  237. package/lib/command/convert/AcApConvertToSvgCmd.js +0 -98
  238. package/lib/command/convert/AcApConvertToSvgCmd.js.map +0 -1
  239. package/lib/command/convert/AcApSvgConvertor.d.ts +0 -53
  240. package/lib/command/convert/AcApSvgConvertor.d.ts.map +0 -1
  241. package/lib/command/convert/AcApSvgConvertor.js +0 -103
  242. package/lib/command/convert/AcApSvgConvertor.js.map +0 -1
@@ -71,14 +71,15 @@ var __values = (this && this.__values) || function(o) {
71
71
  };
72
72
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
73
73
  };
74
- import { acdbHostApplicationServices, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, log } from '@mlightcad/data-model';
74
+ import { acdbHostApplicationServices, AcDbMText, AcDbRasterImage, AcDbRay, AcDbSysVarManager, AcDbViewport, AcDbXline, AcGeBox2d, AcGePoint2d, log } from '@mlightcad/data-model';
75
75
  import { AcDbSystemVariables } from '@mlightcad/data-model';
76
76
  import { AcTrEntity, AcTrGroup, AcTrRenderer, AcTrViewportView, getMaterialMetadata, hasByLayerBinding, setMaterialMetadata } from '@mlightcad/three-renderer';
77
77
  import * as THREE from 'three';
78
78
  import Stats from 'three/examples/jsm/libs/stats.module';
79
79
  import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
80
80
  import { AcApDocManager, AcApSettingManager } from '../app';
81
- import { AcEdBaseView, AcEdConditionWaiter, AcEdCorsorType, AcEdViewMode, applyUiThemeFromBackground, eventBus, resolvePointerSelectionAction } from '../editor';
81
+ import { AcEdBaseView, AcEdConditionWaiter, AcEdCorsorType, AcEdMTextEditor, AcEdOpenMode, AcEdViewMode, eventBus, resolvePointerSelectionAction } from '../editor';
82
+ import { isModelSpaceDatabase, MODEL_SPACE_BACKGROUND, readLayoutBackgroundColor } from '../editor/global/AcEdUiColor';
82
83
  import { AcTrGeometryUtil } from '../util';
83
84
  import { AcTrLayoutView } from './AcTrLayoutView';
84
85
  import { AcTrLayoutViewManager } from './AcTrLayoutViewManager';
@@ -88,7 +89,7 @@ import { AcTrScene } from './AcTrScene';
88
89
  * Default view option values
89
90
  */
90
91
  export var DEFAULT_VIEW_2D_OPTIONS = {
91
- background: 0x000000
92
+ background: MODEL_SPACE_BACKGROUND
92
93
  };
93
94
  /**
94
95
  * A 2D CAD viewer component that renders CAD drawings using Three.js.
@@ -124,7 +125,7 @@ var AcTrView2d = /** @class */ (function (_super) {
124
125
  function AcTrView2d(options) {
125
126
  if (options === void 0) { options = DEFAULT_VIEW_2D_OPTIONS; }
126
127
  var _this = this;
127
- var _a;
128
+ var _a, _b;
128
129
  var mergedOptions = __assign(__assign({}, DEFAULT_VIEW_2D_OPTIONS), options);
129
130
  var container = (_a = mergedOptions.container) !== null && _a !== void 0 ? _a : document.createElement('div');
130
131
  mergedOptions.container = container;
@@ -145,6 +146,37 @@ var AcTrView2d = /** @class */ (function (_super) {
145
146
  * A value of `null` indicates that no animation frame is currently scheduled.
146
147
  */
147
148
  _this._rafId = null;
149
+ /**
150
+ * Block table record ids of layouts whose entities are currently being
151
+ * batch-converted into the scene. Used by
152
+ * {@link AcTrView2d.loadLayoutEntitiesIfNeeded} to guard against
153
+ * re-entrant calls before the `setTimeout` callback flips
154
+ * `AcTrLayout.isLoaded` to `true`, which would otherwise duplicate
155
+ * entities when the same layout tab is clicked twice in quick succession.
156
+ */
157
+ _this._loadingLayouts = new Set();
158
+ /**
159
+ * Block table record ids of layouts that have already received an
160
+ * initial zoom-to-fit. Used by the `layoutSwitched` handler to apply
161
+ * the auto-zoom **only on the first user visit** to each layout, and
162
+ * to preserve the camera state on subsequent visits (matches AutoCAD's
163
+ * per-tab view persistence).
164
+ *
165
+ * Cannot be inferred from `_layoutViewManager.has(btrId)` because
166
+ * `addLayout` pre-creates an `AcTrLayoutView` for every layout in the
167
+ * DWG at document load time — by the time the user clicks a layout
168
+ * tab the view already exists, so "first existence of view" is
169
+ * always false. This set tracks the orthogonal question "has the user
170
+ * actually focused on this layout before?".
171
+ *
172
+ * Marked from two entry points:
173
+ * - `onAfterOpenDocument` (via `markLayoutAsInitialized`): the
174
+ * document's startup layout is initialized externally, so we don't
175
+ * auto-zoom again when the user clicks back to it.
176
+ * - `layoutSwitched` handler: after the first user-driven switch
177
+ * completes its initial zoom-to-fit.
178
+ */
179
+ _this._initializedLayouts = new Set();
148
180
  _this.animate = function () {
149
181
  var _a;
150
182
  _this._rafId = requestAnimationFrame(_this.animate);
@@ -152,14 +184,15 @@ var AcTrView2d = /** @class */ (function (_super) {
152
184
  render: _this._renderer,
153
185
  camera: _this.internalCamera
154
186
  });
155
- if (!_this._isDirty)
187
+ var stillLoading = _this._numOfEntitiesToProcess > 0;
188
+ if (!_this._isDirty && !stillLoading)
156
189
  return;
157
- _this._layoutViewManager.render(_this._scene);
190
+ var needsRedraw = _this._layoutViewManager.render(_this._scene);
158
191
  if (_this.internalCamera) {
159
192
  _this._css2dRenderer.render(_this._scene.internalScene, _this.internalCamera);
160
193
  }
161
194
  (_a = _this._stats) === null || _a === void 0 ? void 0 : _a.update();
162
- _this._isDirty = false;
195
+ _this._isDirty = stillLoading || needsRedraw;
163
196
  };
164
197
  if (options.calculateSizeCallback) {
165
198
  _this.setCalculateSizeCallback(options.calculateSizeCallback);
@@ -177,46 +210,26 @@ var AcTrView2d = /** @class */ (function (_super) {
177
210
  });
178
211
  });
179
212
  _this._scene = _this.createScene();
180
- // Initialize background color through setter to keep renderer/cursor/foreground in sync.
181
- _this.backgroundColor = mergedOptions.background || 0x000000;
213
+ // Initialize background color through setter to keep renderer/cursor in sync.
214
+ _this.backgroundColor = (_b = mergedOptions.background) !== null && _b !== void 0 ? _b : MODEL_SPACE_BACKGROUND;
182
215
  _this._stats = _this.createStats(AcApSettingManager.instance.isShowStats);
183
- // Two sysvars can drive the canvas background:
184
- //
185
- // - WHITEBKCOLOR (boolean): the low-level "is the paper white?" flag
186
- // that the View has always honoured.
187
- // - COLORTHEME (number): the AutoCAD-standard theme selector where
188
- // 0 = dark theme (black bg) and 1 = light theme (white bg).
189
- //
190
- // The Vue composable `useDark` (cad-viewer) toggles only COLORTHEME
191
- // when the user flips the theme. Without this bridge, toggling the
192
- // UI theme left WHITEBKCOLOR stale and the canvas kept its previous
193
- // background even though `changeForeground` had been fired through
194
- // MTEXT/line inversion — producing e.g. a black canvas in light mode
195
- // or a white canvas in dark mode.
196
- //
197
- // Listening to both sysvars keeps either entry point working. The
198
- // two remain independent (settable in isolation) because setting
199
- // `this.backgroundColor` is idempotent and the handler for each
200
- // sysvar only fires when that specific variable changes.
201
- AcDbSysVarManager.instance().events.sysVarChanged.addEventListener(function (args) {
216
+ // Layout background sysvars drive the canvas clear colour and ACI-7
217
+ // inversion (`MODELBKCOLOR` for model space, `PAPERBKCOLOR` for the
218
+ // active layout). `COLORTHEME` only affects UI chrome see
219
+ // `useDark` / `AcEdUiTheme`, not the renderer foreground.
220
+ var sysVarManager = AcDbSysVarManager.instance();
221
+ var modelBkVar = AcDbSystemVariables.MODELBKCOLOR.toLowerCase();
222
+ var paperBkVar = AcDbSystemVariables.PAPERBKCOLOR.toLowerCase();
223
+ sysVarManager.events.sysVarChanged.addEventListener(function (args) {
202
224
  var nameLower = args.name.toLowerCase();
203
- if (nameLower === AcDbSystemVariables.WHITEBKCOLOR.toLowerCase()) {
204
- var useWhiteBackgroundColor = args.newVal;
205
- _this.backgroundColor = useWhiteBackgroundColor ? 0xffffff : 0;
206
- }
207
- else if (nameLower === AcDbSystemVariables.COLORTHEME.toLowerCase()) {
208
- // COLORTHEME is registered with type 'number' in the data-model
209
- // (0 = dark, 1 = light), but the sysvar bus does not strictly
210
- // coerce values. Normalise defensively so plugins setting the
211
- // value as a string or boolean still behave correctly.
212
- var newVal = args.newVal;
213
- var isLight = typeof newVal === 'number'
214
- ? newVal === 1
215
- : typeof newVal === 'boolean'
216
- ? newVal
217
- : String(newVal).toLowerCase() === 'light' ||
218
- String(newVal) === '1';
219
- _this.backgroundColor = isLight ? 0xffffff : 0;
225
+ if (nameLower === modelBkVar || nameLower === paperBkVar) {
226
+ var isModelSpace = _this.isModelSpaceLayout(args.database);
227
+ var applies = (nameLower === modelBkVar && isModelSpace) ||
228
+ (nameLower === paperBkVar && !isModelSpace);
229
+ if (!applies) {
230
+ return;
231
+ }
232
+ _this.applyCanvasBackground(readLayoutBackgroundColor(args.database, isModelSpace));
220
233
  }
221
234
  });
222
235
  AcApSettingManager.instance.events.modified.addEventListener(function (args) {
@@ -228,7 +241,9 @@ var AcTrView2d = /** @class */ (function (_super) {
228
241
  var selectionStartCanvas = null;
229
242
  var selectionPreviewEl = null;
230
243
  var canHandleSelectionGesture = function () {
231
- return _this.mode === AcEdViewMode.SELECTION && !_this.editor.isActive;
244
+ return (_this.mode === AcEdViewMode.SELECTION &&
245
+ !_this.editor.isActive &&
246
+ !AcEdMTextEditor.getActiveInputBox());
232
247
  };
233
248
  var clearSelectionPreview = function () {
234
249
  selectionPreviewEl === null || selectionPreviewEl === void 0 ? void 0 : selectionPreviewEl.remove();
@@ -302,6 +317,16 @@ var AcTrView2d = /** @class */ (function (_super) {
302
317
  selectionStartWcs = null;
303
318
  selectionStartCanvas = null;
304
319
  });
320
+ _this.canvas.addEventListener('dblclick', function (e) {
321
+ if (e.button !== 0)
322
+ return;
323
+ if (!canHandleSelectionGesture())
324
+ return;
325
+ if (AcApDocManager.instance.curDocument.openMode !== AcEdOpenMode.Write) {
326
+ return;
327
+ }
328
+ void _this.openPickedMTextEditor(e);
329
+ });
305
330
  // When using OrbitControls in THREE.js, it attaches its own event listeners to the DOM elements,
306
331
  // such as the canvas or the entire document. This can interfere with other event listeners you
307
332
  // add, including the keydown event.
@@ -333,10 +358,51 @@ var AcTrView2d = /** @class */ (function (_super) {
333
358
  }
334
359
  });
335
360
  acdbHostApplicationServices().layoutManager.events.layoutSwitched.addEventListener(function (args) {
336
- _this.activeLayoutBtrId = args.layout.blockTableRecordId;
337
- _this.createLayoutViewIfNeeded(args.layout.blockTableRecordId);
338
- _this.loadLayoutEntitiesIfNeeded(args.layout.blockTableRecordId);
361
+ var btrId = args.layout.blockTableRecordId;
362
+ // "First visit" is tracked separately from view existence because
363
+ // `addLayout` pre-creates an `AcTrLayoutView` for every layout in
364
+ // the DWG at load time — `_layoutViewManager.has(btrId)` is
365
+ // therefore `true` even for layouts the user has never focused
366
+ // on, and "first visit" computed from it would always be false.
367
+ // We use a dedicated set instead, marked here on first switch and
368
+ // also from `markLayoutAsInitialized` when the document opens
369
+ // straight into a layout (AcApDocManager has already framed it).
370
+ // Each AcTrLayoutView owns its own camera, so on subsequent
371
+ // visits the previous camera state is naturally restored and we
372
+ // must NOT zoom-to-fit again — that would be jarring and
373
+ // diverges from how AutoCAD preserves per-tab view state.
374
+ var isFirstVisit = !_this._initializedLayouts.has(btrId);
375
+ _this._initializedLayouts.add(btrId);
376
+ // Clear measurement overlays before swapping layouts.
377
+ // Measurements are screen/coordinate-anchored — their dimension
378
+ // text, hatch indicators, and HTML overlays were laid out in
379
+ // the previous layout's WCS (paper coords, ~unit scale) and
380
+ // would render at nonsense positions in a different layout
381
+ // (model WCS is typically O(10^5) larger, paper layouts use
382
+ // their own sheet coords). Selection state is intentionally
383
+ // **not** cleared here: it is entity-id-based and the same
384
+ // entity stays selected wherever it is rendered (the model
385
+ // entity drilled through a paper viewport remains visually
386
+ // selected when the user returns to model space, matching
387
+ // AutoCAD desktop's behaviour).
388
+ //
389
+ // Dynamic import avoids a circular dependency: the cleanup
390
+ // module already imports `AcTrView2d` for its
391
+ // `htmlTransientManager` cast, so a static import here would
392
+ // create a cycle. The cost (one extra microtask) is
393
+ // negligible for a layout switch.
394
+ void import('../command/measure/AcApClearMeasurementsCmd').then(function (_a) {
395
+ var clearAllMeasurements = _a.clearAllMeasurements;
396
+ return clearAllMeasurements(_this);
397
+ });
398
+ _this.activeLayoutBtrId = btrId;
399
+ _this.createLayoutViewIfNeeded(btrId);
400
+ _this.loadLayoutEntitiesIfNeeded(btrId);
401
+ _this.refreshCanvasBackgroundForActiveLayout();
339
402
  _this._isDirty = true;
403
+ if (isFirstVisit) {
404
+ _this.applyInitialZoom(btrId, args.layout);
405
+ }
340
406
  });
341
407
  _this._css2dRenderer = new CSS2DRenderer();
342
408
  _this._css2dRenderer.setSize(_this.width, _this.height);
@@ -466,18 +532,49 @@ var AcTrView2d = /** @class */ (function (_super) {
466
532
  * @param value - The background color as a 24-bit hexadecimal RGB number
467
533
  */
468
534
  set: function (value) {
469
- this._renderer.setClearColor(value);
470
- this._renderer.changeForeground(value == 0 ? 0xffffff : 0);
471
- // Store the canvas colour for theme-sensitive materials created after this
472
- // point. `changeForeground` above keeps ACI 7 linework and hatches visible.
473
- this._renderer.currentBackgroundColor = value;
474
- this.editor.setCursorColor(value == 0 ? 'white' : 'black');
475
- applyUiThemeFromBackground(value);
476
- this._isDirty = true;
535
+ this.applyCanvasBackground(value);
477
536
  },
478
537
  enumerable: false,
479
538
  configurable: true
480
539
  });
540
+ /**
541
+ * Applies canvas background colour from layout background sysvars or explicit
542
+ * API calls. Also refreshes ACI-7 foreground inversion via the style
543
+ * manager. Does not touch `COLORTHEME` / UI chrome.
544
+ */
545
+ AcTrView2d.prototype.applyCanvasBackground = function (value) {
546
+ this._renderer.setClearColor(value);
547
+ this._renderer.currentBackgroundColor = value;
548
+ this._renderer.changeBackground(value);
549
+ this.editor.syncCursorBackground(value);
550
+ this._isDirty = true;
551
+ };
552
+ AcTrView2d.prototype.isModelSpaceLayout = function (database) {
553
+ if (!database) {
554
+ return this.activeLayoutBtrId === this.modelSpaceBtrId;
555
+ }
556
+ return isModelSpaceDatabase(database);
557
+ };
558
+ /**
559
+ * Re-reads layout background sysvars from the active database. Called after
560
+ * a document is opened so DWG-stored values take effect.
561
+ */
562
+ AcTrView2d.prototype.syncDisplaySysVars = function (database) {
563
+ this.applyCanvasBackground(readLayoutBackgroundColor(database, this.isModelSpaceLayout(database)));
564
+ };
565
+ /**
566
+ * Re-apply canvas background after a layout tab switch using the sysvar for
567
+ * the newly active layout (model or paper space).
568
+ */
569
+ AcTrView2d.prototype.refreshCanvasBackgroundForActiveLayout = function () {
570
+ var _a, _b;
571
+ var docManager = AcApDocManager;
572
+ var database = (_b = (_a = docManager._instance) === null || _a === void 0 ? void 0 : _a.curDocument) === null || _b === void 0 ? void 0 : _b.database;
573
+ if (!database)
574
+ return;
575
+ var isModelSpace = this.activeLayoutBtrId === this.modelSpaceBtrId;
576
+ this.applyCanvasBackground(readLayoutBackgroundColor(database, isModelSpace));
577
+ };
481
578
  Object.defineProperty(AcTrView2d.prototype, "modelSpaceBtrId", {
482
579
  /**
483
580
  * The block table record id of the model space
@@ -526,6 +623,16 @@ var AcTrView2d = /** @class */ (function (_super) {
526
623
  enumerable: false,
527
624
  configurable: true
528
625
  });
626
+ Object.defineProperty(AcTrView2d.prototype, "cadScene", {
627
+ /**
628
+ * CAD scene graph used for rendering and HTML export.
629
+ */
630
+ get: function () {
631
+ return this._scene;
632
+ },
633
+ enumerable: false,
634
+ configurable: true
635
+ });
529
636
  Object.defineProperty(AcTrView2d.prototype, "internalScene", {
530
637
  /**
531
638
  * The internal THREE scene used by this view.
@@ -653,30 +760,248 @@ var AcTrView2d = /** @class */ (function (_super) {
653
760
  this.activeLayoutView.flyTo(point, scale);
654
761
  this._isDirty = true;
655
762
  };
763
+ AcTrView2d.prototype.openPickedMTextEditor = function (e) {
764
+ return __awaiter(this, void 0, void 0, function () {
765
+ var point, worldPoint, picked, entity;
766
+ return __generator(this, function (_a) {
767
+ switch (_a.label) {
768
+ case 0:
769
+ point = this.viewportToCanvas({
770
+ x: e.clientX,
771
+ y: e.clientY
772
+ });
773
+ worldPoint = this.screenToWorld(point);
774
+ picked = this.pick(worldPoint, undefined, true);
775
+ if (!picked.length)
776
+ return [2 /*return*/];
777
+ entity = AcApDocManager.instance.curDocument.database.tables.blockTable.getEntityById(picked[0].id);
778
+ if (!(entity instanceof AcDbMText))
779
+ return [2 /*return*/];
780
+ e.preventDefault();
781
+ return [4 /*yield*/, this.editMTextEntity(entity)];
782
+ case 1:
783
+ _a.sent();
784
+ return [2 /*return*/];
785
+ }
786
+ });
787
+ });
788
+ };
789
+ AcTrView2d.prototype.editMTextEntity = function (mtext) {
790
+ return __awaiter(this, void 0, void 0, function () {
791
+ var editor, applied, result;
792
+ return __generator(this, function (_a) {
793
+ switch (_a.label) {
794
+ case 0:
795
+ if (mtext.lineSpacingFactor !== AcEdMTextEditor.defaultLineSpacingFactor) {
796
+ mtext.lineSpacingFactor = AcEdMTextEditor.defaultLineSpacingFactor;
797
+ mtext.triggerModifiedEvent();
798
+ }
799
+ // Hide the in-scene MTEXT while the inline editor renders its own copy; otherwise
800
+ // both draw at once (double text) when the user double-clicks to edit.
801
+ // this.removeEntity(mtext)
802
+ this._isDirty = true;
803
+ editor = new AcEdMTextEditor();
804
+ applied = false;
805
+ _a.label = 1;
806
+ case 1:
807
+ _a.trys.push([1, , 3, 4]);
808
+ return [4 /*yield*/, editor.open({
809
+ view: this,
810
+ location: mtext.location,
811
+ width: this.resolveMTextEditorWidth(mtext),
812
+ textHeight: this.resolveMTextEditorTextHeight(mtext),
813
+ initialText: mtext.contents,
814
+ initialAttachmentPoint: mtext.attachmentPoint,
815
+ toolbarFontFamilies: this.getMTextToolbarFontFamilies()
816
+ })];
817
+ case 2:
818
+ result = _a.sent();
819
+ if (!result)
820
+ return [2 /*return*/];
821
+ mtext.location = result.location;
822
+ mtext.contents = result.contents;
823
+ mtext.width = result.width;
824
+ mtext.height = result.height;
825
+ mtext.lineSpacingFactor = result.lineSpacingFactor;
826
+ mtext.attachmentPoint = result.attachmentPoint;
827
+ mtext.triggerModifiedEvent();
828
+ applied = true;
829
+ return [3 /*break*/, 4];
830
+ case 3:
831
+ if (!applied) {
832
+ this.updateEntity(mtext);
833
+ this._isDirty = true;
834
+ }
835
+ return [7 /*endfinally*/];
836
+ case 4: return [2 /*return*/];
837
+ }
838
+ });
839
+ });
840
+ };
841
+ AcTrView2d.prototype.resolveMTextEditorWidth = function (mtext) {
842
+ var width = Number(mtext.width);
843
+ if (Number.isFinite(width) && width > 0)
844
+ return width;
845
+ return 1e-4;
846
+ };
847
+ AcTrView2d.prototype.resolveMTextEditorTextHeight = function (mtext) {
848
+ var textHeight = Number(mtext.height);
849
+ if (Number.isFinite(textHeight) && textHeight > 0)
850
+ return textHeight;
851
+ return this.pixelsToWorldY(24);
852
+ };
853
+ AcTrView2d.prototype.pixelsToWorldY = function (pixels) {
854
+ var p0 = this.screenToWorld({ x: 0, y: 0 });
855
+ var p1 = this.screenToWorld({ x: 0, y: pixels });
856
+ return Math.max(Math.abs(p1.y - p0.y), 1e-4);
857
+ };
858
+ AcTrView2d.prototype.getMTextToolbarFontFamilies = function () {
859
+ return Array.from(new Set(AcApDocManager.instance.avaiableFonts
860
+ .flatMap(function (fontInfo) { return fontInfo.name; })
861
+ .map(function (fontName) { return fontName.trim(); })
862
+ .filter(function (fontName) { return fontName.length > 0; })));
863
+ };
656
864
  /**
657
865
  * @inheritdoc
866
+ *
867
+ * In **paper space** layouts the selection pipeline supports
868
+ * "drill-through": clicks inside a viewport rectangle resolve against
869
+ * the model-space entities that are visually rendered through that
870
+ * viewport, rather than picking the viewport's border. Clicks **near**
871
+ * the border still pick the `AcDbViewport` entity itself so the user
872
+ * can grip, move, lock or delete the viewport.
873
+ *
874
+ * This mirrors AutoCAD **web** behaviour (single-click selection of
875
+ * model content through the viewport). The desktop ARX behaviour
876
+ * (explicit MSPACE/PSPACE modes, CVPORT system variable, double-click
877
+ * to enter mspace) is a separate, larger feature — tracked in
878
+ * `.claude/plans/next_14_viewports_full.md` PR-γ Option A. We
879
+ * intentionally do **not** implement it here.
880
+ *
881
+ * The border vs interior decision uses a tolerance derived from
882
+ * `selectionBoxSize` (the same pixel-sized hit radius used elsewhere
883
+ * in pick) converted to paper-space WCS via `pointToBox`. This keeps
884
+ * the gesture consistent with how other entity edges behave — you
885
+ * don't have to land pixel-perfect on the viewport line to grab it.
658
886
  */
659
887
  AcTrView2d.prototype.pick = function (point, hitRadius, pickOneOnly) {
888
+ var e_1, _a;
660
889
  if (point == null)
661
890
  point = this.curPos;
662
891
  var results = [];
663
892
  var activeLayout = this._scene.activeLayout;
664
- if (activeLayout) {
665
- var activeLayoutView = this.activeLayoutView;
666
- var box = activeLayoutView.pointToBox(point, hitRadius !== null && hitRadius !== void 0 ? hitRadius : this.selectionBoxSize);
667
- var firstQueryResults = this._scene.search(box);
668
- var threshold = Math.max(box.size.width / 2, box.size.height / 2);
669
- var raycaster_1 = activeLayoutView.resetRaycaster(point, threshold);
670
- firstQueryResults.forEach(function (item) {
671
- var objectId = item.id;
672
- if (activeLayout.isIntersectWith(objectId, raycaster_1)) {
893
+ if (!activeLayout)
894
+ return results;
895
+ var activeLayoutView = this.activeLayoutView;
896
+ var effectiveHitRadius = hitRadius !== null && hitRadius !== void 0 ? hitRadius : this.selectionBoxSize;
897
+ var paperBox = activeLayoutView.pointToBox(point, effectiveHitRadius);
898
+ var threshold = Math.max(paperBox.size.width / 2, paperBox.size.height / 2);
899
+ // Identify drill-through viewports (paper space only): viewports whose
900
+ // paper rectangle contains the click AND whose border is NOT within
901
+ // tolerance of the click. The border tolerance is the average of the
902
+ // hit-box width/height — a robust, scale-aware proxy for "user is
903
+ // trying to grab the frame, not click inside".
904
+ var isPaperSpace = activeLayoutView.layoutBtrId !== this._scene.modelSpaceBtrId;
905
+ var borderTolerance = (paperBox.size.width + paperBox.size.height) / 2;
906
+ var drillThroughViewports = [];
907
+ var drillThroughViewportIds = new Set();
908
+ if (isPaperSpace) {
909
+ try {
910
+ for (var _b = __values(activeLayoutView.viewportViews), _c = _b.next(); !_c.done; _c = _b.next()) {
911
+ var vpView = _c.value;
912
+ if (vpView.containsPaperPoint(point) &&
913
+ !vpView.isNearPaperBorder(point, borderTolerance)) {
914
+ drillThroughViewports.push(vpView);
915
+ drillThroughViewportIds.add(vpView.viewport.id);
916
+ }
917
+ }
918
+ }
919
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
920
+ finally {
921
+ try {
922
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
923
+ }
924
+ finally { if (e_1) throw e_1.error; }
925
+ }
926
+ }
927
+ // 1) Resolve hits in the active layout. Skip the `AcDbViewport`
928
+ // entity for any viewport we're drilling through — otherwise the
929
+ // rectangle's bounding box always wins (it covers the whole click
930
+ // region) and selection feels "stuck on the frame".
931
+ var firstQueryResults = this._scene.search(paperBox);
932
+ var raycaster = activeLayoutView.resetRaycaster(point, threshold);
933
+ firstQueryResults.forEach(function (item) {
934
+ if (drillThroughViewportIds.has(item.id))
935
+ return;
936
+ if (activeLayout.isIntersectWith(item.id, raycaster)) {
937
+ results.push(item);
938
+ }
939
+ });
940
+ // 2) For each drill-through viewport, resolve hits against the
941
+ // model-space layout using the viewport's own camera/raycaster.
942
+ if (drillThroughViewports.length > 0) {
943
+ this.pickThroughViewports(point, paperBox, drillThroughViewports, results);
944
+ }
945
+ var sortedResults = sortPickResults(results, point);
946
+ return pickOneOnly ? sortedResults.slice(0, 1) : sortedResults;
947
+ };
948
+ /**
949
+ * Resolves hits against the model-space layout for each viewport the
950
+ * click drills through. Appends the matches into `results` (caller
951
+ * sorts/dedups). Kept private and separate from `pick` so the main
952
+ * pick path stays a single straight read.
953
+ *
954
+ * Each viewport gets its own raycaster shot (using the viewport view's
955
+ * own camera, which is zoomed to `viewport.viewBox` in model WCS), so
956
+ * a click that lands in overlapping viewports correctly resolves
957
+ * against each viewport's particular model framing.
958
+ *
959
+ * `pickThroughViewports` does NOT consult the active (paper) layout's
960
+ * spatial index — that work is already done by the caller. It only
961
+ * adds model-space results that would otherwise be invisible to the
962
+ * paper-space pick.
963
+ */
964
+ AcTrView2d.prototype.pickThroughViewports = function (paperPoint, paperBox, viewports, results) {
965
+ var e_2, _a;
966
+ var modelLayout = this._scene.modelSpaceLayout;
967
+ if (!modelLayout)
968
+ return;
969
+ // Half-extent of the paper-space hit box (== "radius" in paper WCS).
970
+ // Multiplied per viewport by its paper→model scale, this becomes a
971
+ // model-WCS radius that the per-viewport raycaster threshold and the
972
+ // spatial-index probe both use. This keeps the hit area visually
973
+ // consistent across viewports at different zoom levels.
974
+ var paperHalfRadius = (paperBox.size.width + paperBox.size.height) / 4;
975
+ var _loop_1 = function (vpView) {
976
+ var modelPt = vpView.paperPointToModel(paperPoint);
977
+ var modelRadius = paperHalfRadius * vpView.paperToModelScale;
978
+ if (modelRadius <= 0)
979
+ return "continue";
980
+ var modelBox = new AcGeBox2d().setFromPoints([
981
+ new AcGePoint2d(modelPt.x - modelRadius, modelPt.y - modelRadius),
982
+ new AcGePoint2d(modelPt.x + modelRadius, modelPt.y + modelRadius)
983
+ ]);
984
+ var vpRaycaster = vpView.resetRaycaster(modelPt, modelRadius);
985
+ var modelHits = modelLayout.search(modelBox);
986
+ modelHits.forEach(function (item) {
987
+ if (modelLayout.isIntersectWith(item.id, vpRaycaster)) {
673
988
  results.push(item);
674
989
  }
675
990
  });
676
- var sortedResults = sortPickResults(results, point);
677
- return pickOneOnly ? sortedResults.slice(0, 1) : sortedResults;
991
+ };
992
+ try {
993
+ for (var viewports_1 = __values(viewports), viewports_1_1 = viewports_1.next(); !viewports_1_1.done; viewports_1_1 = viewports_1.next()) {
994
+ var vpView = viewports_1_1.value;
995
+ _loop_1(vpView);
996
+ }
997
+ }
998
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
999
+ finally {
1000
+ try {
1001
+ if (viewports_1_1 && !viewports_1_1.done && (_a = viewports_1.return)) _a.call(viewports_1);
1002
+ }
1003
+ finally { if (e_2) throw e_2.error; }
678
1004
  }
679
- return results;
680
1005
  };
681
1006
  /**
682
1007
  * @inheritdoc
@@ -851,6 +1176,91 @@ var AcTrView2d = /** @class */ (function (_super) {
851
1176
  this.createLayoutViewIfNeeded(layout.blockTableRecordId);
852
1177
  this._isDirty = true;
853
1178
  };
1179
+ /**
1180
+ * Marks a layout as already framed by an external caller (typically
1181
+ * `AcApDocManager.onAfterOpenDocument`, which zooms the startup
1182
+ * layout right after parsing). Subsequent `layoutSwitched` events
1183
+ * for this btrId will skip their initial zoom-to-fit so the user's
1184
+ * camera state on the startup layout is preserved when they click
1185
+ * back to that tab.
1186
+ *
1187
+ * This is the public counterpart of the `_initializedLayouts` set —
1188
+ * exposed so the application layer can stay in sync with the view's
1189
+ * notion of "which layouts have been framed already" without
1190
+ * needing access to private state.
1191
+ */
1192
+ AcTrView2d.prototype.markLayoutAsInitialized = function (layoutBtrId) {
1193
+ this._initializedLayouts.add(layoutBtrId);
1194
+ };
1195
+ /**
1196
+ * Applies the initial zoom-to-fit for a layout the user just switched
1197
+ * into for the first time. Picks the best available "what should the
1198
+ * camera frame?" signal in this order:
1199
+ *
1200
+ * 1. **`AcDbLayout.limits`** (LIMMIN/LIMMAX) — only when it actually
1201
+ * contains the layout's viewports. Many real DWGs ship with garbage
1202
+ * limits (e.g. `(0,0)-(12,9)` from a legacy template setup) that
1203
+ * don't reflect the actual paper sheet. We reject those by
1204
+ * checking containment against `viewportsBoundingBox`.
1205
+ *
1206
+ * 2. **`AcTrLayoutView.viewportsBoundingBox`** — bounding box of all
1207
+ * real user viewports in the layout. In production sheets viewports
1208
+ * typically span 70-90% of the paper, so this is a great proxy for
1209
+ * the printable area and (crucially) ignores outliers like title
1210
+ * blocks authored in a different unit/scale.
1211
+ *
1212
+ * 3. **`AcDbLayout.extents`** — the layout's own EXTMIN/EXTMAX, if
1213
+ * populated. Many parsers leave this empty (we've seen `(0,0)-(0,0)`),
1214
+ * so it sits below the viewport-based heuristic.
1215
+ *
1216
+ * 4. **`zoomToFitDrawing`** (entity extents from spatial index) —
1217
+ * last-resort fallback for layouts with no viewports and no
1218
+ * sensible limits/extents (e.g. a freshly created empty paper).
1219
+ * Vulnerable to scale-mismatch outliers, but better than no zoom.
1220
+ *
1221
+ * **Critically, this runs through `AcEdConditionWaiter`**: at the
1222
+ * moment `layoutSwitched` fires, the layout's entities (including its
1223
+ * `AcDbViewport`s) have not yet been batch-converted into the scene
1224
+ * — `loadLayoutEntitiesIfNeeded` chunked-converts via `setTimeout`.
1225
+ * Without the waiter, `viewportsBoundingBox` returns undefined and
1226
+ * the strategy degrades into (1) zooming to garbage `limits`, or
1227
+ * (4) zooming to an empty scene box. The waiter polls
1228
+ * `_numOfEntitiesToProcess` and only fires the heuristic once the
1229
+ * conversion is done.
1230
+ */
1231
+ AcTrView2d.prototype.applyInitialZoom = function (btrId, layout) {
1232
+ var _this = this;
1233
+ var waiter = new AcEdConditionWaiter(function () { return _this._numOfEntitiesToProcess <= 0; }, function () {
1234
+ var limits = layout.limits;
1235
+ var layoutView = _this._layoutViewManager.getAt(btrId);
1236
+ var vpsBox = layoutView === null || layoutView === void 0 ? void 0 : layoutView.viewportsBoundingBox;
1237
+ var limitsContainsViewports = (function () {
1238
+ if (!limits || limits.isEmpty())
1239
+ return false;
1240
+ if (!vpsBox)
1241
+ return true;
1242
+ return (limits.min.x <= vpsBox.min.x &&
1243
+ limits.min.y <= vpsBox.min.y &&
1244
+ limits.max.x >= vpsBox.max.x &&
1245
+ limits.max.y >= vpsBox.max.y);
1246
+ })();
1247
+ if (limits && !limits.isEmpty() && limitsContainsViewports) {
1248
+ _this.zoomTo(limits);
1249
+ }
1250
+ else if (vpsBox) {
1251
+ _this.zoomTo(vpsBox);
1252
+ }
1253
+ else if (layout.extents && !layout.extents.isEmpty()) {
1254
+ var extents = layout.extents;
1255
+ _this.zoomTo(new AcGeBox2d({ x: extents.min.x, y: extents.min.y }, { x: extents.max.x, y: extents.max.y }));
1256
+ }
1257
+ else if (_this._scene.box) {
1258
+ _this.zoomTo(AcTrGeometryUtil.threeBox3dToGeBox2d(_this._scene.box));
1259
+ }
1260
+ _this._isDirty = true;
1261
+ }, 300, 0);
1262
+ waiter.start();
1263
+ };
854
1264
  /**
855
1265
  * @inheritdoc
856
1266
  */
@@ -938,10 +1348,26 @@ var AcTrView2d = /** @class */ (function (_super) {
938
1348
  /**
939
1349
  * Load entities from the specified layout if they haven't been loaded yet.
940
1350
  * This ensures that when switching to a layout, all its entities are available for rendering.
1351
+ *
1352
+ * Two non-obvious invariants are enforced here:
1353
+ *
1354
+ * 1. The layout is looked up by `layoutBtrId` (the argument), not by
1355
+ * `this._scene.activeLayout`. The active layout reference happens to
1356
+ * match in the current `layoutSwitched` handler call site, but relying
1357
+ * on it would silently miss layouts that are pre-loaded ahead of
1358
+ * becoming active (e.g. background prefetch).
1359
+ * 2. The `_loadingLayouts` guard prevents re-entrance while the
1360
+ * `setTimeout` chunked-convert callback is still in flight. Without it,
1361
+ * clicking the same layout tab twice in quick succession (or
1362
+ * `layoutSwitched` firing twice during the async window) would iterate
1363
+ * the block table record again and duplicate every entity in the
1364
+ * layout — visible as ghosted overdraw and double the spatial-index
1365
+ * weight.
1366
+ *
941
1367
  * @param layoutBtrId Input the block table record id of the layout
942
1368
  */
943
1369
  AcTrView2d.prototype.loadLayoutEntitiesIfNeeded = function (layoutBtrId) {
944
- var e_1, _a;
1370
+ var e_3, _a;
945
1371
  var _this = this;
946
1372
  try {
947
1373
  var db = AcApDocManager.instance.curDocument.database;
@@ -949,10 +1375,60 @@ var AcTrView2d = /** @class */ (function (_super) {
949
1375
  if (!blockTableRecord) {
950
1376
  return;
951
1377
  }
952
- var layout = this._scene.activeLayout;
953
- if (layout && layout.isLoaded) {
1378
+ var existingLayout = this._scene.layouts.get(layoutBtrId);
1379
+ if (existingLayout && existingLayout.isLoaded) {
954
1380
  return;
955
1381
  }
1382
+ if (this._loadingLayouts.has(layoutBtrId)) {
1383
+ return;
1384
+ }
1385
+ // Ensure `AcTrViewportView`s exist for every real `AcDbViewport`
1386
+ // in this layout when the layout's entities were already streamed
1387
+ // in by the document parser. There is a race in the parser-driven
1388
+ // load path: `addLayout(layout)` creates the `AcTrLayoutView`,
1389
+ // but the parser may dispatch the AcDbViewport entities before
1390
+ // that happens. When that races, `batchConvert`'s viewport
1391
+ // handler does `_layoutViewManager.getAt(entity.ownerId)`, gets
1392
+ // `undefined`, and **silently skips creating the
1393
+ // AcTrViewportView**. The reload path below used to mask this by
1394
+ // re-running batchConvert after the layoutView existed, but the
1395
+ // entityCount-skip optimization that follows removes that
1396
+ // side-effect, so we do the viewport-view-only pass explicitly
1397
+ // here. Skipped when the layout is empty — in that case the
1398
+ // batchConvert path below will create the viewport views directly
1399
+ // as it processes each entity.
1400
+ var layoutView = this._layoutViewManager.getAt(layoutBtrId);
1401
+ if (existingLayout &&
1402
+ existingLayout.entityCount > 0 &&
1403
+ layoutView &&
1404
+ layoutView.viewportCount === 0) {
1405
+ this.ensureViewportViews(blockTableRecord, layoutView);
1406
+ }
1407
+ // Model space (and any other layout pre-populated by the document
1408
+ // parser at open time) lands here without `isLoaded` ever having
1409
+ // been flipped — the initial entity stream goes through
1410
+ // `addEntity()` directly, bypassing this method. Without this
1411
+ // guard, switching back to model space from a paper layout would
1412
+ // re-iterate the full block table record and re-batch-convert
1413
+ // every entity (5759+ on real DWGs), freezing the UI for several
1414
+ // seconds AND duplicating entities (every entity ends up in the
1415
+ // layout twice, doubling the spatial-index weight and render
1416
+ // cost).
1417
+ //
1418
+ // If the layout already has entities, the parser has finished
1419
+ // loading them — flip the flag and bail. The reload path below
1420
+ // is only for layouts whose entities were never streamed in
1421
+ // (typically non-active paper-space layouts loaded on first user
1422
+ // visit).
1423
+ if (existingLayout && existingLayout.entityCount > 0) {
1424
+ existingLayout.isLoaded = true;
1425
+ return;
1426
+ }
1427
+ // Ensure layout exists in scene. `addEmptyLayout` is idempotent, but
1428
+ // guarding the call avoids an unnecessary Map probe + log noise.
1429
+ if (!existingLayout) {
1430
+ this._scene.addEmptyLayout(layoutBtrId);
1431
+ }
956
1432
  // Collect all entities from this layout
957
1433
  var entities_1 = [];
958
1434
  var iterator = blockTableRecord.newIterator();
@@ -962,34 +1438,46 @@ var AcTrView2d = /** @class */ (function (_super) {
962
1438
  entities_1.push(entity);
963
1439
  }
964
1440
  }
965
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
1441
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
966
1442
  finally {
967
1443
  try {
968
1444
  if (iterator_1_1 && !iterator_1_1.done && (_a = iterator_1.return)) _a.call(iterator_1);
969
1445
  }
970
- finally { if (e_1) throw e_1.error; }
1446
+ finally { if (e_3) throw e_3.error; }
971
1447
  }
972
- // Ensure layout exists in scene
973
- this._scene.addEmptyLayout(layoutBtrId);
974
- if (entities_1.length > 0) {
975
- // Load entities asynchronously
976
- this._numOfEntitiesToProcess += entities_1.length;
977
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
978
- var layout;
979
- return __generator(this, function (_a) {
980
- switch (_a.label) {
981
- case 0: return [4 /*yield*/, this.batchConvert(entities_1)];
982
- case 1:
983
- _a.sent();
984
- layout = this._scene.layouts.get(layoutBtrId);
985
- if (layout) {
986
- layout.isLoaded = true;
987
- }
988
- return [2 /*return*/];
989
- }
990
- });
991
- }); });
1448
+ if (entities_1.length === 0) {
1449
+ // Empty layout (e.g. a freshly-created paper space tab). Mark as
1450
+ // loaded immediately so subsequent visits short-circuit.
1451
+ var layout = this._scene.layouts.get(layoutBtrId);
1452
+ if (layout) {
1453
+ layout.isLoaded = true;
1454
+ }
1455
+ return;
992
1456
  }
1457
+ // Load entities asynchronously
1458
+ this._loadingLayouts.add(layoutBtrId);
1459
+ this._numOfEntitiesToProcess += entities_1.length;
1460
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1461
+ var layout;
1462
+ return __generator(this, function (_a) {
1463
+ switch (_a.label) {
1464
+ case 0:
1465
+ _a.trys.push([0, , 2, 3]);
1466
+ return [4 /*yield*/, this.batchConvert(entities_1)];
1467
+ case 1:
1468
+ _a.sent();
1469
+ layout = this._scene.layouts.get(layoutBtrId);
1470
+ if (layout) {
1471
+ layout.isLoaded = true;
1472
+ }
1473
+ return [3 /*break*/, 3];
1474
+ case 2:
1475
+ this._loadingLayouts.delete(layoutBtrId);
1476
+ return [7 /*endfinally*/];
1477
+ case 3: return [2 /*return*/];
1478
+ }
1479
+ });
1480
+ }); });
993
1481
  }
994
1482
  catch (error) {
995
1483
  log.error('[AcTrView2d] Error loading layout entities:', error);
@@ -1011,6 +1499,48 @@ var AcTrView2d = /** @class */ (function (_super) {
1011
1499
  AcTrView2d.prototype.drawEntity = function (entity, delay) {
1012
1500
  return entity.worldDraw(this._renderer, delay);
1013
1501
  };
1502
+ /**
1503
+ * Walks the given block table record once and creates one
1504
+ * `AcTrViewportView` for every real `AcDbViewport` entity it finds
1505
+ * (skipping the default paper-space viewport that is filtered
1506
+ * everywhere else by `AcTrViewportView.isDefaultPaperSpaceViewport`).
1507
+ *
1508
+ * This is the recovery pass for paper-space layouts whose viewport
1509
+ * entities reached `batchConvert` before the `AcTrLayoutView` was
1510
+ * created — those entities were drawn and added to the scene, but
1511
+ * the viewport-view creation step silently no-oped (lookup returned
1512
+ * undefined). Without this recovery, `viewportsBoundingBox` stays
1513
+ * `undefined` on first user visit, the initial-zoom strategy
1514
+ * degrades to the bogus `limits` branch, and the layout renders as
1515
+ * a "grain in the corner" with empty viewport scissors. See the
1516
+ * call site in `loadLayoutEntitiesIfNeeded` for the full context.
1517
+ *
1518
+ * Cheap operation: only AcDbViewport entities are inspected; for a
1519
+ * typical sheet that's a handful of entities even on 5000-entity
1520
+ * paper layouts.
1521
+ */
1522
+ AcTrView2d.prototype.ensureViewportViews = function (blockTableRecord, layoutView) {
1523
+ var e_4, _a;
1524
+ var iterator = blockTableRecord.newIterator();
1525
+ try {
1526
+ for (var iterator_2 = __values(iterator), iterator_2_1 = iterator_2.next(); !iterator_2_1.done; iterator_2_1 = iterator_2.next()) {
1527
+ var entity = iterator_2_1.value;
1528
+ if (!(entity instanceof AcDbViewport))
1529
+ continue;
1530
+ if (AcTrViewportView.isDefaultPaperSpaceViewport(entity))
1531
+ continue;
1532
+ var viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this._renderer);
1533
+ layoutView.addViewport(viewportView);
1534
+ }
1535
+ }
1536
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
1537
+ finally {
1538
+ try {
1539
+ if (iterator_2_1 && !iterator_2_1.done && (_a = iterator_2.return)) _a.call(iterator_2);
1540
+ }
1541
+ finally { if (e_4) throw e_4.error; }
1542
+ }
1543
+ };
1014
1544
  /**
1015
1545
  * Converts the specified database entities to three entities
1016
1546
  * @param entities - The database entities
@@ -1030,6 +1560,24 @@ var AcTrView2d = /** @class */ (function (_super) {
1030
1560
  _a.label = 2;
1031
1561
  case 2:
1032
1562
  _a.trys.push([2, 6, 7, 8]);
1563
+ // Skip the default paper-space viewport (`*Paper_Space`) entirely:
1564
+ // it is an AutoCAD-internal viewport that exists in every paper
1565
+ // layout and must not be drawn (would render a giant rectangle in
1566
+ // the paper coordinate system), nor added to the spatial index
1567
+ // (would stretch the layout's bounding box and break
1568
+ // zoomToFitDrawing), nor turned into an AcTrViewportView (would
1569
+ // setScissor over most of the canvas and squeeze the real user
1570
+ // viewports into a corner). See
1571
+ // `AcTrViewportView.isDefaultPaperSpaceViewport` for the criterion
1572
+ // and the rationale (legacy `number === 1` is unreliable across
1573
+ // parsers).
1574
+ if (entity instanceof AcDbViewport &&
1575
+ AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
1576
+ return [3 /*break*/, 8];
1577
+ }
1578
+ if (!entity.visibility) {
1579
+ return [3 /*break*/, 8];
1580
+ }
1033
1581
  threeEntity = this.drawEntity(entity, true);
1034
1582
  if (!threeEntity)
1035
1583
  return [3 /*break*/, 8];
@@ -1059,10 +1607,12 @@ var AcTrView2d = /** @class */ (function (_super) {
1059
1607
  _a.label = 5;
1060
1608
  case 5:
1061
1609
  if (entity instanceof AcDbViewport) {
1062
- // In paper space layouts, there is always a system-defined "default" viewport that exists as
1063
- // the bottom-most item. This viewport doesn't show any entities and is mainly for internal
1064
- // AutoCAD purposes. The viewport id number of this system-defined "default" viewport is 1.
1065
- if (entity.number !== 1) {
1610
+ // Default paper-space viewport was already filtered out at the
1611
+ // top of the loop, so anything that reaches here is a real
1612
+ // user-created viewport. The redundant check below is kept as
1613
+ // a defensive guard in case a future refactor reorders the
1614
+ // early-skip — it costs ~nothing and prevents a regression.
1615
+ if (!AcTrViewportView.isDefaultPaperSpaceViewport(entity)) {
1066
1616
  layoutView = this._layoutViewManager.getAt(entity.ownerId);
1067
1617
  if (layoutView) {
1068
1618
  viewportView = new AcTrViewportView(layoutView, entity.toGiViewport(), this._renderer);
@@ -1092,12 +1642,15 @@ var AcTrView2d = /** @class */ (function (_super) {
1092
1642
  });
1093
1643
  };
1094
1644
  AcTrView2d.prototype.handleGroup = function (group) {
1095
- var e_2, _a;
1645
+ var e_5, _a;
1096
1646
  var _this = this;
1097
1647
  var children = group.children;
1098
1648
  var objectsGroupByLayer = new Map();
1099
1649
  children.forEach(function (child) {
1100
1650
  var _a;
1651
+ if (!child.visible) {
1652
+ return;
1653
+ }
1101
1654
  var layerName = child.userData.layerName;
1102
1655
  if (!objectsGroupByLayer.has(layerName)) {
1103
1656
  objectsGroupByLayer.set(layerName, []);
@@ -1120,12 +1673,12 @@ var AcTrView2d = /** @class */ (function (_super) {
1120
1673
  child.parent = null;
1121
1674
  }
1122
1675
  }
1123
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
1676
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1124
1677
  finally {
1125
1678
  try {
1126
1679
  if (children_1_1 && !children_1_1.done && (_a = children_1.return)) _a.call(children_1);
1127
1680
  }
1128
- finally { if (e_2) throw e_2.error; }
1681
+ finally { if (e_5) throw e_5.error; }
1129
1682
  }
1130
1683
  var styleManager = group.styleManager;
1131
1684
  var groupObjectId = group.objectId;
@@ -1180,7 +1733,7 @@ var AcTrView2d = /** @class */ (function (_super) {
1180
1733
  * @param effectiveLayerName - Final layer name used by rendering and style updates.
1181
1734
  */
1182
1735
  AcTrView2d.prototype.remapInheritedLayerObjects = function (objects, sourceLayerName, effectiveLayerName) {
1183
- var e_3, _a;
1736
+ var e_6, _a;
1184
1737
  var _this = this;
1185
1738
  if (sourceLayerName === effectiveLayerName)
1186
1739
  return;
@@ -1209,12 +1762,12 @@ var AcTrView2d = /** @class */ (function (_super) {
1209
1762
  });
1210
1763
  }
1211
1764
  }
1212
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
1765
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
1213
1766
  finally {
1214
1767
  try {
1215
1768
  if (objects_1_1 && !objects_1_1.done && (_a = objects_1.return)) _a.call(objects_1);
1216
1769
  }
1217
- finally { if (e_3) throw e_3.error; }
1770
+ finally { if (e_6) throw e_6.error; }
1218
1771
  }
1219
1772
  };
1220
1773
  /**