@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/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 { Coordinate } from "./coordinate";
18
- import { ConstraintRegistry } from "./constraints";
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 features: DielineFeature[] = [];
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: DielineFeature[];
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
- this.features = configService.get("dieline.features", []);
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
- this.features = e.value || [];
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
- const configService =
142
- this.context?.services.get<ConfigurationService>(
143
- "ConfigurationService",
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 addFeature(type: "add" | "subtract") {
156
- if (!this.canvasService) return false;
189
+ private cloneFeatures(features: ConstraintFeature[]): ConstraintFeature[] {
190
+ return JSON.parse(JSON.stringify(features || [])) as ConstraintFeature[];
191
+ }
157
192
 
158
- const configService = this.context?.services.get<ConfigurationService>(
159
- "ConfigurationService",
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
- const unit = configService?.get("dieline.unit", "mm") || "mm";
162
- const defaultSize = Coordinate.convertUnit(10, "mm", unit);
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: DielineFeature = {
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: defaultSize,
173
- height: defaultSize,
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
- if (configService) {
178
- const current = configService.get(
179
- "dieline.features",
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: DielineFeature = {
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: lugRadius, // 20mm
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: DielineFeature = {
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: holeRadius, // 15mm
366
+ radius: 15,
223
367
  rotation: 0,
368
+ renderBehavior: "edge",
369
+ constraints: [{ type: "path" }],
224
370
  };
225
371
 
226
- if (configService) {
227
- const current = configService.get(
228
- "dieline.features",
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?: DielineFeature,
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: DielineFeature | undefined;
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.features[indices[0]];
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.features[index];
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.features];
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.features[originalIndex];
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.features = newFeatures;
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?: DielineFeature
566
+ feature?: ConstraintFeature
438
567
  ): { x: number; y: number } {
439
- if (feature && feature.constraints) {
440
- // Use Constraint Registry
441
- // Convert to normalized coordinates
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
- const scale = geometry.scale || 1;
449
- const dielineWidth = geometry.width / scale;
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
- const constrained = ConstraintRegistry.apply(nx, ny, feature, {
453
- dielineWidth,
454
- dielineHeight,
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
- return {
458
- x: minX + constrained.x * geometry.width,
459
- y: minY + constrained.y * geometry.height,
460
- };
461
- }
579
+ const scale = geometry.scale || 1;
580
+ const dielineWidth = geometry.width / scale;
581
+ const dielineHeight = geometry.height / scale;
462
582
 
463
- if (feature && feature.placement === "internal") {
464
- // Constrain to bounds
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
- // Use geometry helper to find nearest point on Base Shape
478
- // geometry object matches GeometryOptions structure required by getNearestPointOnDieline
479
- // except for 'features' which we don't need for base shape snapping
480
- const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, {
481
- ...geometry,
482
- features: [],
483
- } as any);
484
-
485
- // Calculate vector from nearest point to current point
486
- const dx = p.x - nearest.x;
487
- const dy = p.y - nearest.y;
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
- // Otherwise, clamp to limit
496
- const scale = limit / dist;
598
+ // Denormalize
497
599
  return {
498
- x: nearest.x + dx * scale,
499
- y: nearest.y + dy * scale,
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 (index === undefined || index < 0 || index >= this.features.length)
609
+ if (
610
+ index === undefined ||
611
+ index < 0 ||
612
+ index >= this.workingFeatures.length
613
+ )
508
614
  return;
509
615
 
510
- const feature = this.features[index];
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.features];
636
+ const newFeatures = [...this.workingFeatures];
531
637
  newFeatures[index] = updatedFeature;
532
- this.features = newFeatures;
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.features || this.features.length === 0) {
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: DielineFeature; index: number }[] } =
662
+ const groups: { [key: string]: { feature: ConstraintFeature; index: number }[] } =
569
663
  {};
570
- const singles: { feature: DielineFeature; index: number }[] = [];
664
+ const singles: { feature: ConstraintFeature; index: number }[] = [];
571
665
 
572
- this.features.forEach((f, i) => {
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: DielineFeature,
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: DielineFeature | undefined;
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.features[indices[0]];
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.features[index];
855
+ feature = this.workingFeatures[index];
769
856
  }
770
857
  }
771
858