@pooder/kit 4.1.0 → 4.3.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.
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +62 -10
- package/dist/index.d.ts +62 -10
- package/dist/index.js +756 -348
- package/dist/index.mjs +753 -347
- package/package.json +3 -2
- package/src/CanvasService.ts +96 -89
- package/src/ViewportSystem.ts +92 -0
- package/src/background.ts +230 -230
- package/src/constraints.ts +191 -27
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +955 -871
- package/src/feature.ts +282 -195
- package/src/featureComplete.ts +46 -0
- package/src/film.ts +194 -194
- package/src/geometry.ts +161 -30
- package/src/image.ts +512 -512
- package/src/index.ts +10 -9
- package/src/mirror.ts +128 -128
- package/src/ruler.ts +508 -500
- package/src/tracer.ts +570 -570
- package/src/units.ts +27 -0
- package/src/white-ink.ts +373 -373
- package/tsconfig.test.json +15 -0
package/src/feature.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
ContributionPointIds,
|
|
5
5
|
CommandContribution,
|
|
6
|
-
ConfigurationContribution,
|
|
7
6
|
ConfigurationService,
|
|
8
7
|
} from "@pooder/core";
|
|
9
8
|
import { Circle, Group, Point, Rect } from "fabric";
|
|
@@ -14,8 +13,11 @@ import {
|
|
|
14
13
|
DielineFeature,
|
|
15
14
|
resolveFeaturePosition,
|
|
16
15
|
} from "./geometry";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { ConstraintRegistry, ConstraintFeature } from "./constraints";
|
|
17
|
+
import {
|
|
18
|
+
completeFeaturesStrict,
|
|
19
|
+
} from "./featureComplete";
|
|
20
|
+
import { parseLengthToMm } from "./units";
|
|
19
21
|
|
|
20
22
|
export class FeatureTool implements Extension {
|
|
21
23
|
id = "pooder.kit.feature";
|
|
@@ -24,7 +26,7 @@ export class FeatureTool implements Extension {
|
|
|
24
26
|
name: "FeatureTool",
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
private
|
|
29
|
+
private workingFeatures: ConstraintFeature[] = [];
|
|
28
30
|
private canvasService?: CanvasService;
|
|
29
31
|
private context?: ExtensionContext;
|
|
30
32
|
private isUpdatingConfig = false;
|
|
@@ -39,7 +41,7 @@ export class FeatureTool implements Extension {
|
|
|
39
41
|
|
|
40
42
|
constructor(
|
|
41
43
|
options?: Partial<{
|
|
42
|
-
features:
|
|
44
|
+
features: ConstraintFeature[];
|
|
43
45
|
}>,
|
|
44
46
|
) {
|
|
45
47
|
if (options) {
|
|
@@ -60,14 +62,18 @@ export class FeatureTool implements Extension {
|
|
|
60
62
|
"ConfigurationService",
|
|
61
63
|
);
|
|
62
64
|
if (configService) {
|
|
63
|
-
|
|
65
|
+
const features = (configService.get("dieline.features", []) ||
|
|
66
|
+
[]) as ConstraintFeature[];
|
|
67
|
+
this.workingFeatures = this.cloneFeatures(features);
|
|
64
68
|
|
|
65
69
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
66
70
|
if (this.isUpdatingConfig) return;
|
|
67
71
|
|
|
68
72
|
if (e.key === "dieline.features") {
|
|
69
|
-
|
|
73
|
+
const next = (e.value || []) as ConstraintFeature[];
|
|
74
|
+
this.workingFeatures = this.cloneFeatures(next);
|
|
70
75
|
this.redraw();
|
|
76
|
+
this.emitWorkingChange();
|
|
71
77
|
}
|
|
72
78
|
});
|
|
73
79
|
}
|
|
@@ -138,104 +144,240 @@ export class FeatureTool implements Extension {
|
|
|
138
144
|
command: "clearFeatures",
|
|
139
145
|
title: "Clear Features",
|
|
140
146
|
handler: () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
if (configService) {
|
|
146
|
-
configService.update("dieline.features", []);
|
|
147
|
-
}
|
|
147
|
+
this.setWorkingFeatures([]);
|
|
148
|
+
this.redraw();
|
|
149
|
+
this.emitWorkingChange();
|
|
148
150
|
return true;
|
|
149
151
|
},
|
|
150
152
|
},
|
|
153
|
+
{
|
|
154
|
+
command: "getWorkingFeatures",
|
|
155
|
+
title: "Get Working Features",
|
|
156
|
+
handler: () => {
|
|
157
|
+
return this.cloneFeatures(this.workingFeatures);
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
command: "setWorkingFeatures",
|
|
162
|
+
title: "Set Working Features",
|
|
163
|
+
handler: async (features: ConstraintFeature[]) => {
|
|
164
|
+
await this.refreshGeometry();
|
|
165
|
+
this.setWorkingFeatures(this.cloneFeatures(features || []));
|
|
166
|
+
this.redraw();
|
|
167
|
+
this.emitWorkingChange();
|
|
168
|
+
return { ok: true };
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
command: "updateWorkingGroupPosition",
|
|
173
|
+
title: "Update Working Group Position",
|
|
174
|
+
handler: (groupId: string, x: number, y: number) => {
|
|
175
|
+
return this.updateWorkingGroupPosition(groupId, x, y);
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
command: "completeFeatures",
|
|
180
|
+
title: "Complete Features",
|
|
181
|
+
handler: () => {
|
|
182
|
+
return this.completeFeatures();
|
|
183
|
+
},
|
|
184
|
+
},
|
|
151
185
|
] as CommandContribution[],
|
|
152
186
|
};
|
|
153
187
|
}
|
|
154
188
|
|
|
155
|
-
private
|
|
156
|
-
|
|
189
|
+
private cloneFeatures(features: ConstraintFeature[]): ConstraintFeature[] {
|
|
190
|
+
return JSON.parse(JSON.stringify(features || [])) as ConstraintFeature[];
|
|
191
|
+
}
|
|
157
192
|
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
private emitWorkingChange() {
|
|
194
|
+
this.context?.eventBus.emit("feature:working:change", {
|
|
195
|
+
features: this.cloneFeatures(this.workingFeatures),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async refreshGeometry() {
|
|
200
|
+
if (!this.context) return;
|
|
201
|
+
const commandService = this.context.services.get<any>("CommandService");
|
|
202
|
+
if (!commandService) return;
|
|
203
|
+
try {
|
|
204
|
+
const g = await Promise.resolve(commandService.executeCommand("getGeometry"));
|
|
205
|
+
if (g) this.currentGeometry = g as DielineGeometry;
|
|
206
|
+
} catch (e) {}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private setWorkingFeatures(next: ConstraintFeature[]) {
|
|
210
|
+
this.workingFeatures = next;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private updateWorkingGroupPosition(groupId: string, x: number, y: number) {
|
|
214
|
+
if (!groupId) return { ok: false };
|
|
215
|
+
|
|
216
|
+
const configService =
|
|
217
|
+
this.context?.services.get<ConfigurationService>("ConfigurationService");
|
|
218
|
+
if (!configService) return { ok: false };
|
|
219
|
+
|
|
220
|
+
const dielineWidth = parseLengthToMm(
|
|
221
|
+
configService.get("dieline.width") ?? 500,
|
|
222
|
+
"mm",
|
|
223
|
+
);
|
|
224
|
+
const dielineHeight = parseLengthToMm(
|
|
225
|
+
configService.get("dieline.height") ?? 500,
|
|
226
|
+
"mm",
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
let changed = false;
|
|
230
|
+
const next = this.workingFeatures.map((f) => {
|
|
231
|
+
if (f.groupId !== groupId) return f;
|
|
232
|
+
let nx = x;
|
|
233
|
+
let ny = y;
|
|
234
|
+
if (f.constraints && dielineWidth > 0 && dielineHeight > 0) {
|
|
235
|
+
const constrained = ConstraintRegistry.apply(nx, ny, f, {
|
|
236
|
+
dielineWidth,
|
|
237
|
+
dielineHeight,
|
|
238
|
+
});
|
|
239
|
+
nx = constrained.x;
|
|
240
|
+
ny = constrained.y;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (f.x !== nx || f.y !== ny) {
|
|
244
|
+
changed = true;
|
|
245
|
+
return { ...f, x: nx, y: ny };
|
|
246
|
+
}
|
|
247
|
+
return f;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!changed) return { ok: true };
|
|
251
|
+
|
|
252
|
+
this.setWorkingFeatures(next);
|
|
253
|
+
this.redraw();
|
|
254
|
+
this.enforceConstraints();
|
|
255
|
+
this.emitWorkingChange();
|
|
256
|
+
|
|
257
|
+
return { ok: true };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private completeFeatures(): {
|
|
261
|
+
ok: boolean;
|
|
262
|
+
issues?: Array<{
|
|
263
|
+
featureId: string;
|
|
264
|
+
groupId?: string;
|
|
265
|
+
reason: string;
|
|
266
|
+
}>;
|
|
267
|
+
} {
|
|
268
|
+
const configService =
|
|
269
|
+
this.context?.services.get<ConfigurationService>("ConfigurationService");
|
|
270
|
+
if (!configService) {
|
|
271
|
+
return {
|
|
272
|
+
ok: false,
|
|
273
|
+
issues: [
|
|
274
|
+
{ featureId: "unknown", reason: "ConfigurationService not found" },
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const dielineWidth = parseLengthToMm(
|
|
280
|
+
configService.get("dieline.width") ?? 500,
|
|
281
|
+
"mm",
|
|
282
|
+
);
|
|
283
|
+
const dielineHeight = parseLengthToMm(
|
|
284
|
+
configService.get("dieline.height") ?? 500,
|
|
285
|
+
"mm",
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const result = completeFeaturesStrict(
|
|
289
|
+
this.workingFeatures,
|
|
290
|
+
{ dielineWidth, dielineHeight },
|
|
291
|
+
(next) => {
|
|
292
|
+
this.isUpdatingConfig = true;
|
|
293
|
+
try {
|
|
294
|
+
configService.update("dieline.features", next);
|
|
295
|
+
} finally {
|
|
296
|
+
this.isUpdatingConfig = false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.workingFeatures = this.cloneFeatures(next as any);
|
|
300
|
+
this.emitWorkingChange();
|
|
301
|
+
},
|
|
160
302
|
);
|
|
161
|
-
|
|
162
|
-
|
|
303
|
+
|
|
304
|
+
if (!result.ok) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
issues: result.issues,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { ok: true };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private addFeature(type: "add" | "subtract") {
|
|
315
|
+
if (!this.canvasService) return false;
|
|
163
316
|
|
|
164
317
|
// Default to top edge center
|
|
165
|
-
const newFeature:
|
|
318
|
+
const newFeature: ConstraintFeature = {
|
|
166
319
|
id: Date.now().toString(),
|
|
167
320
|
operation: type,
|
|
168
|
-
placement: "edge",
|
|
169
321
|
shape: "rect",
|
|
170
322
|
x: 0.5,
|
|
171
323
|
y: 0, // Top edge
|
|
172
|
-
width:
|
|
173
|
-
height:
|
|
324
|
+
width: 10,
|
|
325
|
+
height: 10,
|
|
174
326
|
rotation: 0,
|
|
327
|
+
renderBehavior: "edge",
|
|
328
|
+
// Default constraint: path (snap to edge)
|
|
329
|
+
constraints: [{ type: "path" }],
|
|
175
330
|
};
|
|
176
331
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
[],
|
|
181
|
-
) as DielineFeature[];
|
|
182
|
-
configService.update("dieline.features", [...current, newFeature]);
|
|
183
|
-
}
|
|
332
|
+
this.setWorkingFeatures([...(this.workingFeatures || []), newFeature]);
|
|
333
|
+
this.redraw();
|
|
334
|
+
this.emitWorkingChange();
|
|
184
335
|
return true;
|
|
185
336
|
}
|
|
186
337
|
|
|
187
338
|
private addDoubleLayerHole() {
|
|
188
339
|
if (!this.canvasService) return false;
|
|
189
340
|
|
|
190
|
-
const configService = this.context?.services.get<ConfigurationService>(
|
|
191
|
-
"ConfigurationService",
|
|
192
|
-
);
|
|
193
|
-
const unit = configService?.get("dieline.unit", "mm") || "mm";
|
|
194
|
-
const lugRadius = Coordinate.convertUnit(20, "mm", unit);
|
|
195
|
-
const holeRadius = Coordinate.convertUnit(15, "mm", unit);
|
|
196
|
-
|
|
197
341
|
const groupId = Date.now().toString();
|
|
198
342
|
const timestamp = Date.now();
|
|
199
343
|
|
|
200
344
|
// 1. Lug (Outer) - Add
|
|
201
|
-
const lug:
|
|
345
|
+
const lug: ConstraintFeature = {
|
|
202
346
|
id: `${timestamp}-lug`,
|
|
203
347
|
groupId,
|
|
204
348
|
operation: "add",
|
|
205
349
|
shape: "circle",
|
|
206
|
-
placement: "edge",
|
|
207
350
|
x: 0.5,
|
|
208
351
|
y: 0,
|
|
209
|
-
radius:
|
|
352
|
+
radius: 20,
|
|
210
353
|
rotation: 0,
|
|
354
|
+
renderBehavior: "edge",
|
|
355
|
+
constraints: [{ type: "path" }],
|
|
211
356
|
};
|
|
212
357
|
|
|
213
358
|
// 2. Hole (Inner) - Subtract
|
|
214
|
-
const hole:
|
|
359
|
+
const hole: ConstraintFeature = {
|
|
215
360
|
id: `${timestamp}-hole`,
|
|
216
361
|
groupId,
|
|
217
362
|
operation: "subtract",
|
|
218
363
|
shape: "circle",
|
|
219
|
-
placement: "edge",
|
|
220
364
|
x: 0.5,
|
|
221
365
|
y: 0,
|
|
222
|
-
radius:
|
|
366
|
+
radius: 15,
|
|
223
367
|
rotation: 0,
|
|
368
|
+
renderBehavior: "edge",
|
|
369
|
+
constraints: [{ type: "path" }],
|
|
224
370
|
};
|
|
225
371
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
[],
|
|
230
|
-
) as DielineFeature[];
|
|
231
|
-
configService.update("dieline.features", [...current, lug, hole]);
|
|
232
|
-
}
|
|
372
|
+
this.setWorkingFeatures([...(this.workingFeatures || []), lug, hole]);
|
|
373
|
+
this.redraw();
|
|
374
|
+
this.emitWorkingChange();
|
|
233
375
|
return true;
|
|
234
376
|
}
|
|
235
377
|
|
|
236
378
|
private getGeometryForFeature(
|
|
237
379
|
geometry: DielineGeometry,
|
|
238
|
-
feature?:
|
|
380
|
+
feature?: ConstraintFeature,
|
|
239
381
|
): DielineGeometry {
|
|
240
382
|
// Legacy support or specialized scaling can go here if needed
|
|
241
383
|
// Currently all features operate on the base geometry (or scaled version of it)
|
|
@@ -282,16 +424,16 @@ export class FeatureTool implements Extension {
|
|
|
282
424
|
if (!this.currentGeometry) return;
|
|
283
425
|
|
|
284
426
|
// Determine feature to use for snapping context
|
|
285
|
-
let feature:
|
|
427
|
+
let feature: ConstraintFeature | undefined;
|
|
286
428
|
if (target.data?.isGroup) {
|
|
287
429
|
const indices = target.data?.indices as number[];
|
|
288
430
|
if (indices && indices.length > 0) {
|
|
289
|
-
feature = this.
|
|
431
|
+
feature = this.workingFeatures[indices[0]];
|
|
290
432
|
}
|
|
291
433
|
} else {
|
|
292
434
|
const index = target.data?.index;
|
|
293
435
|
if (index !== undefined) {
|
|
294
|
-
feature = this.
|
|
436
|
+
feature = this.workingFeatures[index];
|
|
295
437
|
}
|
|
296
438
|
}
|
|
297
439
|
|
|
@@ -327,7 +469,6 @@ export class FeatureTool implements Extension {
|
|
|
327
469
|
const target = e.target;
|
|
328
470
|
if (!target || target.data?.type !== "feature-marker") return;
|
|
329
471
|
|
|
330
|
-
// Sync changes back to config
|
|
331
472
|
if (target.data?.isGroup) {
|
|
332
473
|
// It's a Group object
|
|
333
474
|
const groupObj = target as Group;
|
|
@@ -345,7 +486,7 @@ export class FeatureTool implements Extension {
|
|
|
345
486
|
// Simplified: just add relative coordinates if no rotation/scaling on group
|
|
346
487
|
// We locked rotation/scaling, so it's safe.
|
|
347
488
|
|
|
348
|
-
const newFeatures = [...this.
|
|
489
|
+
const newFeatures = [...this.workingFeatures];
|
|
349
490
|
const { x, y } = this.currentGeometry!; // Center is same
|
|
350
491
|
|
|
351
492
|
// Fabric Group objects have .getObjects() which returns children
|
|
@@ -354,7 +495,7 @@ export class FeatureTool implements Extension {
|
|
|
354
495
|
|
|
355
496
|
groupObj.getObjects().forEach((child, i) => {
|
|
356
497
|
const originalIndex = indices[i];
|
|
357
|
-
const feature = this.
|
|
498
|
+
const feature = this.workingFeatures[originalIndex];
|
|
358
499
|
const geometry = this.getGeometryForFeature(
|
|
359
500
|
this.currentGeometry!,
|
|
360
501
|
feature,
|
|
@@ -379,20 +520,8 @@ export class FeatureTool implements Extension {
|
|
|
379
520
|
};
|
|
380
521
|
});
|
|
381
522
|
|
|
382
|
-
this.
|
|
383
|
-
|
|
384
|
-
const configService =
|
|
385
|
-
this.context?.services.get<ConfigurationService>(
|
|
386
|
-
"ConfigurationService",
|
|
387
|
-
);
|
|
388
|
-
if (configService) {
|
|
389
|
-
this.isUpdatingConfig = true;
|
|
390
|
-
try {
|
|
391
|
-
configService.update("dieline.features", this.features);
|
|
392
|
-
} finally {
|
|
393
|
-
this.isUpdatingConfig = false;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
523
|
+
this.setWorkingFeatures(newFeatures);
|
|
524
|
+
this.emitWorkingChange();
|
|
396
525
|
} else {
|
|
397
526
|
// Single object
|
|
398
527
|
this.syncFeatureFromCanvas(target);
|
|
@@ -434,69 +563,42 @@ export class FeatureTool implements Extension {
|
|
|
434
563
|
p: Point,
|
|
435
564
|
geometry: DielineGeometry,
|
|
436
565
|
limit: number,
|
|
437
|
-
feature?:
|
|
566
|
+
feature?: ConstraintFeature
|
|
438
567
|
): { x: number; y: number } {
|
|
439
|
-
if (feature
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const minX = geometry.x - geometry.width / 2;
|
|
443
|
-
const minY = geometry.y - geometry.height / 2;
|
|
444
|
-
|
|
445
|
-
const nx = geometry.width > 0 ? (p.x - minX) / geometry.width : 0.5;
|
|
446
|
-
const ny = geometry.height > 0 ? (p.y - minY) / geometry.height : 0.5;
|
|
568
|
+
if (!feature) {
|
|
569
|
+
return { x: p.x, y: p.y };
|
|
570
|
+
}
|
|
447
571
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const dielineHeight = geometry.height / scale;
|
|
572
|
+
const minX = geometry.x - geometry.width / 2;
|
|
573
|
+
const minY = geometry.y - geometry.height / 2;
|
|
451
574
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
});
|
|
575
|
+
// Normalize
|
|
576
|
+
const nx = geometry.width > 0 ? (p.x - minX) / geometry.width : 0.5;
|
|
577
|
+
const ny = geometry.height > 0 ? (p.y - minY) / geometry.height : 0.5;
|
|
456
578
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
};
|
|
461
|
-
}
|
|
579
|
+
const scale = geometry.scale || 1;
|
|
580
|
+
const dielineWidth = geometry.width / scale;
|
|
581
|
+
const dielineHeight = geometry.height / scale;
|
|
462
582
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// geometry.x/y is center
|
|
466
|
-
const minX = geometry.x - geometry.width / 2;
|
|
467
|
-
const maxX = geometry.x + geometry.width / 2;
|
|
468
|
-
const minY = geometry.y - geometry.height / 2;
|
|
469
|
-
const maxY = geometry.y + geometry.height / 2;
|
|
470
|
-
|
|
471
|
-
return {
|
|
472
|
-
x: Math.max(minX, Math.min(maxX, p.x)),
|
|
473
|
-
y: Math.max(minY, Math.min(maxY, p.y))
|
|
474
|
-
};
|
|
475
|
-
}
|
|
583
|
+
// Filter constraints: only apply those that are NOT validateOnly
|
|
584
|
+
const activeConstraints = feature.constraints?.filter((c) => !c.validateOnly);
|
|
476
585
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
489
|
-
|
|
490
|
-
// If within limit, allow current position (offset from edge)
|
|
491
|
-
if (dist <= limit) {
|
|
492
|
-
return { x: p.x, y: p.y };
|
|
493
|
-
}
|
|
586
|
+
const constrained = ConstraintRegistry.apply(
|
|
587
|
+
nx,
|
|
588
|
+
ny,
|
|
589
|
+
feature,
|
|
590
|
+
{
|
|
591
|
+
dielineWidth,
|
|
592
|
+
dielineHeight,
|
|
593
|
+
geometry,
|
|
594
|
+
},
|
|
595
|
+
activeConstraints,
|
|
596
|
+
);
|
|
494
597
|
|
|
495
|
-
//
|
|
496
|
-
const scale = limit / dist;
|
|
598
|
+
// Denormalize
|
|
497
599
|
return {
|
|
498
|
-
x:
|
|
499
|
-
y:
|
|
600
|
+
x: minX + constrained.x * geometry.width,
|
|
601
|
+
y: minY + constrained.y * geometry.height,
|
|
500
602
|
};
|
|
501
603
|
}
|
|
502
604
|
|
|
@@ -504,10 +606,14 @@ export class FeatureTool implements Extension {
|
|
|
504
606
|
if (!this.currentGeometry || !this.context) return;
|
|
505
607
|
|
|
506
608
|
const index = target.data?.index;
|
|
507
|
-
if (
|
|
609
|
+
if (
|
|
610
|
+
index === undefined ||
|
|
611
|
+
index < 0 ||
|
|
612
|
+
index >= this.workingFeatures.length
|
|
613
|
+
)
|
|
508
614
|
return;
|
|
509
615
|
|
|
510
|
-
const feature = this.
|
|
616
|
+
const feature = this.workingFeatures[index];
|
|
511
617
|
const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
|
|
512
618
|
const { width, height, x, y } = geometry;
|
|
513
619
|
|
|
@@ -527,22 +633,10 @@ export class FeatureTool implements Extension {
|
|
|
527
633
|
// Could also update rotation if we allowed rotating markers
|
|
528
634
|
};
|
|
529
635
|
|
|
530
|
-
const newFeatures = [...this.
|
|
636
|
+
const newFeatures = [...this.workingFeatures];
|
|
531
637
|
newFeatures[index] = updatedFeature;
|
|
532
|
-
this.
|
|
533
|
-
|
|
534
|
-
// Save to config
|
|
535
|
-
const configService = this.context.services.get<ConfigurationService>(
|
|
536
|
-
"ConfigurationService",
|
|
537
|
-
);
|
|
538
|
-
if (configService) {
|
|
539
|
-
this.isUpdatingConfig = true;
|
|
540
|
-
try {
|
|
541
|
-
configService.update("dieline.features", this.features);
|
|
542
|
-
} finally {
|
|
543
|
-
this.isUpdatingConfig = false;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
638
|
+
this.setWorkingFeatures(newFeatures);
|
|
639
|
+
this.emitWorkingChange();
|
|
546
640
|
}
|
|
547
641
|
|
|
548
642
|
private redraw() {
|
|
@@ -556,7 +650,7 @@ export class FeatureTool implements Extension {
|
|
|
556
650
|
.filter((obj: any) => obj.data?.type === "feature-marker");
|
|
557
651
|
existing.forEach((obj) => canvas.remove(obj));
|
|
558
652
|
|
|
559
|
-
if (!this.
|
|
653
|
+
if (!this.workingFeatures || this.workingFeatures.length === 0) {
|
|
560
654
|
this.canvasService.requestRenderAll();
|
|
561
655
|
return;
|
|
562
656
|
}
|
|
@@ -565,11 +659,11 @@ export class FeatureTool implements Extension {
|
|
|
565
659
|
const finalScale = scale;
|
|
566
660
|
|
|
567
661
|
// Group features by groupId
|
|
568
|
-
const groups: { [key: string]: { feature:
|
|
662
|
+
const groups: { [key: string]: { feature: ConstraintFeature; index: number }[] } =
|
|
569
663
|
{};
|
|
570
|
-
const singles: { feature:
|
|
664
|
+
const singles: { feature: ConstraintFeature; index: number }[] = [];
|
|
571
665
|
|
|
572
|
-
this.
|
|
666
|
+
this.workingFeatures.forEach((f: ConstraintFeature, i: number) => {
|
|
573
667
|
if (f.groupId) {
|
|
574
668
|
if (!groups[f.groupId]) groups[f.groupId] = [];
|
|
575
669
|
groups[f.groupId].push({ feature: f, index: i });
|
|
@@ -580,10 +674,9 @@ export class FeatureTool implements Extension {
|
|
|
580
674
|
|
|
581
675
|
// Helper to create marker shape
|
|
582
676
|
const createMarkerShape = (
|
|
583
|
-
feature:
|
|
677
|
+
feature: ConstraintFeature,
|
|
584
678
|
pos: { x: number; y: number },
|
|
585
679
|
) => {
|
|
586
|
-
// Features are in the same unit as geometry.unit
|
|
587
680
|
const featureScale = scale;
|
|
588
681
|
|
|
589
682
|
const visualWidth = (feature.width || 10) * featureScale;
|
|
@@ -628,6 +721,42 @@ export class FeatureTool implements Extension {
|
|
|
628
721
|
if (feature.rotation) {
|
|
629
722
|
shape.rotate(feature.rotation);
|
|
630
723
|
}
|
|
724
|
+
|
|
725
|
+
// Handle Indicator for Bridge
|
|
726
|
+
if (feature.bridge && feature.bridge.type === "vertical") {
|
|
727
|
+
// Create a visual indicator for the bridge
|
|
728
|
+
// A dashed rectangle extending upwards
|
|
729
|
+
const bridgeIndicator = new Rect({
|
|
730
|
+
width: visualWidth,
|
|
731
|
+
height: 100 * featureScale, // Arbitrary long length to show direction
|
|
732
|
+
fill: "transparent",
|
|
733
|
+
stroke: "#888",
|
|
734
|
+
strokeWidth: 1,
|
|
735
|
+
strokeDashArray: [2, 2],
|
|
736
|
+
originX: "center",
|
|
737
|
+
originY: "bottom", // Anchor at bottom so it extends up
|
|
738
|
+
left: pos.x,
|
|
739
|
+
top: pos.y - visualHeight / 2, // Start from top of feature
|
|
740
|
+
opacity: 0.5,
|
|
741
|
+
selectable: false,
|
|
742
|
+
evented: false
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// We need to return a group containing both shape and indicator
|
|
746
|
+
// But createMarkerShape is expected to return one object.
|
|
747
|
+
// If we return a Group, Fabric handles it.
|
|
748
|
+
// But the caller might wrap this in another Group if it's part of a feature group.
|
|
749
|
+
// Fabric supports nested groups.
|
|
750
|
+
|
|
751
|
+
const group = new Group([bridgeIndicator, shape], {
|
|
752
|
+
originX: "center",
|
|
753
|
+
originY: "center",
|
|
754
|
+
left: pos.x,
|
|
755
|
+
top: pos.y
|
|
756
|
+
});
|
|
757
|
+
return group;
|
|
758
|
+
}
|
|
759
|
+
|
|
631
760
|
return shape;
|
|
632
761
|
};
|
|
633
762
|
|
|
@@ -653,27 +782,6 @@ export class FeatureTool implements Extension {
|
|
|
653
782
|
data: { type: "feature-marker", index, isGroup: false },
|
|
654
783
|
});
|
|
655
784
|
|
|
656
|
-
// Auto-hide logic
|
|
657
|
-
marker.set("opacity", 0);
|
|
658
|
-
marker.on("mouseover", () => {
|
|
659
|
-
marker.set("opacity", 1);
|
|
660
|
-
canvas.requestRenderAll();
|
|
661
|
-
});
|
|
662
|
-
marker.on("mouseout", () => {
|
|
663
|
-
if (canvas.getActiveObject() !== marker) {
|
|
664
|
-
marker.set("opacity", 0);
|
|
665
|
-
canvas.requestRenderAll();
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
marker.on("selected", () => {
|
|
669
|
-
marker.set("opacity", 1);
|
|
670
|
-
canvas.requestRenderAll();
|
|
671
|
-
});
|
|
672
|
-
marker.on("deselected", () => {
|
|
673
|
-
marker.set("opacity", 0);
|
|
674
|
-
canvas.requestRenderAll();
|
|
675
|
-
});
|
|
676
|
-
|
|
677
785
|
canvas.add(marker);
|
|
678
786
|
canvas.bringObjectToFront(marker);
|
|
679
787
|
});
|
|
@@ -718,27 +826,6 @@ export class FeatureTool implements Extension {
|
|
|
718
826
|
},
|
|
719
827
|
});
|
|
720
828
|
|
|
721
|
-
// Auto-hide logic for group
|
|
722
|
-
groupObj.set("opacity", 0);
|
|
723
|
-
groupObj.on("mouseover", () => {
|
|
724
|
-
groupObj.set("opacity", 1);
|
|
725
|
-
canvas.requestRenderAll();
|
|
726
|
-
});
|
|
727
|
-
groupObj.on("mouseout", () => {
|
|
728
|
-
if (canvas.getActiveObject() !== groupObj) {
|
|
729
|
-
groupObj.set("opacity", 0);
|
|
730
|
-
canvas.requestRenderAll();
|
|
731
|
-
}
|
|
732
|
-
});
|
|
733
|
-
groupObj.on("selected", () => {
|
|
734
|
-
groupObj.set("opacity", 1);
|
|
735
|
-
canvas.requestRenderAll();
|
|
736
|
-
});
|
|
737
|
-
groupObj.on("deselected", () => {
|
|
738
|
-
groupObj.set("opacity", 0);
|
|
739
|
-
canvas.requestRenderAll();
|
|
740
|
-
});
|
|
741
|
-
|
|
742
829
|
canvas.add(groupObj);
|
|
743
830
|
canvas.bringObjectToFront(groupObj);
|
|
744
831
|
});
|
|
@@ -756,16 +843,16 @@ export class FeatureTool implements Extension {
|
|
|
756
843
|
|
|
757
844
|
markers.forEach((marker: any) => {
|
|
758
845
|
// Find associated feature
|
|
759
|
-
let feature:
|
|
846
|
+
let feature: ConstraintFeature | undefined;
|
|
760
847
|
if (marker.data?.isGroup) {
|
|
761
848
|
const indices = marker.data?.indices as number[];
|
|
762
849
|
if (indices && indices.length > 0) {
|
|
763
|
-
feature = this.
|
|
850
|
+
feature = this.workingFeatures[indices[0]];
|
|
764
851
|
}
|
|
765
852
|
} else {
|
|
766
853
|
const index = marker.data?.index;
|
|
767
854
|
if (index !== undefined) {
|
|
768
|
-
feature = this.
|
|
855
|
+
feature = this.workingFeatures[index];
|
|
769
856
|
}
|
|
770
857
|
}
|
|
771
858
|
|