@pooder/kit 5.4.0 → 6.0.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 (69) hide show
  1. package/.test-dist/src/coordinate.js +74 -0
  2. package/.test-dist/src/extensions/background.js +547 -0
  3. package/.test-dist/src/extensions/bridgeSelection.js +20 -0
  4. package/.test-dist/src/extensions/constraints.js +237 -0
  5. package/.test-dist/src/extensions/dieline.js +931 -0
  6. package/.test-dist/src/extensions/dielineShape.js +66 -0
  7. package/.test-dist/src/extensions/edgeScale.js +12 -0
  8. package/.test-dist/src/extensions/feature.js +910 -0
  9. package/.test-dist/src/extensions/featureComplete.js +32 -0
  10. package/.test-dist/src/extensions/film.js +226 -0
  11. package/.test-dist/src/extensions/geometry.js +609 -0
  12. package/.test-dist/src/extensions/image.js +1613 -0
  13. package/.test-dist/src/extensions/index.js +28 -0
  14. package/.test-dist/src/extensions/maskOps.js +334 -0
  15. package/.test-dist/src/extensions/mirror.js +104 -0
  16. package/.test-dist/src/extensions/ruler.js +442 -0
  17. package/.test-dist/src/extensions/sceneLayout.js +96 -0
  18. package/.test-dist/src/extensions/sceneLayoutModel.js +202 -0
  19. package/.test-dist/src/extensions/sceneVisibility.js +55 -0
  20. package/.test-dist/src/extensions/size.js +331 -0
  21. package/.test-dist/src/extensions/tracer.js +709 -0
  22. package/.test-dist/src/extensions/white-ink.js +1200 -0
  23. package/.test-dist/src/extensions/wrappedOffsets.js +33 -0
  24. package/.test-dist/src/index.js +18 -0
  25. package/.test-dist/src/services/CanvasService.js +1011 -0
  26. package/.test-dist/src/services/ViewportSystem.js +76 -0
  27. package/.test-dist/src/services/index.js +25 -0
  28. package/.test-dist/src/services/renderSpec.js +2 -0
  29. package/.test-dist/src/services/visibility.js +54 -0
  30. package/.test-dist/src/units.js +30 -0
  31. package/.test-dist/tests/run.js +148 -0
  32. package/CHANGELOG.md +6 -0
  33. package/dist/index.d.mts +150 -62
  34. package/dist/index.d.ts +150 -62
  35. package/dist/index.js +2219 -1714
  36. package/dist/index.mjs +2226 -1718
  37. package/package.json +1 -1
  38. package/src/coordinate.ts +106 -106
  39. package/src/extensions/background.ts +716 -323
  40. package/src/extensions/bridgeSelection.ts +17 -17
  41. package/src/extensions/constraints.ts +322 -322
  42. package/src/extensions/dieline.ts +1169 -1149
  43. package/src/extensions/dielineShape.ts +109 -109
  44. package/src/extensions/edgeScale.ts +19 -19
  45. package/src/extensions/feature.ts +1140 -1137
  46. package/src/extensions/featureComplete.ts +46 -46
  47. package/src/extensions/film.ts +270 -266
  48. package/src/extensions/geometry.ts +851 -885
  49. package/src/extensions/image.ts +2007 -2054
  50. package/src/extensions/index.ts +10 -11
  51. package/src/extensions/maskOps.ts +283 -283
  52. package/src/extensions/mirror.ts +128 -128
  53. package/src/extensions/ruler.ts +664 -654
  54. package/src/extensions/sceneLayout.ts +140 -140
  55. package/src/extensions/sceneLayoutModel.ts +364 -364
  56. package/src/extensions/size.ts +389 -389
  57. package/src/extensions/tracer.ts +1019 -1019
  58. package/src/extensions/white-ink.ts +1508 -1575
  59. package/src/extensions/wrappedOffsets.ts +33 -33
  60. package/src/index.ts +2 -2
  61. package/src/services/CanvasService.ts +1286 -832
  62. package/src/services/ViewportSystem.ts +95 -95
  63. package/src/services/index.ts +4 -3
  64. package/src/services/renderSpec.ts +83 -53
  65. package/src/services/visibility.ts +78 -0
  66. package/src/units.ts +27 -27
  67. package/tests/run.ts +253 -118
  68. package/tsconfig.test.json +15 -15
  69. package/src/extensions/sceneVisibility.ts +0 -64
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RulerTool = void 0;
4
+ const core_1 = require("@pooder/core");
5
+ const sceneLayoutModel_1 = require("./sceneLayoutModel");
6
+ const RULER_LAYER_ID = "ruler-overlay";
7
+ const EXTENSION_LINE_LENGTH = 5;
8
+ const MIN_ARROW_SIZE = 4;
9
+ const THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
10
+ const DEFAULT_THICKNESS = 20;
11
+ const DEFAULT_GAP = 45;
12
+ const DEFAULT_FONT_SIZE = 10;
13
+ const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
14
+ const DEFAULT_TEXT_COLOR = "#333333";
15
+ const DEFAULT_LINE_COLOR = "#999999";
16
+ const RULER_THICKNESS_MIN = 10;
17
+ const RULER_THICKNESS_MAX = 100;
18
+ const RULER_GAP_MIN = 0;
19
+ const RULER_GAP_MAX = 100;
20
+ const RULER_FONT_SIZE_MIN = 8;
21
+ const RULER_FONT_SIZE_MAX = 24;
22
+ class RulerTool {
23
+ constructor(options) {
24
+ this.id = "pooder.kit.ruler";
25
+ this.metadata = {
26
+ name: "RulerTool",
27
+ };
28
+ this.thickness = DEFAULT_THICKNESS;
29
+ this.gap = DEFAULT_GAP;
30
+ this.backgroundColor = DEFAULT_BACKGROUND_COLOR;
31
+ this.textColor = DEFAULT_TEXT_COLOR;
32
+ this.lineColor = DEFAULT_LINE_COLOR;
33
+ this.fontSize = DEFAULT_FONT_SIZE;
34
+ this.renderSeq = 0;
35
+ this.numericProps = new Set(["thickness", "gap", "fontSize"]);
36
+ this.specs = [];
37
+ this.onCanvasResized = () => {
38
+ this.updateRuler();
39
+ };
40
+ if (options) {
41
+ Object.assign(this, options);
42
+ }
43
+ }
44
+ activate(context) {
45
+ this.context = context;
46
+ this.canvasService = context.services.get("CanvasService");
47
+ if (!this.canvasService) {
48
+ console.warn("[RulerTool] CanvasService not found.");
49
+ return;
50
+ }
51
+ this.renderProducerDisposable?.dispose();
52
+ this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
53
+ passes: [
54
+ {
55
+ id: RULER_LAYER_ID,
56
+ stack: 950,
57
+ order: 0,
58
+ replace: true,
59
+ visibility: {
60
+ op: "not",
61
+ expr: {
62
+ op: "activeToolIn",
63
+ ids: ["pooder.kit.white-ink"],
64
+ },
65
+ },
66
+ objects: this.specs,
67
+ },
68
+ ],
69
+ }), { priority: 400 });
70
+ const configService = context.services.get("ConfigurationService");
71
+ if (configService) {
72
+ this.syncConfig(configService);
73
+ configService.onAnyChange((e) => {
74
+ let shouldUpdate = false;
75
+ if (e.key.startsWith("ruler.")) {
76
+ const prop = e.key.split(".")[1];
77
+ if (prop && prop in this) {
78
+ if (this.numericProps.has(prop)) {
79
+ this[prop] = this.toFiniteNumber(e.value, this[prop]);
80
+ }
81
+ else {
82
+ this[prop] = e.value;
83
+ }
84
+ shouldUpdate = true;
85
+ this.log("config:update", {
86
+ key: e.key,
87
+ raw: e.value,
88
+ normalized: this[prop],
89
+ });
90
+ }
91
+ }
92
+ else if (e.key.startsWith("size.")) {
93
+ shouldUpdate = true;
94
+ this.log("size:update", { key: e.key, value: e.value });
95
+ }
96
+ if (shouldUpdate) {
97
+ this.updateRuler();
98
+ }
99
+ });
100
+ }
101
+ context.eventBus.on("canvas:resized", this.onCanvasResized);
102
+ this.updateRuler();
103
+ }
104
+ deactivate(context) {
105
+ context.eventBus.off("canvas:resized", this.onCanvasResized);
106
+ this.specs = [];
107
+ this.renderProducerDisposable?.dispose();
108
+ this.renderProducerDisposable = undefined;
109
+ if (this.canvasService) {
110
+ void this.canvasService.flushRenderFromProducers();
111
+ }
112
+ this.canvasService = undefined;
113
+ this.context = undefined;
114
+ this.renderSeq = 0;
115
+ }
116
+ contribute() {
117
+ return {
118
+ [core_1.ContributionPointIds.CONFIGURATIONS]: [
119
+ {
120
+ id: "ruler.thickness",
121
+ type: "number",
122
+ label: "Thickness",
123
+ min: RULER_THICKNESS_MIN,
124
+ max: RULER_THICKNESS_MAX,
125
+ default: DEFAULT_THICKNESS,
126
+ },
127
+ {
128
+ id: "ruler.gap",
129
+ type: "number",
130
+ label: "Gap",
131
+ min: RULER_GAP_MIN,
132
+ max: RULER_GAP_MAX,
133
+ default: DEFAULT_GAP,
134
+ },
135
+ {
136
+ id: "ruler.backgroundColor",
137
+ type: "color",
138
+ label: "Background Color",
139
+ default: DEFAULT_BACKGROUND_COLOR,
140
+ },
141
+ {
142
+ id: "ruler.textColor",
143
+ type: "color",
144
+ label: "Text Color",
145
+ default: DEFAULT_TEXT_COLOR,
146
+ },
147
+ {
148
+ id: "ruler.lineColor",
149
+ type: "color",
150
+ label: "Line Color",
151
+ default: DEFAULT_LINE_COLOR,
152
+ },
153
+ {
154
+ id: "ruler.fontSize",
155
+ type: "number",
156
+ label: "Font Size",
157
+ min: RULER_FONT_SIZE_MIN,
158
+ max: RULER_FONT_SIZE_MAX,
159
+ default: DEFAULT_FONT_SIZE,
160
+ },
161
+ ],
162
+ [core_1.ContributionPointIds.COMMANDS]: [
163
+ {
164
+ command: "setTheme",
165
+ title: "Set Ruler Theme",
166
+ handler: (theme) => {
167
+ const oldState = {
168
+ backgroundColor: this.backgroundColor,
169
+ textColor: this.textColor,
170
+ lineColor: this.lineColor,
171
+ fontSize: this.fontSize,
172
+ thickness: this.thickness,
173
+ gap: this.gap,
174
+ };
175
+ const newState = { ...oldState, ...theme };
176
+ if (JSON.stringify(newState) === JSON.stringify(oldState)) {
177
+ return true;
178
+ }
179
+ Object.assign(this, newState);
180
+ this.thickness = this.toFiniteNumber(this.thickness, DEFAULT_THICKNESS);
181
+ this.gap = this.toFiniteNumber(this.gap, DEFAULT_GAP);
182
+ this.fontSize = this.toFiniteNumber(this.fontSize, DEFAULT_FONT_SIZE);
183
+ this.updateRuler();
184
+ return true;
185
+ },
186
+ },
187
+ ],
188
+ };
189
+ }
190
+ log(step, payload) {
191
+ if (payload) {
192
+ console.debug(`[RulerTool] ${step}`, payload);
193
+ return;
194
+ }
195
+ console.debug(`[RulerTool] ${step}`);
196
+ }
197
+ syncConfig(configService) {
198
+ this.thickness = this.toFiniteNumber(configService.get("ruler.thickness", this.thickness), DEFAULT_THICKNESS);
199
+ this.gap = Math.max(0, this.toFiniteNumber(configService.get("ruler.gap", this.gap), DEFAULT_GAP));
200
+ this.backgroundColor = configService.get("ruler.backgroundColor", this.backgroundColor);
201
+ this.textColor = configService.get("ruler.textColor", this.textColor);
202
+ this.lineColor = configService.get("ruler.lineColor", this.lineColor);
203
+ this.fontSize = this.toFiniteNumber(configService.get("ruler.fontSize", this.fontSize), DEFAULT_FONT_SIZE);
204
+ this.log("config:loaded", {
205
+ thickness: this.thickness,
206
+ gap: this.gap,
207
+ fontSize: this.fontSize,
208
+ backgroundColor: this.backgroundColor,
209
+ textColor: this.textColor,
210
+ lineColor: this.lineColor,
211
+ });
212
+ }
213
+ toFiniteNumber(value, fallback) {
214
+ const numeric = Number(value);
215
+ return Number.isFinite(numeric) ? numeric : fallback;
216
+ }
217
+ toSceneDisplayLength(value) {
218
+ if (!this.canvasService)
219
+ return value;
220
+ return this.canvasService.toSceneLength(value);
221
+ }
222
+ formatLengthMm(valueMm, unit) {
223
+ const converted = (0, sceneLayoutModel_1.fromMm)(valueMm, unit);
224
+ const fractionDigits = unit === "in" ? 3 : 2;
225
+ return Number(converted.toFixed(fractionDigits)).toString();
226
+ }
227
+ buildLinePath(start, end) {
228
+ const dx = end.x - start.x;
229
+ const dy = end.y - start.y;
230
+ return `M 0 0 L ${dx} ${dy}`;
231
+ }
232
+ buildStartArrowPath(size) {
233
+ return `M 0 0 L ${size} ${-size / 2} L ${size} ${size / 2} Z`;
234
+ }
235
+ buildEndArrowPath(size) {
236
+ return `M 0 0 L ${-size} ${-size / 2} L ${-size} ${size / 2} Z`;
237
+ }
238
+ createPathSpec(id, pathData, position, options) {
239
+ return {
240
+ id,
241
+ type: "path",
242
+ data: {
243
+ id,
244
+ type: "ruler",
245
+ },
246
+ props: {
247
+ pathData,
248
+ left: position.x,
249
+ top: position.y,
250
+ originX: options.originX ?? "left",
251
+ originY: options.originY ?? "top",
252
+ angle: options.angle ?? 0,
253
+ stroke: options.stroke ?? null,
254
+ fill: options.fill ?? null,
255
+ strokeWidth: options.strokeWidth ?? 1,
256
+ strokeLineCap: options.strokeLineCap ?? "butt",
257
+ selectable: false,
258
+ evented: false,
259
+ excludeFromExport: true,
260
+ },
261
+ };
262
+ }
263
+ createTextSpec(id, text, position, angle = 0) {
264
+ return {
265
+ id,
266
+ type: "text",
267
+ data: {
268
+ id,
269
+ type: "ruler",
270
+ },
271
+ props: {
272
+ text,
273
+ left: position.x,
274
+ top: position.y,
275
+ angle,
276
+ fontSize: this.toSceneDisplayLength(this.fontSize),
277
+ fill: this.textColor,
278
+ fontFamily: "Arial",
279
+ originX: "center",
280
+ originY: "center",
281
+ backgroundColor: this.backgroundColor,
282
+ selectable: false,
283
+ evented: false,
284
+ excludeFromExport: true,
285
+ },
286
+ };
287
+ }
288
+ buildRulerSpecs(input) {
289
+ const { left, top, right, bottom, widthLabel, heightLabel } = input;
290
+ const gap = Math.max(0, this.toSceneDisplayLength(this.toFiniteNumber(this.gap, DEFAULT_GAP)));
291
+ const topY = top - gap;
292
+ const leftX = left - gap;
293
+ const arrowSize = Math.max(this.toSceneDisplayLength(MIN_ARROW_SIZE), this.toSceneDisplayLength(this.thickness * 0.3));
294
+ const strokeWidth = Math.max(this.toSceneDisplayLength(1), this.toSceneDisplayLength(this.thickness / THICKNESS_TO_STROKE_WIDTH_RATIO));
295
+ const extensionLength = this.toSceneDisplayLength(EXTENSION_LINE_LENGTH);
296
+ const topLineAngleDeg = 0;
297
+ const leftLineAngleDeg = 90;
298
+ // Keep dimension line inside the arrow heads so it doesn't visually overflow.
299
+ const topMidX = left + (right - left) / 2;
300
+ const leftMidY = top + (bottom - top) / 2;
301
+ const topLineStartX = Math.min(left + arrowSize, topMidX);
302
+ const topLineEndX = Math.max(right - arrowSize, topMidX);
303
+ const leftLineStartY = Math.min(top + arrowSize, leftMidY);
304
+ const leftLineEndY = Math.max(bottom - arrowSize, leftMidY);
305
+ const specs = [];
306
+ specs.push(this.createPathSpec("ruler.top.line", this.buildLinePath({ x: topLineStartX, y: topY }, { x: topLineEndX, y: topY }), { x: topLineStartX, y: topY }, {
307
+ stroke: this.lineColor,
308
+ strokeWidth,
309
+ strokeLineCap: "butt",
310
+ }), this.createPathSpec("ruler.top.arrow.start", this.buildStartArrowPath(arrowSize), { x: left, y: topY }, {
311
+ fill: this.lineColor,
312
+ stroke: this.lineColor,
313
+ strokeWidth: this.toSceneDisplayLength(1),
314
+ originX: "left",
315
+ originY: "center",
316
+ angle: topLineAngleDeg,
317
+ }), this.createPathSpec("ruler.top.arrow.end", this.buildEndArrowPath(arrowSize), { x: right, y: topY }, {
318
+ fill: this.lineColor,
319
+ stroke: this.lineColor,
320
+ strokeWidth: this.toSceneDisplayLength(1),
321
+ originX: "right",
322
+ originY: "center",
323
+ angle: topLineAngleDeg,
324
+ }), this.createPathSpec("ruler.top.ext.start", this.buildLinePath({ x: left, y: topY - extensionLength }, { x: left, y: topY + extensionLength }), { x: left, y: topY - extensionLength }, {
325
+ stroke: this.lineColor,
326
+ strokeWidth: this.toSceneDisplayLength(1),
327
+ }), this.createPathSpec("ruler.top.ext.end", this.buildLinePath({ x: right, y: topY - extensionLength }, { x: right, y: topY + extensionLength }), { x: right, y: topY - extensionLength }, {
328
+ stroke: this.lineColor,
329
+ strokeWidth: this.toSceneDisplayLength(1),
330
+ }), this.createTextSpec("ruler.top.label", widthLabel, {
331
+ x: left + (right - left) / 2,
332
+ y: topY,
333
+ }));
334
+ specs.push(this.createPathSpec("ruler.left.line", this.buildLinePath({ x: leftX, y: leftLineStartY }, { x: leftX, y: leftLineEndY }), { x: leftX, y: leftLineStartY }, {
335
+ stroke: this.lineColor,
336
+ strokeWidth,
337
+ strokeLineCap: "butt",
338
+ }), this.createPathSpec("ruler.left.arrow.start", this.buildStartArrowPath(arrowSize), { x: leftX, y: top }, {
339
+ fill: this.lineColor,
340
+ stroke: this.lineColor,
341
+ strokeWidth: this.toSceneDisplayLength(1),
342
+ originX: "left",
343
+ originY: "center",
344
+ angle: leftLineAngleDeg,
345
+ }), this.createPathSpec("ruler.left.arrow.end", this.buildEndArrowPath(arrowSize), { x: leftX, y: bottom }, {
346
+ fill: this.lineColor,
347
+ stroke: this.lineColor,
348
+ strokeWidth: this.toSceneDisplayLength(1),
349
+ originX: "right",
350
+ originY: "center",
351
+ angle: leftLineAngleDeg,
352
+ }), this.createPathSpec("ruler.left.ext.start", this.buildLinePath({ x: leftX - extensionLength, y: top }, { x: leftX + extensionLength, y: top }), { x: leftX - extensionLength, y: top }, {
353
+ stroke: this.lineColor,
354
+ strokeWidth: this.toSceneDisplayLength(1),
355
+ }), this.createPathSpec("ruler.left.ext.end", this.buildLinePath({ x: leftX - extensionLength, y: bottom }, { x: leftX + extensionLength, y: bottom }), { x: leftX - extensionLength, y: bottom }, {
356
+ stroke: this.lineColor,
357
+ strokeWidth: this.toSceneDisplayLength(1),
358
+ }), this.createTextSpec("ruler.left.label", heightLabel, {
359
+ x: leftX,
360
+ y: top + (bottom - top) / 2,
361
+ }, -90));
362
+ return specs;
363
+ }
364
+ updateRuler() {
365
+ void this.updateRulerAsync();
366
+ }
367
+ async updateRulerAsync() {
368
+ if (!this.canvasService)
369
+ return;
370
+ const configService = this.context?.services.get("ConfigurationService");
371
+ if (!configService)
372
+ return;
373
+ const seq = ++this.renderSeq;
374
+ const sizeState = (0, sceneLayoutModel_1.readSizeState)(configService);
375
+ const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, sizeState);
376
+ this.log("render:start", {
377
+ seq,
378
+ unit: sizeState.unit,
379
+ gap: this.gap,
380
+ thickness: this.thickness,
381
+ fontSize: this.fontSize,
382
+ hasLayout: !!layout,
383
+ scale: layout?.scale ?? null,
384
+ });
385
+ if (!layout || layout.scale <= 0) {
386
+ if (seq !== this.renderSeq)
387
+ return;
388
+ this.log("render:skip", { seq, reason: "invalid-layout" });
389
+ this.specs = [];
390
+ await this.canvasService.flushRenderFromProducers();
391
+ return;
392
+ }
393
+ const geometry = (0, sceneLayoutModel_1.buildSceneGeometry)(configService, layout);
394
+ if (geometry.unit !== "px") {
395
+ console.warn("[RulerTool] Unexpected geometry unit.", geometry.unit);
396
+ }
397
+ const centerScene = this.canvasService.toScenePoint({
398
+ x: geometry.x,
399
+ y: geometry.y,
400
+ });
401
+ const widthScene = this.canvasService.toSceneLength(geometry.width);
402
+ const heightScene = this.canvasService.toSceneLength(geometry.height);
403
+ const rulerLeft = centerScene.x - widthScene / 2;
404
+ const rulerTop = centerScene.y - heightScene / 2;
405
+ const rulerRight = rulerLeft + widthScene;
406
+ const rulerBottom = rulerTop + heightScene;
407
+ const widthMm = widthScene;
408
+ const heightMm = heightScene;
409
+ const unit = sizeState.unit;
410
+ const widthLabel = `${this.formatLengthMm(widthMm, unit)} ${unit}`;
411
+ const heightLabel = `${this.formatLengthMm(heightMm, unit)} ${unit}`;
412
+ const specs = this.buildRulerSpecs({
413
+ left: rulerLeft,
414
+ top: rulerTop,
415
+ right: rulerRight,
416
+ bottom: rulerBottom,
417
+ widthLabel,
418
+ heightLabel,
419
+ });
420
+ this.log("render:geometry", {
421
+ seq,
422
+ left: rulerLeft,
423
+ top: rulerTop,
424
+ right: rulerRight,
425
+ bottom: rulerBottom,
426
+ widthScene,
427
+ heightScene,
428
+ widthMm,
429
+ heightMm,
430
+ specCount: specs.length,
431
+ });
432
+ if (seq !== this.renderSeq)
433
+ return;
434
+ this.specs = specs;
435
+ await this.canvasService.flushRenderFromProducers();
436
+ if (seq !== this.renderSeq)
437
+ return;
438
+ this.canvasService.requestRenderAll();
439
+ this.log("render:done", { seq });
440
+ }
441
+ }
442
+ exports.RulerTool = RulerTool;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SceneLayoutService = void 0;
4
+ const core_1 = require("@pooder/core");
5
+ const sceneLayoutModel_1 = require("./sceneLayoutModel");
6
+ const CONFIG_WATCH_PREFIXES = ["size.", "dieline."];
7
+ const CANVAS_SERVICE_ID = "CanvasService";
8
+ const GET_SCENE_LAYOUT_COMMAND = "getSceneLayout";
9
+ const GET_SCENE_GEOMETRY_COMMAND = "getSceneGeometry";
10
+ class SceneLayoutService {
11
+ constructor() {
12
+ this.lastLayout = null;
13
+ this.lastGeometry = null;
14
+ this.commandDisposables = [];
15
+ this.onCanvasResized = () => {
16
+ this.refresh();
17
+ };
18
+ this.onConfigChanged = (e) => {
19
+ if (CONFIG_WATCH_PREFIXES.some((prefix) => e.key.startsWith(prefix))) {
20
+ this.refresh();
21
+ }
22
+ };
23
+ }
24
+ init(context) {
25
+ if (this.context) {
26
+ this.dispose(this.context);
27
+ }
28
+ const canvasService = context.get(CANVAS_SERVICE_ID);
29
+ const configService = context.get(core_1.CONFIGURATION_SERVICE);
30
+ const commandService = context.get(core_1.COMMAND_SERVICE);
31
+ if (!canvasService || !configService || !commandService) {
32
+ throw new Error("[SceneLayoutService] CanvasService, ConfigurationService and CommandService are required.");
33
+ }
34
+ this.context = context;
35
+ this.canvasService = canvasService;
36
+ this.configService = configService;
37
+ this.commandDisposables.push(commandService.registerCommand(GET_SCENE_LAYOUT_COMMAND, () => this.getLayout()), commandService.registerCommand(GET_SCENE_GEOMETRY_COMMAND, () => this.getGeometry()));
38
+ this.onConfigChange = configService.onAnyChange(this.onConfigChanged);
39
+ context.eventBus.on("canvas:resized", this.onCanvasResized);
40
+ this.refresh();
41
+ }
42
+ dispose(context) {
43
+ const activeContext = this.context ?? context;
44
+ activeContext.eventBus.off("canvas:resized", this.onCanvasResized);
45
+ this.onConfigChange?.dispose();
46
+ this.onConfigChange = undefined;
47
+ this.commandDisposables.forEach((item) => item.dispose());
48
+ this.commandDisposables = [];
49
+ this.context = undefined;
50
+ this.canvasService = undefined;
51
+ this.configService = undefined;
52
+ this.lastLayout = null;
53
+ this.lastGeometry = null;
54
+ }
55
+ refresh() {
56
+ const layout = this.getLayout(true);
57
+ if (!layout) {
58
+ this.lastGeometry = null;
59
+ return;
60
+ }
61
+ this.context?.eventBus.emit("scene:layout:change", layout);
62
+ const geometry = this.getGeometry(true);
63
+ if (geometry) {
64
+ this.context?.eventBus.emit("scene:geometry:change", geometry);
65
+ }
66
+ }
67
+ getLayout(forceRefresh = false) {
68
+ if (!this.canvasService || !this.configService)
69
+ return null;
70
+ if (!forceRefresh && this.lastLayout)
71
+ return this.lastLayout;
72
+ const state = (0, sceneLayoutModel_1.readSizeState)(this.configService);
73
+ const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, state);
74
+ if (!layout) {
75
+ this.lastLayout = null;
76
+ return null;
77
+ }
78
+ this.lastLayout = layout;
79
+ return layout;
80
+ }
81
+ getGeometry(forceRefresh = false) {
82
+ if (!this.configService)
83
+ return null;
84
+ const layout = this.getLayout(forceRefresh);
85
+ if (!layout) {
86
+ this.lastGeometry = null;
87
+ return null;
88
+ }
89
+ if (!forceRefresh && this.lastGeometry)
90
+ return this.lastGeometry;
91
+ const geometry = (0, sceneLayoutModel_1.buildSceneGeometry)(this.configService, layout);
92
+ this.lastGeometry = geometry;
93
+ return geometry;
94
+ }
95
+ }
96
+ exports.SceneLayoutService = SceneLayoutService;