@pooder/kit 4.0.0 → 4.2.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/.test-dist/src/CanvasService.js +83 -0
- package/.test-dist/src/ViewportSystem.js +75 -0
- package/.test-dist/src/background.js +203 -0
- package/.test-dist/src/constraints.js +153 -0
- package/.test-dist/src/coordinate.js +74 -0
- package/.test-dist/src/dieline.js +758 -0
- package/.test-dist/src/feature.js +687 -0
- package/.test-dist/src/featureComplete.js +31 -0
- package/.test-dist/src/featureDraft.js +31 -0
- package/.test-dist/src/film.js +167 -0
- package/.test-dist/src/geometry.js +292 -0
- package/.test-dist/src/image.js +421 -0
- package/.test-dist/src/index.js +31 -0
- package/.test-dist/src/mirror.js +104 -0
- package/.test-dist/src/ruler.js +383 -0
- package/.test-dist/src/tracer.js +448 -0
- package/.test-dist/src/units.js +30 -0
- package/.test-dist/src/white-ink.js +310 -0
- package/.test-dist/tests/run.js +60 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +54 -5
- package/dist/index.d.ts +54 -5
- package/dist/index.js +584 -190
- package/dist/index.mjs +581 -189
- package/package.json +3 -2
- package/src/CanvasService.ts +7 -0
- package/src/ViewportSystem.ts +92 -0
- package/src/background.ts +230 -230
- package/src/constraints.ts +207 -0
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +194 -75
- package/src/feature.ts +239 -147
- package/src/featureComplete.ts +45 -0
- package/src/film.ts +194 -194
- package/src/geometry.ts +4 -0
- package/src/image.ts +512 -512
- package/src/index.ts +1 -0
- 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/tests/run.ts +81 -0
- 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,7 +13,11 @@ import {
|
|
|
14
13
|
DielineFeature,
|
|
15
14
|
resolveFeaturePosition,
|
|
16
15
|
} from "./geometry";
|
|
17
|
-
import {
|
|
16
|
+
import { ConstraintRegistry } from "./constraints";
|
|
17
|
+
import {
|
|
18
|
+
completeFeaturesStrict,
|
|
19
|
+
} from "./featureComplete";
|
|
20
|
+
import { parseLengthToMm } from "./units";
|
|
18
21
|
|
|
19
22
|
export class FeatureTool implements Extension {
|
|
20
23
|
id = "pooder.kit.feature";
|
|
@@ -23,7 +26,7 @@ export class FeatureTool implements Extension {
|
|
|
23
26
|
name: "FeatureTool",
|
|
24
27
|
};
|
|
25
28
|
|
|
26
|
-
private
|
|
29
|
+
private workingFeatures: DielineFeature[] = [];
|
|
27
30
|
private canvasService?: CanvasService;
|
|
28
31
|
private context?: ExtensionContext;
|
|
29
32
|
private isUpdatingConfig = false;
|
|
@@ -59,14 +62,18 @@ export class FeatureTool implements Extension {
|
|
|
59
62
|
"ConfigurationService",
|
|
60
63
|
);
|
|
61
64
|
if (configService) {
|
|
62
|
-
|
|
65
|
+
const features = (configService.get("dieline.features", []) ||
|
|
66
|
+
[]) as DielineFeature[];
|
|
67
|
+
this.workingFeatures = this.cloneFeatures(features);
|
|
63
68
|
|
|
64
69
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
65
70
|
if (this.isUpdatingConfig) return;
|
|
66
71
|
|
|
67
72
|
if (e.key === "dieline.features") {
|
|
68
|
-
|
|
73
|
+
const next = (e.value || []) as DielineFeature[];
|
|
74
|
+
this.workingFeatures = this.cloneFeatures(next);
|
|
69
75
|
this.redraw();
|
|
76
|
+
this.emitWorkingChange();
|
|
70
77
|
}
|
|
71
78
|
});
|
|
72
79
|
}
|
|
@@ -137,28 +144,175 @@ export class FeatureTool implements Extension {
|
|
|
137
144
|
command: "clearFeatures",
|
|
138
145
|
title: "Clear Features",
|
|
139
146
|
handler: () => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
if (configService) {
|
|
145
|
-
configService.update("dieline.features", []);
|
|
146
|
-
}
|
|
147
|
+
this.setWorkingFeatures([]);
|
|
148
|
+
this.redraw();
|
|
149
|
+
this.emitWorkingChange();
|
|
147
150
|
return true;
|
|
148
151
|
},
|
|
149
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: DielineFeature[]) => {
|
|
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
|
+
},
|
|
150
185
|
] as CommandContribution[],
|
|
151
186
|
};
|
|
152
187
|
}
|
|
153
188
|
|
|
154
|
-
private
|
|
155
|
-
|
|
189
|
+
private cloneFeatures(features: DielineFeature[]): DielineFeature[] {
|
|
190
|
+
return JSON.parse(JSON.stringify(features || [])) as DielineFeature[];
|
|
191
|
+
}
|
|
156
192
|
|
|
157
|
-
|
|
158
|
-
|
|
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: DielineFeature[]) {
|
|
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",
|
|
159
227
|
);
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
},
|
|
302
|
+
);
|
|
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;
|
|
162
316
|
|
|
163
317
|
// Default to top edge center
|
|
164
318
|
const newFeature: DielineFeature = {
|
|
@@ -168,31 +322,20 @@ export class FeatureTool implements Extension {
|
|
|
168
322
|
shape: "rect",
|
|
169
323
|
x: 0.5,
|
|
170
324
|
y: 0, // Top edge
|
|
171
|
-
width:
|
|
172
|
-
height:
|
|
325
|
+
width: 10,
|
|
326
|
+
height: 10,
|
|
173
327
|
rotation: 0,
|
|
174
328
|
};
|
|
175
329
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
[],
|
|
180
|
-
) as DielineFeature[];
|
|
181
|
-
configService.update("dieline.features", [...current, newFeature]);
|
|
182
|
-
}
|
|
330
|
+
this.setWorkingFeatures([...(this.workingFeatures || []), newFeature]);
|
|
331
|
+
this.redraw();
|
|
332
|
+
this.emitWorkingChange();
|
|
183
333
|
return true;
|
|
184
334
|
}
|
|
185
335
|
|
|
186
336
|
private addDoubleLayerHole() {
|
|
187
337
|
if (!this.canvasService) return false;
|
|
188
338
|
|
|
189
|
-
const configService = this.context?.services.get<ConfigurationService>(
|
|
190
|
-
"ConfigurationService",
|
|
191
|
-
);
|
|
192
|
-
const unit = configService?.get("dieline.unit", "mm") || "mm";
|
|
193
|
-
const lugRadius = Coordinate.convertUnit(20, "mm", unit);
|
|
194
|
-
const holeRadius = Coordinate.convertUnit(15, "mm", unit);
|
|
195
|
-
|
|
196
339
|
const groupId = Date.now().toString();
|
|
197
340
|
const timestamp = Date.now();
|
|
198
341
|
|
|
@@ -205,7 +348,7 @@ export class FeatureTool implements Extension {
|
|
|
205
348
|
placement: "edge",
|
|
206
349
|
x: 0.5,
|
|
207
350
|
y: 0,
|
|
208
|
-
radius:
|
|
351
|
+
radius: 20,
|
|
209
352
|
rotation: 0,
|
|
210
353
|
};
|
|
211
354
|
|
|
@@ -218,17 +361,13 @@ export class FeatureTool implements Extension {
|
|
|
218
361
|
placement: "edge",
|
|
219
362
|
x: 0.5,
|
|
220
363
|
y: 0,
|
|
221
|
-
radius:
|
|
364
|
+
radius: 15,
|
|
222
365
|
rotation: 0,
|
|
223
366
|
};
|
|
224
367
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
[],
|
|
229
|
-
) as DielineFeature[];
|
|
230
|
-
configService.update("dieline.features", [...current, lug, hole]);
|
|
231
|
-
}
|
|
368
|
+
this.setWorkingFeatures([...(this.workingFeatures || []), lug, hole]);
|
|
369
|
+
this.redraw();
|
|
370
|
+
this.emitWorkingChange();
|
|
232
371
|
return true;
|
|
233
372
|
}
|
|
234
373
|
|
|
@@ -285,12 +424,12 @@ export class FeatureTool implements Extension {
|
|
|
285
424
|
if (target.data?.isGroup) {
|
|
286
425
|
const indices = target.data?.indices as number[];
|
|
287
426
|
if (indices && indices.length > 0) {
|
|
288
|
-
feature = this.
|
|
427
|
+
feature = this.workingFeatures[indices[0]];
|
|
289
428
|
}
|
|
290
429
|
} else {
|
|
291
430
|
const index = target.data?.index;
|
|
292
431
|
if (index !== undefined) {
|
|
293
|
-
feature = this.
|
|
432
|
+
feature = this.workingFeatures[index];
|
|
294
433
|
}
|
|
295
434
|
}
|
|
296
435
|
|
|
@@ -326,7 +465,6 @@ export class FeatureTool implements Extension {
|
|
|
326
465
|
const target = e.target;
|
|
327
466
|
if (!target || target.data?.type !== "feature-marker") return;
|
|
328
467
|
|
|
329
|
-
// Sync changes back to config
|
|
330
468
|
if (target.data?.isGroup) {
|
|
331
469
|
// It's a Group object
|
|
332
470
|
const groupObj = target as Group;
|
|
@@ -344,7 +482,7 @@ export class FeatureTool implements Extension {
|
|
|
344
482
|
// Simplified: just add relative coordinates if no rotation/scaling on group
|
|
345
483
|
// We locked rotation/scaling, so it's safe.
|
|
346
484
|
|
|
347
|
-
const newFeatures = [...this.
|
|
485
|
+
const newFeatures = [...this.workingFeatures];
|
|
348
486
|
const { x, y } = this.currentGeometry!; // Center is same
|
|
349
487
|
|
|
350
488
|
// Fabric Group objects have .getObjects() which returns children
|
|
@@ -353,7 +491,7 @@ export class FeatureTool implements Extension {
|
|
|
353
491
|
|
|
354
492
|
groupObj.getObjects().forEach((child, i) => {
|
|
355
493
|
const originalIndex = indices[i];
|
|
356
|
-
const feature = this.
|
|
494
|
+
const feature = this.workingFeatures[originalIndex];
|
|
357
495
|
const geometry = this.getGeometryForFeature(
|
|
358
496
|
this.currentGeometry!,
|
|
359
497
|
feature,
|
|
@@ -378,20 +516,8 @@ export class FeatureTool implements Extension {
|
|
|
378
516
|
};
|
|
379
517
|
});
|
|
380
518
|
|
|
381
|
-
this.
|
|
382
|
-
|
|
383
|
-
const configService =
|
|
384
|
-
this.context?.services.get<ConfigurationService>(
|
|
385
|
-
"ConfigurationService",
|
|
386
|
-
);
|
|
387
|
-
if (configService) {
|
|
388
|
-
this.isUpdatingConfig = true;
|
|
389
|
-
try {
|
|
390
|
-
configService.update("dieline.features", this.features);
|
|
391
|
-
} finally {
|
|
392
|
-
this.isUpdatingConfig = false;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
519
|
+
this.setWorkingFeatures(newFeatures);
|
|
520
|
+
this.emitWorkingChange();
|
|
395
521
|
} else {
|
|
396
522
|
// Single object
|
|
397
523
|
this.syncFeatureFromCanvas(target);
|
|
@@ -435,39 +561,56 @@ export class FeatureTool implements Extension {
|
|
|
435
561
|
limit: number,
|
|
436
562
|
feature?: DielineFeature
|
|
437
563
|
): { x: number; y: number } {
|
|
564
|
+
if (feature && feature.constraints) {
|
|
565
|
+
const minX = geometry.x - geometry.width / 2;
|
|
566
|
+
const minY = geometry.y - geometry.height / 2;
|
|
567
|
+
|
|
568
|
+
const nx = geometry.width > 0 ? (p.x - minX) / geometry.width : 0.5;
|
|
569
|
+
const ny = geometry.height > 0 ? (p.y - minY) / geometry.height : 0.5;
|
|
570
|
+
|
|
571
|
+
const scale = geometry.scale || 1;
|
|
572
|
+
const dielineWidth = geometry.width / scale;
|
|
573
|
+
const dielineHeight = geometry.height / scale;
|
|
574
|
+
|
|
575
|
+
const constrained = ConstraintRegistry.apply(nx, ny, feature, {
|
|
576
|
+
dielineWidth,
|
|
577
|
+
dielineHeight,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
x: minX + constrained.x * geometry.width,
|
|
582
|
+
y: minY + constrained.y * geometry.height,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
438
586
|
if (feature && feature.placement === "internal") {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
y: Math.max(minY, Math.min(maxY, p.y))
|
|
449
|
-
};
|
|
587
|
+
const minX = geometry.x - geometry.width / 2;
|
|
588
|
+
const maxX = geometry.x + geometry.width / 2;
|
|
589
|
+
const minY = geometry.y - geometry.height / 2;
|
|
590
|
+
const maxY = geometry.y + geometry.height / 2;
|
|
591
|
+
|
|
592
|
+
return {
|
|
593
|
+
x: Math.max(minX, Math.min(maxX, p.x)),
|
|
594
|
+
y: Math.max(minY, Math.min(maxY, p.y)),
|
|
595
|
+
};
|
|
450
596
|
}
|
|
451
597
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
598
|
+
const nearest = getNearestPointOnDieline(
|
|
599
|
+
{ x: p.x, y: p.y },
|
|
600
|
+
{
|
|
601
|
+
...geometry,
|
|
602
|
+
features: [],
|
|
603
|
+
} as any,
|
|
604
|
+
);
|
|
459
605
|
|
|
460
|
-
// Calculate vector from nearest point to current point
|
|
461
606
|
const dx = p.x - nearest.x;
|
|
462
607
|
const dy = p.y - nearest.y;
|
|
463
608
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
464
609
|
|
|
465
|
-
// If within limit, allow current position (offset from edge)
|
|
466
610
|
if (dist <= limit) {
|
|
467
611
|
return { x: p.x, y: p.y };
|
|
468
612
|
}
|
|
469
613
|
|
|
470
|
-
// Otherwise, clamp to limit
|
|
471
614
|
const scale = limit / dist;
|
|
472
615
|
return {
|
|
473
616
|
x: nearest.x + dx * scale,
|
|
@@ -479,10 +622,14 @@ export class FeatureTool implements Extension {
|
|
|
479
622
|
if (!this.currentGeometry || !this.context) return;
|
|
480
623
|
|
|
481
624
|
const index = target.data?.index;
|
|
482
|
-
if (
|
|
625
|
+
if (
|
|
626
|
+
index === undefined ||
|
|
627
|
+
index < 0 ||
|
|
628
|
+
index >= this.workingFeatures.length
|
|
629
|
+
)
|
|
483
630
|
return;
|
|
484
631
|
|
|
485
|
-
const feature = this.
|
|
632
|
+
const feature = this.workingFeatures[index];
|
|
486
633
|
const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
|
|
487
634
|
const { width, height, x, y } = geometry;
|
|
488
635
|
|
|
@@ -502,22 +649,10 @@ export class FeatureTool implements Extension {
|
|
|
502
649
|
// Could also update rotation if we allowed rotating markers
|
|
503
650
|
};
|
|
504
651
|
|
|
505
|
-
const newFeatures = [...this.
|
|
652
|
+
const newFeatures = [...this.workingFeatures];
|
|
506
653
|
newFeatures[index] = updatedFeature;
|
|
507
|
-
this.
|
|
508
|
-
|
|
509
|
-
// Save to config
|
|
510
|
-
const configService = this.context.services.get<ConfigurationService>(
|
|
511
|
-
"ConfigurationService",
|
|
512
|
-
);
|
|
513
|
-
if (configService) {
|
|
514
|
-
this.isUpdatingConfig = true;
|
|
515
|
-
try {
|
|
516
|
-
configService.update("dieline.features", this.features);
|
|
517
|
-
} finally {
|
|
518
|
-
this.isUpdatingConfig = false;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
654
|
+
this.setWorkingFeatures(newFeatures);
|
|
655
|
+
this.emitWorkingChange();
|
|
521
656
|
}
|
|
522
657
|
|
|
523
658
|
private redraw() {
|
|
@@ -531,7 +666,7 @@ export class FeatureTool implements Extension {
|
|
|
531
666
|
.filter((obj: any) => obj.data?.type === "feature-marker");
|
|
532
667
|
existing.forEach((obj) => canvas.remove(obj));
|
|
533
668
|
|
|
534
|
-
if (!this.
|
|
669
|
+
if (!this.workingFeatures || this.workingFeatures.length === 0) {
|
|
535
670
|
this.canvasService.requestRenderAll();
|
|
536
671
|
return;
|
|
537
672
|
}
|
|
@@ -544,7 +679,7 @@ export class FeatureTool implements Extension {
|
|
|
544
679
|
{};
|
|
545
680
|
const singles: { feature: DielineFeature; index: number }[] = [];
|
|
546
681
|
|
|
547
|
-
this.
|
|
682
|
+
this.workingFeatures.forEach((f: DielineFeature, i: number) => {
|
|
548
683
|
if (f.groupId) {
|
|
549
684
|
if (!groups[f.groupId]) groups[f.groupId] = [];
|
|
550
685
|
groups[f.groupId].push({ feature: f, index: i });
|
|
@@ -558,7 +693,6 @@ export class FeatureTool implements Extension {
|
|
|
558
693
|
feature: DielineFeature,
|
|
559
694
|
pos: { x: number; y: number },
|
|
560
695
|
) => {
|
|
561
|
-
// Features are in the same unit as geometry.unit
|
|
562
696
|
const featureScale = scale;
|
|
563
697
|
|
|
564
698
|
const visualWidth = (feature.width || 10) * featureScale;
|
|
@@ -628,27 +762,6 @@ export class FeatureTool implements Extension {
|
|
|
628
762
|
data: { type: "feature-marker", index, isGroup: false },
|
|
629
763
|
});
|
|
630
764
|
|
|
631
|
-
// Auto-hide logic
|
|
632
|
-
marker.set("opacity", 0);
|
|
633
|
-
marker.on("mouseover", () => {
|
|
634
|
-
marker.set("opacity", 1);
|
|
635
|
-
canvas.requestRenderAll();
|
|
636
|
-
});
|
|
637
|
-
marker.on("mouseout", () => {
|
|
638
|
-
if (canvas.getActiveObject() !== marker) {
|
|
639
|
-
marker.set("opacity", 0);
|
|
640
|
-
canvas.requestRenderAll();
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
marker.on("selected", () => {
|
|
644
|
-
marker.set("opacity", 1);
|
|
645
|
-
canvas.requestRenderAll();
|
|
646
|
-
});
|
|
647
|
-
marker.on("deselected", () => {
|
|
648
|
-
marker.set("opacity", 0);
|
|
649
|
-
canvas.requestRenderAll();
|
|
650
|
-
});
|
|
651
|
-
|
|
652
765
|
canvas.add(marker);
|
|
653
766
|
canvas.bringObjectToFront(marker);
|
|
654
767
|
});
|
|
@@ -693,27 +806,6 @@ export class FeatureTool implements Extension {
|
|
|
693
806
|
},
|
|
694
807
|
});
|
|
695
808
|
|
|
696
|
-
// Auto-hide logic for group
|
|
697
|
-
groupObj.set("opacity", 0);
|
|
698
|
-
groupObj.on("mouseover", () => {
|
|
699
|
-
groupObj.set("opacity", 1);
|
|
700
|
-
canvas.requestRenderAll();
|
|
701
|
-
});
|
|
702
|
-
groupObj.on("mouseout", () => {
|
|
703
|
-
if (canvas.getActiveObject() !== groupObj) {
|
|
704
|
-
groupObj.set("opacity", 0);
|
|
705
|
-
canvas.requestRenderAll();
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
groupObj.on("selected", () => {
|
|
709
|
-
groupObj.set("opacity", 1);
|
|
710
|
-
canvas.requestRenderAll();
|
|
711
|
-
});
|
|
712
|
-
groupObj.on("deselected", () => {
|
|
713
|
-
groupObj.set("opacity", 0);
|
|
714
|
-
canvas.requestRenderAll();
|
|
715
|
-
});
|
|
716
|
-
|
|
717
809
|
canvas.add(groupObj);
|
|
718
810
|
canvas.bringObjectToFront(groupObj);
|
|
719
811
|
});
|
|
@@ -735,12 +827,12 @@ export class FeatureTool implements Extension {
|
|
|
735
827
|
if (marker.data?.isGroup) {
|
|
736
828
|
const indices = marker.data?.indices as number[];
|
|
737
829
|
if (indices && indices.length > 0) {
|
|
738
|
-
feature = this.
|
|
830
|
+
feature = this.workingFeatures[indices[0]];
|
|
739
831
|
}
|
|
740
832
|
} else {
|
|
741
833
|
const index = marker.data?.index;
|
|
742
834
|
if (index !== undefined) {
|
|
743
|
-
feature = this.
|
|
835
|
+
feature = this.workingFeatures[index];
|
|
744
836
|
}
|
|
745
837
|
}
|
|
746
838
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ConstraintContext, ConstraintFeature, ConstraintRegistry } from "./constraints";
|
|
2
|
+
|
|
3
|
+
export type FeatureCompleteIssue = {
|
|
4
|
+
featureId: string;
|
|
5
|
+
groupId?: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function validateFeaturesStrict(
|
|
10
|
+
features: ConstraintFeature[],
|
|
11
|
+
context: ConstraintContext,
|
|
12
|
+
): { ok: boolean; issues?: FeatureCompleteIssue[] } {
|
|
13
|
+
const eps = 1e-6;
|
|
14
|
+
const issues: FeatureCompleteIssue[] = [];
|
|
15
|
+
|
|
16
|
+
for (const f of features) {
|
|
17
|
+
if (!f.constraints?.type) continue;
|
|
18
|
+
const constrained = ConstraintRegistry.apply(f.x, f.y, f, context);
|
|
19
|
+
if (
|
|
20
|
+
Math.abs(constrained.x - f.x) > eps ||
|
|
21
|
+
Math.abs(constrained.y - f.y) > eps
|
|
22
|
+
) {
|
|
23
|
+
issues.push({
|
|
24
|
+
featureId: f.id,
|
|
25
|
+
groupId: f.groupId,
|
|
26
|
+
reason: "Position violates constraint strategy",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { ok: issues.length === 0, issues: issues.length ? issues : undefined };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function completeFeaturesStrict(
|
|
35
|
+
features: ConstraintFeature[],
|
|
36
|
+
context: ConstraintContext,
|
|
37
|
+
update: (nextFeatures: ConstraintFeature[]) => void,
|
|
38
|
+
): { ok: boolean; issues?: FeatureCompleteIssue[] } {
|
|
39
|
+
const validation = validateFeaturesStrict(features, context);
|
|
40
|
+
if (!validation.ok) return validation;
|
|
41
|
+
const next = JSON.parse(JSON.stringify(features || [])) as ConstraintFeature[];
|
|
42
|
+
update(next);
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
|