@design-core/core 51.1.0 → 52.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.
- package/.github/workflows/publish.yml +40 -0
- package/README.md +14 -1
- package/core/dimensions/alignedDimension.js +3 -1
- package/core/dimensions/angularDimension.js +118 -36
- package/core/dimensions/circularDimensionBase.js +32 -12
- package/core/dimensions/diametricDimension.js +29 -1
- package/core/dimensions/dimension.js +58 -5
- package/core/dimensions/dimensionBase.js +107 -4
- package/core/dimensions/linearDimensionBase.js +75 -37
- package/core/dimensions/radialDimension.js +26 -1
- package/core/dimensions/rotatedDimension.js +6 -3
- package/core/entities/entity.js +9 -0
- package/core/lib/auxiliary/snapPoint.js +9 -0
- package/core/lib/canvas.js +26 -1
- package/core/lib/commandManager.js +9 -4
- package/core/lib/dxf/dxf.js +8 -2
- package/core/lib/inputManager.js +24 -3
- package/core/lib/mouse.js +10 -0
- package/core/lib/plotOptions.js +17 -0
- package/core/lib/renderers/pdfRenderer.js +17 -1
- package/core/lib/scene.js +2 -0
- package/core/lib/selectionManager.js +28 -0
- package/core/lib/strings.js +3 -0
- package/core/properties/dimType.js +14 -0
- package/core/properties/property.js +1 -0
- package/core/tables/dimStyle.js +32 -2
- package/core/tools/gripStretch.js +97 -0
- package/core/tools/offset.js +7 -1
- package/core/tools/tool.js +10 -0
- package/core/tools/windowPick.js +75 -0
- package/cspell.json +5 -0
- package/package.json +7 -2
- package/test/dimensions/alignedDimension.test.js +165 -0
- package/test/dimensions/angularDimension.test.js +149 -0
- package/test/dimensions/diametricDimension.test.js +76 -4
- package/test/dimensions/dimension.test.js +130 -0
- package/test/dimensions/dimensionBase.test.js +97 -4
- package/test/dimensions/radialDimension.test.js +33 -0
- package/test/dimensions/rotatedDimension.test.js +80 -0
- package/test/integration/exportIntegration.reference.dxf +3 -9
- package/test/integration/plotPageSizeIntegration.test.js +113 -0
- package/test/integration/userInputIntegration.reference.dxf +3 -9
- package/test/lib/inputManager.test.js +316 -1
- package/test/lib/plotOptions.test.js +58 -1
- package/test/lib/selectionManager.test.js +42 -0
- package/test/renderers/pdfRenderer.test.js +26 -0
- package/test/tables/dimstyle.test.js +32 -0
- package/test/tools/gripStretch.test.js +108 -0
- package/test/tools/offset.test.js +2 -0
- package/test/tools/tool.test.js +25 -0
- package/test/tools/windowPick.test.js +129 -0
|
@@ -84,6 +84,17 @@ export class DimensionBase extends Entity {
|
|
|
84
84
|
writable: true,
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
// DXF Groupcode 70 bit 128 - user-positioned text flag, exposed as a property so a grip-drop
|
|
88
|
+
// commit can include it explicitly in the UpdateState snapshot, alongside points, for undo/redo
|
|
89
|
+
this.properties.add(Property.Names.USERPOSITIONEDTEXT, {
|
|
90
|
+
type: Property.Type.BOOLEAN,
|
|
91
|
+
visible: false,
|
|
92
|
+
get: (entity) => entity.dimType.hasUserPositionedText(),
|
|
93
|
+
set: (entity, value) => {
|
|
94
|
+
entity.dimType.userPositionedText = value;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
87
98
|
if (data) {
|
|
88
99
|
if (data.hasOwnProperty('41')) {
|
|
89
100
|
// DXF Groupcode 41 - Line Spacing Factor
|
|
@@ -350,6 +361,30 @@ export class DimensionBase extends Entity {
|
|
|
350
361
|
return DesignCore.DimStyleManager.getItemByName(this.getProperty(Property.Names.DIMENSIONSTYLE));
|
|
351
362
|
}
|
|
352
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Get the grip points for interactive editing on the canvas.
|
|
366
|
+
* Only the text position (Pt11) is exposed - it's never a dependency for any other point.
|
|
367
|
+
* @return {Array<{point: Point, apply: Function}>} grips
|
|
368
|
+
*/
|
|
369
|
+
getGrips() {
|
|
370
|
+
const Pt11 = this.getPointBySequence(this.points, 11);
|
|
371
|
+
|
|
372
|
+
if (!Pt11) {
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const apply = (newPoint) => {
|
|
377
|
+
return this.points.map((point) => {
|
|
378
|
+
if (point.sequence === 11) {
|
|
379
|
+
return new Point(newPoint.x, newPoint.y, point.bulge, point.sequence);
|
|
380
|
+
}
|
|
381
|
+
return point;
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
return [{ point: Pt11, apply: apply }];
|
|
386
|
+
}
|
|
387
|
+
|
|
353
388
|
/**
|
|
354
389
|
* Get the point for the sequence number
|
|
355
390
|
* @param {Array} points - array of points to search
|
|
@@ -430,15 +465,24 @@ export class DimensionBase extends Entity {
|
|
|
430
465
|
return centreMark;
|
|
431
466
|
}
|
|
432
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Refresh the block geometry if it hasn't been built yet.
|
|
470
|
+
* Needed because block delegates (draw, boundingBox, within, touched, closestPoint)
|
|
471
|
+
* can run before the first draw() call (e.g. a window-select immediately after creation).
|
|
472
|
+
*/
|
|
473
|
+
ensureRefreshed() {
|
|
474
|
+
if (this.block.entities.length === 0) {
|
|
475
|
+
this.refresh();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
433
479
|
/**
|
|
434
480
|
* Draw the entity
|
|
435
481
|
* @param {Object} renderer
|
|
436
482
|
* @return {Array} block entities for the canvas to render recursively
|
|
437
483
|
*/
|
|
438
484
|
draw(renderer) {
|
|
439
|
-
|
|
440
|
-
this.refresh();
|
|
441
|
-
}
|
|
485
|
+
this.ensureRefreshed();
|
|
442
486
|
|
|
443
487
|
return this.block.entities;
|
|
444
488
|
}
|
|
@@ -460,6 +504,7 @@ export class DimensionBase extends Entity {
|
|
|
460
504
|
* @return {Array} - [Point, distance]
|
|
461
505
|
*/
|
|
462
506
|
closestPoint(P) {
|
|
507
|
+
this.ensureRefreshed();
|
|
463
508
|
return this.block.closestPoint(P);
|
|
464
509
|
}
|
|
465
510
|
|
|
@@ -468,6 +513,7 @@ export class DimensionBase extends Entity {
|
|
|
468
513
|
* @return {BoundingBox}
|
|
469
514
|
*/
|
|
470
515
|
boundingBox() {
|
|
516
|
+
this.ensureRefreshed();
|
|
471
517
|
return this.block.boundingBox();
|
|
472
518
|
}
|
|
473
519
|
|
|
@@ -477,6 +523,7 @@ export class DimensionBase extends Entity {
|
|
|
477
523
|
* @return {boolean} true if within
|
|
478
524
|
*/
|
|
479
525
|
within(selection) {
|
|
526
|
+
this.ensureRefreshed();
|
|
480
527
|
return this.block.within(selection);
|
|
481
528
|
}
|
|
482
529
|
|
|
@@ -486,6 +533,7 @@ export class DimensionBase extends Entity {
|
|
|
486
533
|
* @return {boolean} true if touched
|
|
487
534
|
*/
|
|
488
535
|
touched(selection) {
|
|
536
|
+
this.ensureRefreshed();
|
|
489
537
|
return this.block.touched(selection);
|
|
490
538
|
}
|
|
491
539
|
|
|
@@ -493,7 +541,22 @@ export class DimensionBase extends Entity {
|
|
|
493
541
|
* Refresh the dimension geometry
|
|
494
542
|
*/
|
|
495
543
|
refresh() {
|
|
496
|
-
|
|
544
|
+
// buildDimension() can itself call setProperty() (e.g. LINEARDIMANGLE) which would
|
|
545
|
+
// otherwise re-enter refresh() recursively - skip while already refreshing
|
|
546
|
+
if (this.refreshing) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
this.refreshing = true;
|
|
551
|
+
let entities;
|
|
552
|
+
try {
|
|
553
|
+
entities = this.buildDimension();
|
|
554
|
+
} catch (err) {
|
|
555
|
+
// geometry can't be built yet (e.g. incomplete points during interactive creation) - skip this refresh
|
|
556
|
+
return;
|
|
557
|
+
} finally {
|
|
558
|
+
this.refreshing = false;
|
|
559
|
+
}
|
|
497
560
|
|
|
498
561
|
if (entities) {
|
|
499
562
|
this.block.clearEntities();
|
|
@@ -513,7 +576,47 @@ export class DimensionBase extends Entity {
|
|
|
513
576
|
*/
|
|
514
577
|
setProperty(property, value) {
|
|
515
578
|
if (this.properties.has(property)) {
|
|
579
|
+
if (property === Property.Names.POINTS) {
|
|
580
|
+
// Dragging the Pt11 grip independently of the anchor points (Pt13/Pt14) - as opposed
|
|
581
|
+
// to a rigid whole-dimension Move/Rotate, which moves them together - means the user
|
|
582
|
+
// has explicitly repositioned the text; record that via the DXF bit 128 flag so
|
|
583
|
+
// buildDimension() uses Pt11 directly instead of auto-placing it (DIMJUST/DIMTAD)
|
|
584
|
+
const oldPt11 = this.getPointBySequence(this.points, 11);
|
|
585
|
+
const oldPt13 = this.getPointBySequence(this.points, 13);
|
|
586
|
+
const oldPt14 = this.getPointBySequence(this.points, 14);
|
|
587
|
+
const newPt11 = this.getPointBySequence(value, 11);
|
|
588
|
+
const newPt13 = this.getPointBySequence(value, 13);
|
|
589
|
+
const newPt14 = this.getPointBySequence(value, 14);
|
|
590
|
+
|
|
591
|
+
if (oldPt11 && newPt11 && oldPt13 && newPt13 && oldPt14 && newPt14) {
|
|
592
|
+
const pt11Moved = !oldPt11.isSame(newPt11);
|
|
593
|
+
const anchorsUnchanged = oldPt13.isSame(newPt13) && oldPt14.isSame(newPt14);
|
|
594
|
+
if (pt11Moved && anchorsUnchanged) {
|
|
595
|
+
this.dimType.userPositionedText = true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (property === Property.Names.POINTS && this.dimType.getBaseDimType() === 0) {
|
|
601
|
+
// Rotated dimension: LINEARDIMANGLE is a fixed angle independent of the points,
|
|
602
|
+
// so a rigid rotation of the points (e.g. via the Rotate tool) must also rotate
|
|
603
|
+
// this stored angle by the same amount - mirrors Hatch's points -> angle handling
|
|
604
|
+
const oldPt13 = this.getPointBySequence(this.points, 13);
|
|
605
|
+
const oldPt14 = this.getPointBySequence(this.points, 14);
|
|
606
|
+
const newPt13 = this.getPointBySequence(value, 13);
|
|
607
|
+
const newPt14 = this.getPointBySequence(value, 14);
|
|
608
|
+
|
|
609
|
+
if (oldPt13 && oldPt14 && newPt13 && newPt14) {
|
|
610
|
+
const theta = newPt13.angle(newPt14) - oldPt13.angle(oldPt14);
|
|
611
|
+
if (theta !== 0) {
|
|
612
|
+
this.setProperty(Property.Names.LINEARDIMANGLE, this.getProperty(Property.Names.LINEARDIMANGLE) + Utils.radians2degrees(theta));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
516
616
|
super.setProperty(property, value);
|
|
617
|
+
// geometry is derived from properties (points, style, angle, etc.) - rebuild it,
|
|
618
|
+
// e.g. so tools like Rotate/Move that update 'points' are reflected visually
|
|
619
|
+
this.refresh();
|
|
517
620
|
return;
|
|
518
621
|
}
|
|
519
622
|
if (this.hasOwnProperty(property)) {
|
|
@@ -4,12 +4,12 @@ import { Text } from '../entities/text.js';
|
|
|
4
4
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
5
5
|
import { Logging } from '../lib/logging.js';
|
|
6
6
|
import { DimensionBase } from './dimensionBase.js';
|
|
7
|
-
import { Point } from '../entities/point.js';
|
|
8
7
|
|
|
9
8
|
import { DesignCore } from '../designCore.js';
|
|
10
9
|
import { Property } from '../properties/property.js';
|
|
11
10
|
import { Utils } from '../lib/utils.js';
|
|
12
11
|
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
12
|
+
import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Aligned Dimension Entity Class
|
|
@@ -45,6 +45,8 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
45
45
|
this.points.push(pt14);
|
|
46
46
|
|
|
47
47
|
const op2 = new PromptOptions(Strings.Input.DIMENSION, [Input.Type.POINT]);
|
|
48
|
+
// avoid polar/ortho tracking a line from pt14 while placing the dimension
|
|
49
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
48
50
|
const pt11 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
49
51
|
pt11.sequence = 11;
|
|
50
52
|
this.points.push(pt11);
|
|
@@ -76,6 +78,70 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Get the grip points for interactive editing on the canvas.
|
|
83
|
+
* Pt10 (the dimension line's far end) is derived from Pt11 via getPointsFromSelection() -
|
|
84
|
+
* it must be recomputed, not left stale, when the Pt11 grip moves.
|
|
85
|
+
* @return {Array<{point: Point, apply: Function}>} grips
|
|
86
|
+
*/
|
|
87
|
+
getGrips() {
|
|
88
|
+
const Pt13 = this.getPointBySequence(this.points, 13);
|
|
89
|
+
const Pt14 = this.getPointBySequence(this.points, 14);
|
|
90
|
+
const Pt11 = this.getPointBySequence(this.points, 11);
|
|
91
|
+
|
|
92
|
+
if (!Pt13 || !Pt14 || !Pt11) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const apply = (newPoint) => {
|
|
97
|
+
const tempLine = new Line({ points: [Pt13, Pt14] });
|
|
98
|
+
const angle = this.getProperty(Property.Names.LINEARDIMANGLE);
|
|
99
|
+
return this.constructor.getPointsFromSelection([tempLine], newPoint, angle);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return [{ point: Pt11, apply: apply }];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the points where the extension lines meet the dimension line
|
|
107
|
+
* @return {Array<Point>} [Pt13e, Pt14e]
|
|
108
|
+
*/
|
|
109
|
+
getDimensionLineEndPoints() {
|
|
110
|
+
const Pt10 = this.getPointBySequence(this.points, 10);
|
|
111
|
+
const Pt13 = this.getPointBySequence(this.points, 13);
|
|
112
|
+
const Pt14 = this.getPointBySequence(this.points, 14);
|
|
113
|
+
|
|
114
|
+
if (this.dimType.getBaseDimType() === 0) {
|
|
115
|
+
// Rotated dimension: the dimension line direction is a fixed, user-specified angle
|
|
116
|
+
// (LINEARDIMANGLE) independent of the measured points - project the extension
|
|
117
|
+
// line origin onto that fixed line, so it stays correct at any rotation, not just 0/90/180/270
|
|
118
|
+
const dimLineAngleRad = Utils.degrees2radians(this.getProperty(Property.Names.LINEARDIMANGLE));
|
|
119
|
+
const linePt2 = Pt10.project(dimLineAngleRad, 1);
|
|
120
|
+
return [Pt13.perpendicular(Pt10, linePt2), Pt10];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Aligned dimension: the dimension line is parallel to the measured edge (Pt13 -> Pt14),
|
|
124
|
+
// derived dynamically from the points rather than a fixed angle
|
|
125
|
+
return [Pt13.project(Pt14.angle(Pt10), Pt14.distance(Pt10)), Pt10];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get snap points
|
|
130
|
+
* @param {Point} mousePoint
|
|
131
|
+
* @param {number} delta
|
|
132
|
+
* @return {Array} - array of snap points
|
|
133
|
+
*/
|
|
134
|
+
snaps(mousePoint, delta) {
|
|
135
|
+
const Pt10 = this.getPointBySequence(this.points, 10);
|
|
136
|
+
const Pt13 = this.getPointBySequence(this.points, 13);
|
|
137
|
+
const Pt14 = this.getPointBySequence(this.points, 14);
|
|
138
|
+
|
|
139
|
+
if (!Pt10 || !Pt13 || !Pt14) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this.getDimensionLineEndPoints().map((point) => new SnapPoint(point, SnapPoint.Type.END));
|
|
144
|
+
}
|
|
79
145
|
|
|
80
146
|
/**
|
|
81
147
|
* Build the dimension
|
|
@@ -86,7 +152,6 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
86
152
|
let dimension = 0;
|
|
87
153
|
const entities = [];
|
|
88
154
|
|
|
89
|
-
const Pt10 = this.getPointBySequence(this.points, 10);
|
|
90
155
|
const Pt13 = this.getPointBySequence(this.points, 13);
|
|
91
156
|
const Pt14 = this.getPointBySequence(this.points, 14);
|
|
92
157
|
const Pt11 = this.getPointBySequence(this.points, 11);
|
|
@@ -113,38 +178,8 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
113
178
|
return;
|
|
114
179
|
}
|
|
115
180
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// generate the x and y delta values
|
|
120
|
-
const dx = Pt14.x - Pt13.x;
|
|
121
|
-
const dy = Pt14.y - Pt13.y;
|
|
122
|
-
|
|
123
|
-
// check if Line Pt13 -> P14 is perpendicular to Line Pt14 -> Pt10
|
|
124
|
-
const m1 = (Pt14.y - Pt13.y) / (Pt14.x - Pt13.x);
|
|
125
|
-
const m2 = (Pt14.y - Pt10.y) / (Pt14.x - Pt10.x);
|
|
126
|
-
const perpendicular = Number((m1 * m2).toFixed(1));
|
|
127
|
-
|
|
128
|
-
const isAligned = perpendicular !== -1.0 ? false : true;
|
|
129
|
-
|
|
130
|
-
if (isAligned || Utils.round(dx) === 0 || Utils.round(dy) === 0) {
|
|
131
|
-
Pt13e = Pt13.project(Pt14.angle(Pt10), Pt14.distance(Pt10));
|
|
132
|
-
dimension = Pt13.distance(Pt14);
|
|
133
|
-
} else {
|
|
134
|
-
// Not perpendicular to the selected line: get the primary axis (x or y) and calculate the dimension
|
|
135
|
-
const iX = ((Math.abs(Pt10.x - Pt13.x) + Math.abs(Pt14.x - Pt10.x)) - Math.abs(dx));
|
|
136
|
-
const iY = ((Math.abs(Pt10.y - Pt13.y) + Math.abs(Pt14.y - Pt10.y)) - Math.abs(dy));
|
|
137
|
-
|
|
138
|
-
if (iX >= iY && dy !== 0) {
|
|
139
|
-
Pt13e.x = Pt10.x;
|
|
140
|
-
Pt13e.y = Pt13.y;
|
|
141
|
-
dimension = dy;
|
|
142
|
-
} else if (iX < iY && dx !== 0) {
|
|
143
|
-
Pt13e.x = Pt13.x;
|
|
144
|
-
Pt13e.y = Pt10.y;
|
|
145
|
-
dimension = dx;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
181
|
+
const [Pt13e, Pt14e] = this.getDimensionLineEndPoints();
|
|
182
|
+
dimension = this.dimType.getBaseDimType() === 0 ? Pt13e.distance(Pt14e) : Pt13.distance(Pt14);
|
|
148
183
|
|
|
149
184
|
// ensure dimension is positive
|
|
150
185
|
dimension = Math.abs(dimension);
|
|
@@ -155,9 +190,6 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
155
190
|
const reverseDimLineAngle = Pt14e.angle(Pt13e);
|
|
156
191
|
const midPoint = Pt13e.midPoint(Pt14e);
|
|
157
192
|
|
|
158
|
-
// set the dimension line angle. This is required for rotated dimension dxf definition
|
|
159
|
-
this.setProperty(Property.Names.LINEARDIMANGLE, Utils.radians2degrees(dimLineAngle));
|
|
160
|
-
|
|
161
193
|
// Approximate text width based on height using the formatted dimension value i.e with units, precision and symbols
|
|
162
194
|
const approxTextWidth = Text.getApproximateWidth(this.getDimensionValue(dimension), DIMTXT);
|
|
163
195
|
const approxTextHalfWidth = approxTextWidth * 0.5;
|
|
@@ -221,6 +253,12 @@ export class LinearDimensionBase extends DimensionBase {
|
|
|
221
253
|
break;
|
|
222
254
|
}
|
|
223
255
|
|
|
256
|
+
// A grip-dragged text position overrides the DIMJUST auto-placement above with Pt11 as
|
|
257
|
+
// the anchor - DIMTAD's vertical offset below still applies on top, matching AutoCAD
|
|
258
|
+
if (this.dimType.hasUserPositionedText()) {
|
|
259
|
+
textPosition = Pt11;
|
|
260
|
+
}
|
|
261
|
+
|
|
224
262
|
// Check if the text is aligned with the dimension line
|
|
225
263
|
const textAndDimlineAligned = this.alignedOrOpposite(textRotation, dimLineAngle);
|
|
226
264
|
|
|
@@ -14,7 +14,7 @@ export class RadialDimension extends CircularDimensionBase {
|
|
|
14
14
|
*/
|
|
15
15
|
constructor(data) {
|
|
16
16
|
super(data);
|
|
17
|
-
this.dimType.
|
|
17
|
+
this.dimType.setBaseType(4); // Radial dimension - preserve flags loaded from DXF group 70
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -48,6 +48,31 @@ export class RadialDimension extends CircularDimensionBase {
|
|
|
48
48
|
return points;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Get the grip points for interactive editing on the canvas.
|
|
53
|
+
* Pt15 (the circle attach point) is derived from Pt11 via getPointsFromSelection() -
|
|
54
|
+
* must be recomputed from the unchanged centre (Pt10) and radius, not left stale.
|
|
55
|
+
* @return {Array<{point: Point, apply: Function}>} grips
|
|
56
|
+
*/
|
|
57
|
+
getGrips() {
|
|
58
|
+
const Pt10 = this.getPointBySequence(this.points, 10); // centre
|
|
59
|
+
const Pt15 = this.getPointBySequence(this.points, 15); // point on circle
|
|
60
|
+
const Pt11 = this.getPointBySequence(this.points, 11); // text position
|
|
61
|
+
|
|
62
|
+
if (!Pt10 || !Pt15 || !Pt11) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const radius = Pt10.distance(Pt15);
|
|
67
|
+
|
|
68
|
+
const apply = (newPoint) => {
|
|
69
|
+
const tempEntity = { points: [Pt10], getRadius: () => radius };
|
|
70
|
+
return this.constructor.getPointsFromSelection([tempEntity], newPoint);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return [{ point: Pt11, apply: apply }];
|
|
74
|
+
}
|
|
75
|
+
|
|
51
76
|
/**
|
|
52
77
|
* Write the entity to file in the dxf format
|
|
53
78
|
* @param {DXFFile} file
|
|
@@ -22,7 +22,7 @@ export class RotatedDimension extends LinearDimensionBase {
|
|
|
22
22
|
*/
|
|
23
23
|
constructor(data) {
|
|
24
24
|
super(data);
|
|
25
|
-
this.dimType.
|
|
25
|
+
this.dimType.setBaseType(0); // Rotated dimension - preserve flags loaded from DXF group 70
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -62,6 +62,8 @@ export class RotatedDimension extends LinearDimensionBase {
|
|
|
62
62
|
this.points.push(pt14);
|
|
63
63
|
|
|
64
64
|
const op2 = new PromptOptions(Strings.Input.DIMENSION, [Input.Type.POINT]);
|
|
65
|
+
// avoid polar/ortho tracking a line from pt14 while placing the dimension
|
|
66
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
65
67
|
const pt11 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
66
68
|
pt11.sequence = 11;
|
|
67
69
|
this.points.push(pt11);
|
|
@@ -126,8 +128,9 @@ export class RotatedDimension extends LinearDimensionBase {
|
|
|
126
128
|
const pt11 = DesignCore.Mouse.pointOnScene();
|
|
127
129
|
pt11.sequence = 11;
|
|
128
130
|
const tempLine = new Line({ points: [this.points[0], this.points[1]] });
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
+
const angle = this.getProperty(Property.Names.LINEARDIMANGLE);
|
|
132
|
+
const points = RotatedDimension.getPointsFromSelection([tempLine], pt11, angle);
|
|
133
|
+
DesignCore.Scene.previewEntities.create(this.type, { points: points, dimensionStyle: this.getProperty(Property.Names.DIMENSIONSTYLE), linearDimAngle: angle });
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
|
package/core/entities/entity.js
CHANGED
|
@@ -109,6 +109,15 @@ export class Entity {
|
|
|
109
109
|
this.properties.set(Property.Names.POINTS, value, this);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Get the grip points for interactive editing on the canvas.
|
|
114
|
+
* Overridden by entities that expose editable grips (e.g. DimensionBase).
|
|
115
|
+
* @return {Array<{point: Point, apply: Function}>} grips - apply(newPoint) returns a new points array
|
|
116
|
+
*/
|
|
117
|
+
getGrips() {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
/**
|
|
113
122
|
* Normalise entity-level DXF group codes before construction.
|
|
114
123
|
* Subclasses override this for geometric transformation; they receive data
|
|
@@ -13,6 +13,7 @@ export class SnapPoint {
|
|
|
13
13
|
TANGENT: 'tangent',
|
|
14
14
|
NODE: 'node',
|
|
15
15
|
PERPENDICULAR: 'perpendicular',
|
|
16
|
+
GRIP: 'grip',
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -57,6 +58,14 @@ export class SnapPoint {
|
|
|
57
58
|
new Point(x - size, y + size),
|
|
58
59
|
], { closed: true });
|
|
59
60
|
break;
|
|
61
|
+
case SnapPoint.Type.GRIP: // filled square
|
|
62
|
+
renderer.drawShape([
|
|
63
|
+
new Point(x - size, y - size),
|
|
64
|
+
new Point(x + size, y - size),
|
|
65
|
+
new Point(x + size, y + size),
|
|
66
|
+
new Point(x - size, y + size),
|
|
67
|
+
], { closed: true, fill: true });
|
|
68
|
+
break;
|
|
60
69
|
case SnapPoint.Type.MID: // triangle
|
|
61
70
|
renderer.drawShape([
|
|
62
71
|
new Point(x, y + size),
|
package/core/lib/canvas.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Point } from '../entities/point.js';
|
|
|
5
5
|
import { Input } from './input.js';
|
|
6
6
|
import { Logging } from './logging.js';
|
|
7
7
|
import { PlotOptions } from './plotOptions.js';
|
|
8
|
+
import { SnapPoint } from './auxiliary/snapPoint.js';
|
|
8
9
|
// import { RendererBase } from './renderers/rendererBase.js';
|
|
9
10
|
|
|
10
11
|
import { DesignCore } from '../designCore.js';
|
|
@@ -305,7 +306,9 @@ export class Canvas {
|
|
|
305
306
|
*/
|
|
306
307
|
exportTo(renderer, plotOptions) {
|
|
307
308
|
let area;
|
|
308
|
-
if (plotOptions.plotArea === PlotOptions.Area.
|
|
309
|
+
if (plotOptions.plotArea === PlotOptions.Area.WINDOW) {
|
|
310
|
+
area = new BoundingBox(plotOptions.windowArea.point1, plotOptions.windowArea.point2);
|
|
311
|
+
} else if (plotOptions.plotArea === PlotOptions.Area.DISPLAY) {
|
|
309
312
|
const viewport = this.getSceneOffset();
|
|
310
313
|
area = new BoundingBox(new Point(viewport.xmin, viewport.ymin), new Point(viewport.xmax, viewport.ymax));
|
|
311
314
|
} else {
|
|
@@ -318,6 +321,7 @@ export class Canvas {
|
|
|
318
321
|
pageWidth: plotOptions.pageWidth,
|
|
319
322
|
pageHeight: plotOptions.pageHeight,
|
|
320
323
|
plotScale: plotOptions.plotScale,
|
|
324
|
+
margin: plotOptions.margin,
|
|
321
325
|
});
|
|
322
326
|
if (!matrix) return false;
|
|
323
327
|
|
|
@@ -396,6 +400,11 @@ export class Canvas {
|
|
|
396
400
|
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
397
401
|
this.#paintEntities(selectedEntities, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
|
|
398
402
|
|
|
403
|
+
// Paint grips for selected entities - only when idle (no active command)
|
|
404
|
+
if (DesignCore.Scene.inputManager.activeCommand === undefined) {
|
|
405
|
+
this.#paintGrips(selectedEntities, renderer, scale);
|
|
406
|
+
}
|
|
407
|
+
|
|
399
408
|
// Paint the auxiliary scene items
|
|
400
409
|
// auxiliary items include things like the selection window, snap points etc
|
|
401
410
|
// these items have their own draw routine
|
|
@@ -459,6 +468,22 @@ export class Canvas {
|
|
|
459
468
|
}
|
|
460
469
|
}
|
|
461
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Draw grip markers for every grip exposed by the given entities.
|
|
473
|
+
* @param {Array} entities - selected entities to draw grips for
|
|
474
|
+
* @param {Object} renderer - renderer instance
|
|
475
|
+
* @param {number} scale - current canvas scale
|
|
476
|
+
*/
|
|
477
|
+
#paintGrips(entities, renderer, scale) {
|
|
478
|
+
for (const entity of entities) {
|
|
479
|
+
for (const grip of entity.getGrips()) {
|
|
480
|
+
renderer.save();
|
|
481
|
+
new SnapPoint(grip.point, SnapPoint.Type.GRIP).draw(renderer, scale);
|
|
482
|
+
renderer.restore();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
462
487
|
/**
|
|
463
488
|
* Set the scene context on the renderer before drawing an entity.
|
|
464
489
|
* @param {Object} item - entity to draw
|
|
@@ -12,7 +12,7 @@ import { Text } from '../entities/text.js';
|
|
|
12
12
|
import { Block } from '../tables/block.js';
|
|
13
13
|
import { Insert } from '../entities/insert.js';
|
|
14
14
|
import { Hatch } from '../entities/hatch.js';
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
import { Dimension } from '../dimensions/dimension.js';
|
|
17
17
|
import { AlignedDimension } from '../dimensions/alignedDimension.js';
|
|
18
18
|
import { AngularDimension } from '../dimensions/angularDimension.js';
|
|
@@ -20,7 +20,7 @@ import { LinearDimension } from '../dimensions/linearDimension.js';
|
|
|
20
20
|
import { DiametricDimension } from '../dimensions/diametricDimension.js';
|
|
21
21
|
import { RadialDimension } from '../dimensions/radialDimension.js';
|
|
22
22
|
import { RotatedDimension } from '../dimensions/rotatedDimension.js';
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
// import { Point } from "../entities/point.js"
|
|
26
26
|
|
|
@@ -43,6 +43,9 @@ import { Purge } from '../tools/purge.js';
|
|
|
43
43
|
import { Pan } from '../tools/pan.js';
|
|
44
44
|
import { Zoom } from '../tools/zoom.js';
|
|
45
45
|
|
|
46
|
+
// Internal tools (not shown in any toolbar, invoked programmatically)
|
|
47
|
+
import { WindowPick } from '../tools/windowPick.js';
|
|
48
|
+
|
|
46
49
|
// Clipboard tools
|
|
47
50
|
import { Copybase } from '../tools/copybase.js';
|
|
48
51
|
import { Copyclip } from '../tools/copyclip.js';
|
|
@@ -67,7 +70,7 @@ const classes = {
|
|
|
67
70
|
// Spline,
|
|
68
71
|
ArcAlignedText,
|
|
69
72
|
Text,
|
|
70
|
-
|
|
73
|
+
|
|
71
74
|
Dimension,
|
|
72
75
|
AngularDimension,
|
|
73
76
|
AlignedDimension,
|
|
@@ -75,7 +78,7 @@ const classes = {
|
|
|
75
78
|
LinearDimension,
|
|
76
79
|
RadialDimension,
|
|
77
80
|
RotatedDimension,
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
Block,
|
|
80
83
|
Insert,
|
|
81
84
|
Hatch,
|
|
@@ -99,6 +102,8 @@ const classes = {
|
|
|
99
102
|
Zoom,
|
|
100
103
|
Pan,
|
|
101
104
|
|
|
105
|
+
WindowPick,
|
|
106
|
+
|
|
102
107
|
Copybase,
|
|
103
108
|
Copyclip,
|
|
104
109
|
Cutclip,
|
package/core/lib/dxf/dxf.js
CHANGED
|
@@ -388,8 +388,14 @@ export class DXF {
|
|
|
388
388
|
|
|
389
389
|
const command = entity[0];
|
|
390
390
|
if (DesignCore.CommandManager.isCommand(command)) {
|
|
391
|
-
|
|
392
|
-
|
|
391
|
+
try {
|
|
392
|
+
const newEntity = DesignCore.CommandManager.createNew(command, entity);
|
|
393
|
+
DesignCore.Scene.entities.add(newEntity);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
// a recognised command can still fail to build (e.g. unsupported dimension subtype) - skip it, don't abort the rest of the file
|
|
396
|
+
Logging.instance.warn(`${Strings.Message.UNKNOWNCOMMAND} ${command}: ${err}`);
|
|
397
|
+
this.unsupportedElements = true;
|
|
398
|
+
}
|
|
393
399
|
} else {
|
|
394
400
|
Logging.instance.warn(`${Strings.Message.UNKNOWNCOMMAND} ${command}`);
|
|
395
401
|
this.unsupportedElements = true;
|
package/core/lib/inputManager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tool } from '../tools/tool.js';
|
|
2
|
+
import { GripStretch } from '../tools/gripStretch.js';
|
|
2
3
|
import { Snapping } from './snapping.js';
|
|
3
4
|
|
|
4
5
|
import { DesignCore } from '../designCore.js';
|
|
@@ -39,6 +40,7 @@ export class InputManager {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
this.promptOption = undefined;
|
|
43
|
+
DesignCore.Mouse.reset();
|
|
42
44
|
DesignCore.Scene.reset();
|
|
43
45
|
DesignCore.Canvas.setCursorForInputTypes([]);
|
|
44
46
|
}
|
|
@@ -130,7 +132,9 @@ export class InputManager {
|
|
|
130
132
|
* @param {string} command - command name or shortcut
|
|
131
133
|
*/
|
|
132
134
|
startCommand(command) {
|
|
133
|
-
if (this.activeCommand
|
|
135
|
+
if (this.activeCommand instanceof Tool) {
|
|
136
|
+
this.activeCommand.cancel();
|
|
137
|
+
} else if (this.activeCommand !== undefined) {
|
|
134
138
|
this.reset();
|
|
135
139
|
}
|
|
136
140
|
|
|
@@ -195,6 +199,17 @@ export class InputManager {
|
|
|
195
199
|
* @param {Point} point
|
|
196
200
|
*/
|
|
197
201
|
onLeftClick(point) {
|
|
202
|
+
// Grip hit-test takes priority over normal entity selection when idle, so clicking a grip
|
|
203
|
+
// starts GripStretch (which requests the stretch point itself) instead of reselecting it
|
|
204
|
+
if (this.activeCommand === undefined) {
|
|
205
|
+
const grabbedGrip = DesignCore.Scene.selectionManager.findGripUnderMouse(DesignCore.Mouse.pointOnScene());
|
|
206
|
+
if (grabbedGrip) {
|
|
207
|
+
this.activeCommand = new GripStretch(grabbedGrip.entityIndex, grabbedGrip.grip);
|
|
208
|
+
this.activeCommand.execute();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
198
213
|
// left clicks are used for selection of entities and points
|
|
199
214
|
// Selection of entities can only occur if there is no active snap available
|
|
200
215
|
// Selection of entities can only occur if the mouse is over an entity
|
|
@@ -281,8 +296,14 @@ export class InputManager {
|
|
|
281
296
|
this.activeCommand.preview();
|
|
282
297
|
}
|
|
283
298
|
|
|
284
|
-
//
|
|
285
|
-
|
|
299
|
+
// a command preview can coexist with an active selection prompt (e.g. a dimension
|
|
300
|
+
// previewing a point-based result while still accepting a second entity selection)
|
|
301
|
+
const canSelectEntity = this.promptOption?.types.includes(Input.Type.SINGLESELECTION) ||
|
|
302
|
+
this.promptOption?.types.includes(Input.Type.SELECTIONSET);
|
|
303
|
+
|
|
304
|
+
// Only show hover glow when no command preview is occupying previewEntities,
|
|
305
|
+
// unless the active prompt also accepts an entity selection
|
|
306
|
+
if (!snapped && (DesignCore.Scene.previewEntities.count() === 0 || canSelectEntity)) {
|
|
286
307
|
this.highlightEntityUnderMouse();
|
|
287
308
|
}
|
|
288
309
|
|
package/core/lib/mouse.js
CHANGED
|
@@ -15,6 +15,16 @@ export class Mouse {
|
|
|
15
15
|
this.lastButton = 0; // last button for double click check
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Clear button state - guards against a stuck button when a mouseUp
|
|
20
|
+
* never reaches the canvas (e.g. focus stolen by another window mid-click)
|
|
21
|
+
*/
|
|
22
|
+
reset() {
|
|
23
|
+
this.buttonOneDown = false;
|
|
24
|
+
this.buttonTwoDown = false;
|
|
25
|
+
this.buttonThreeDown = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Returns the point on the design canvas
|
|
20
30
|
* @return {Point}
|