@design-core/core 51.2.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/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 +22 -0
- package/core/lib/commandManager.js +4 -4
- package/core/lib/dxf/dxf.js +8 -2
- package/core/lib/inputManager.js +23 -3
- 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 +1 -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/cspell.json +3 -0
- package/package.json +1 -1
- 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/userInputIntegration.reference.dxf +3 -9
- package/test/lib/inputManager.test.js +316 -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
|
@@ -21,7 +21,7 @@ export class AlignedDimension extends LinearDimensionBase {
|
|
|
21
21
|
*/
|
|
22
22
|
constructor(data) {
|
|
23
23
|
super(data);
|
|
24
|
-
this.dimType.
|
|
24
|
+
this.dimType.setBaseType(1); // Aligned dimension - preserve flags loaded from DXF group 70
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -57,6 +57,8 @@ export class AlignedDimension extends LinearDimensionBase {
|
|
|
57
57
|
this.points.push(pt14);
|
|
58
58
|
|
|
59
59
|
const op2 = new PromptOptions(Strings.Input.DIMENSION, [Input.Type.POINT]);
|
|
60
|
+
// avoid polar/ortho tracking a line from pt14 while placing the dimension
|
|
61
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
60
62
|
const pt11 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
61
63
|
pt11.sequence = 11;
|
|
62
64
|
this.points.push(pt11);
|
|
@@ -10,6 +10,7 @@ import { DXFFile } from '../lib/dxf/dxfFile.js';
|
|
|
10
10
|
import { DimensionBase } from './dimensionBase.js';
|
|
11
11
|
import { Intersection } from '../lib/intersect.js';
|
|
12
12
|
import { Point } from '../entities/point.js';
|
|
13
|
+
import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
|
|
13
14
|
|
|
14
15
|
import { Text } from '../entities/text.js'; // used to debug the dimension points
|
|
15
16
|
|
|
@@ -27,7 +28,7 @@ export class AngularDimension extends DimensionBase {
|
|
|
27
28
|
*/
|
|
28
29
|
constructor(data) {
|
|
29
30
|
super(data);
|
|
30
|
-
this.dimType.
|
|
31
|
+
this.dimType.setBaseType(2); // Angular dimension - preserve flags loaded from DXF group 70
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -116,6 +117,8 @@ export class AngularDimension extends DimensionBase {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
const op2 = new PromptOptions(Strings.Input.START, [Input.Type.POINT]);
|
|
120
|
+
// avoid polar/ortho tracking a line from the last defining point while placing the dimension
|
|
121
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
119
122
|
const Pt11 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
120
123
|
|
|
121
124
|
const tempLineOne = new Line({ points: [Pt15, Pt10] });
|
|
@@ -151,6 +154,32 @@ export class AngularDimension extends DimensionBase {
|
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Get the grip points for interactive editing on the canvas.
|
|
159
|
+
* Pt16 (the dimension arc's radial position) is derived from Pt11 via getPointsFromSelection() -
|
|
160
|
+
* must be recomputed from the two unchanged defining lines, not left stale.
|
|
161
|
+
* @return {Array<{point: Point, apply: Function}>} grips
|
|
162
|
+
*/
|
|
163
|
+
getGrips() {
|
|
164
|
+
const Pt15 = this.getPointBySequence(this.points, 15);
|
|
165
|
+
const Pt10 = this.getPointBySequence(this.points, 10);
|
|
166
|
+
const Pt13 = this.getPointBySequence(this.points, 13);
|
|
167
|
+
const Pt14 = this.getPointBySequence(this.points, 14);
|
|
168
|
+
const Pt11 = this.getPointBySequence(this.points, 11);
|
|
169
|
+
|
|
170
|
+
if (!Pt15 || !Pt10 || !Pt13 || !Pt14 || !Pt11) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const apply = (newPoint) => {
|
|
175
|
+
const tempLineOne = new Line({ points: [Pt15, Pt10] });
|
|
176
|
+
const tempLineTwo = new Line({ points: [Pt13, Pt14] });
|
|
177
|
+
return AngularDimension.getPointsFromSelection([tempLineOne, tempLineTwo], newPoint);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return [{ point: Pt11, apply: apply }];
|
|
181
|
+
}
|
|
182
|
+
|
|
154
183
|
/**
|
|
155
184
|
* Get sequenced points from user selection
|
|
156
185
|
* @param {any} entities
|
|
@@ -298,6 +327,79 @@ export class AngularDimension extends DimensionBase {
|
|
|
298
327
|
return sortedPoints;
|
|
299
328
|
}
|
|
300
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Get the dimension arc end points (arrow positions) and the extension line extents
|
|
332
|
+
* @param {Point} Pt10 - sorted point 10
|
|
333
|
+
* @param {Point} Pt13 - sorted point 13
|
|
334
|
+
* @param {Point} Pt14 - sorted point 14
|
|
335
|
+
* @param {Point} Pt15 - sorted point 15
|
|
336
|
+
* @param {Point} Pt16 - dimension arc position
|
|
337
|
+
* @return {Object} {intersectPt, radius, arrow1pos, arrow2pos, line1Extents, line2Extents}
|
|
338
|
+
*/
|
|
339
|
+
getDimensionArcGeometry(Pt10, Pt13, Pt14, Pt15, Pt16) {
|
|
340
|
+
const intersectPt = Intersection.intersectSegmentSegment(Pt15, Pt10, Pt13, Pt14, false, true).points[0];
|
|
341
|
+
const radius = intersectPt.distance(Pt16);
|
|
342
|
+
|
|
343
|
+
const quadOneStart = Pt14;
|
|
344
|
+
const quadOneEnd = Pt10;
|
|
345
|
+
const quadTwoStart = Pt14.rotate(intersectPt, Math.PI);
|
|
346
|
+
const quadTwoEnd = Pt10.rotate(intersectPt, Math.PI);
|
|
347
|
+
let arrow1pos = intersectPt;
|
|
348
|
+
let arrow2pos = intersectPt;
|
|
349
|
+
let line1Extents = Pt14;
|
|
350
|
+
let line2Extents = Pt10;
|
|
351
|
+
if (Pt16.isOnArc(quadOneStart, quadOneEnd, intersectPt, 1)) {
|
|
352
|
+
arrow1pos = intersectPt.project(intersectPt.angle(quadOneStart), radius);
|
|
353
|
+
arrow2pos = intersectPt.project(intersectPt.angle(quadOneEnd), radius);
|
|
354
|
+
line1Extents = Pt14;
|
|
355
|
+
line2Extents = Pt10;
|
|
356
|
+
} else if (Pt16.isOnArc(quadOneEnd, quadTwoStart, intersectPt, 1)) {
|
|
357
|
+
arrow1pos = intersectPt.project(intersectPt.angle(quadOneEnd), radius);
|
|
358
|
+
arrow2pos = intersectPt.project(intersectPt.angle(quadTwoStart), radius);
|
|
359
|
+
line1Extents = Pt10;
|
|
360
|
+
line2Extents = Pt13;
|
|
361
|
+
} else if (Pt16.isOnArc(quadTwoStart, quadTwoEnd, intersectPt, 1)) {
|
|
362
|
+
arrow1pos = intersectPt.project(intersectPt.angle(quadTwoStart), radius);
|
|
363
|
+
arrow2pos = intersectPt.project(intersectPt.angle(quadTwoEnd), radius);
|
|
364
|
+
line1Extents = Pt13;
|
|
365
|
+
line2Extents = Pt15;
|
|
366
|
+
} else if (Pt16.isOnArc(quadTwoEnd, quadOneStart, intersectPt, 1)) {
|
|
367
|
+
arrow1pos = intersectPt.project(intersectPt.angle(quadTwoEnd), radius);
|
|
368
|
+
arrow2pos = intersectPt.project(intersectPt.angle(quadOneStart), radius);
|
|
369
|
+
line1Extents = Pt15;
|
|
370
|
+
line2Extents = Pt14;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { intersectPt, radius, arrow1pos, arrow2pos, line1Extents, line2Extents };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get snap points
|
|
378
|
+
* @param {Point} mousePoint
|
|
379
|
+
* @param {number} delta
|
|
380
|
+
* @return {Array} - array of snap points
|
|
381
|
+
*/
|
|
382
|
+
snaps(mousePoint, delta) {
|
|
383
|
+
if (!this.getPointBySequence(this.points, 16) || this.points.length < 6) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const sortedPoints = this.sortDimensionPoints();
|
|
388
|
+
const Pt10 = this.getPointBySequence(sortedPoints, 10);
|
|
389
|
+
const Pt13 = this.getPointBySequence(sortedPoints, 13);
|
|
390
|
+
const Pt14 = this.getPointBySequence(sortedPoints, 14);
|
|
391
|
+
const Pt15 = this.getPointBySequence(sortedPoints, 15);
|
|
392
|
+
const Pt16 = this.getPointBySequence(this.points, 16);
|
|
393
|
+
|
|
394
|
+
const { radius, arrow1pos, arrow2pos } = this.getDimensionArcGeometry(Pt10, Pt13, Pt14, Pt15, Pt16);
|
|
395
|
+
|
|
396
|
+
if (radius === 0) {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return [arrow1pos, arrow2pos].map((point) => new SnapPoint(point, SnapPoint.Type.END));
|
|
401
|
+
}
|
|
402
|
+
|
|
301
403
|
/**
|
|
302
404
|
* Build the dimension
|
|
303
405
|
* @param {Object} style
|
|
@@ -337,36 +439,7 @@ export class AngularDimension extends DimensionBase {
|
|
|
337
439
|
return;
|
|
338
440
|
}
|
|
339
441
|
|
|
340
|
-
|
|
341
|
-
const quadOneStart = Pt14;
|
|
342
|
-
const quadOneEnd = Pt10;
|
|
343
|
-
const quadTwoStart = Pt14.rotate(intersectPt, Math.PI);
|
|
344
|
-
const quadTwoEnd = Pt10.rotate(intersectPt, Math.PI);
|
|
345
|
-
let arrow1pos = intersectPt;
|
|
346
|
-
let arrow2pos = intersectPt;
|
|
347
|
-
let line1Extents = Pt14;
|
|
348
|
-
let line2Extents = Pt10;
|
|
349
|
-
if (Pt16.isOnArc(quadOneStart, quadOneEnd, intersectPt, 1)) {
|
|
350
|
-
arrow1pos = intersectPt.project(intersectPt.angle(quadOneStart), radius);
|
|
351
|
-
arrow2pos = intersectPt.project(intersectPt.angle(quadOneEnd), radius);
|
|
352
|
-
line1Extents = Pt14;
|
|
353
|
-
line2Extents = Pt10;
|
|
354
|
-
} else if (Pt16.isOnArc(quadOneEnd, quadTwoStart, intersectPt, 1)) {
|
|
355
|
-
arrow1pos = intersectPt.project(intersectPt.angle(quadOneEnd), radius);
|
|
356
|
-
arrow2pos = intersectPt.project(intersectPt.angle(quadTwoStart), radius);
|
|
357
|
-
line1Extents = Pt10;
|
|
358
|
-
line2Extents = Pt13;
|
|
359
|
-
} else if (Pt16.isOnArc(quadTwoStart, quadTwoEnd, intersectPt, 1)) {
|
|
360
|
-
arrow1pos = intersectPt.project(intersectPt.angle(quadTwoStart), radius);
|
|
361
|
-
arrow2pos = intersectPt.project(intersectPt.angle(quadTwoEnd), radius);
|
|
362
|
-
line1Extents = Pt13;
|
|
363
|
-
line2Extents = Pt15;
|
|
364
|
-
} else if (Pt16.isOnArc(quadTwoEnd, quadOneStart, intersectPt, 1)) {
|
|
365
|
-
arrow1pos = intersectPt.project(intersectPt.angle(quadTwoEnd), radius);
|
|
366
|
-
arrow2pos = intersectPt.project(intersectPt.angle(quadOneStart), radius);
|
|
367
|
-
line1Extents = Pt15;
|
|
368
|
-
line2Extents = Pt14;
|
|
369
|
-
}
|
|
442
|
+
const { arrow1pos, arrow2pos, line1Extents, line2Extents } = this.getDimensionArcGeometry(Pt10, Pt13, Pt14, Pt15, Pt16);
|
|
370
443
|
|
|
371
444
|
const line1Angle = intersectPt.angle(arrow1pos);
|
|
372
445
|
const line2Angle = intersectPt.angle(arrow2pos);
|
|
@@ -389,7 +462,7 @@ export class AngularDimension extends DimensionBase {
|
|
|
389
462
|
let textPosition = arcMiddle;
|
|
390
463
|
let textRotation = this.getTextDirection(horizontalText ? 0 : arrow1pos.angle(arrow2pos));
|
|
391
464
|
let textAboveDirection = textRotation + Math.PI / 2;
|
|
392
|
-
|
|
465
|
+
const textAboveDistance = DIMTXT * 0.5 + DIMGAP;
|
|
393
466
|
const outsideOffsetDirection = Pt11.distance(arrow1pos) < Pt11.distance(arrow2pos) ? -1 : 1;
|
|
394
467
|
const textPositionOffsetAngle = arrowRadians * 3;
|
|
395
468
|
const internalPositionAdjustmentAngle = dimension * 0.5 > textPositionOffsetAngle ? dimension * 0.5 - textPositionOffsetAngle : 0;
|
|
@@ -428,10 +501,19 @@ export class AngularDimension extends DimensionBase {
|
|
|
428
501
|
break;
|
|
429
502
|
}
|
|
430
503
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
504
|
+
// A grip-dragged text position overrides the DIMJUST auto-placement above with Pt11 as
|
|
505
|
+
// the anchor - DIMTAD's vertical offset below still applies on top, matching AutoCAD.
|
|
506
|
+
// Rotation must be recomputed from Pt11's own tangent too, otherwise it's stuck at the
|
|
507
|
+
// DIMJUST-computed (pre-drag) rotation, which is wrong once the text has moved elsewhere
|
|
508
|
+
if (this.dimType.hasUserPositionedText()) {
|
|
509
|
+
textPosition = Pt11;
|
|
510
|
+
textRotation = this.getTextDirection(horizontalText ? 0 : (intersectPt.angle(Pt11) + Math.PI / 2));
|
|
511
|
+
textAboveDirection = textRotation + Math.PI / 2;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// DIMTAD applies unconditionally (matching CircularDimensionBase) - forced-horizontal text
|
|
515
|
+
// (DIMTIH/DIMTOH) rarely aligns with the arc's tangent, so gating the offset on that
|
|
516
|
+
// alignment suppressed DIMTAD for nearly every angular dimension
|
|
435
517
|
switch (DIMTAD) {
|
|
436
518
|
case 1:
|
|
437
519
|
case 2:
|
|
@@ -5,7 +5,6 @@ import { PolylineBase } from '../entities/polylineBase.js';
|
|
|
5
5
|
import { Line } from '../entities/line.js';
|
|
6
6
|
import { Text } from '../entities/text.js';
|
|
7
7
|
import { Point } from '../entities/point.js';
|
|
8
|
-
import { Intersection } from '../lib/intersect.js';
|
|
9
8
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
10
9
|
import { Logging } from '../lib/logging.js';
|
|
11
10
|
import { DimensionBase } from './dimensionBase.js';
|
|
@@ -70,6 +69,8 @@ export class CircularDimensionBase extends DimensionBase {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
const op1 = new PromptOptions(Strings.Input.DIMENSION, [Input.Type.POINT]);
|
|
72
|
+
// avoid polar/ortho tracking a line from the last defining point while placing the dimension
|
|
73
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
73
74
|
const Pt11 = await DesignCore.Scene.inputManager.requestInput(op1);
|
|
74
75
|
Pt11.sequence = 11;
|
|
75
76
|
this.points.push(Pt11);
|
|
@@ -130,15 +131,9 @@ export class CircularDimensionBase extends DimensionBase {
|
|
|
130
131
|
// Ensure points are aligned Pt10 > Pt15 > Pt11
|
|
131
132
|
// This resets the points to a known state to allow application of the dimstyle
|
|
132
133
|
if (Pt15.isOnLine(Pt10, Pt11) === false) {
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const intersect = Intersection.intersectSegmentSegment(line1.start, line1.end, line2.start, line2.end, false, true);
|
|
137
|
-
// Reset Pt11 - This should be the same as the originally selected Pt11
|
|
138
|
-
// Pt11 position can be changed depending on the dimstyle
|
|
139
|
-
if (intersect.points[0]) {
|
|
140
|
-
Pt11 = intersect.points[0];
|
|
141
|
-
}
|
|
134
|
+
// Snap Pt11 onto the (extended) dimension line through Pt10/Pt15 via perpendicular
|
|
135
|
+
// projection - rotation-invariant, unlike a world-axis-aligned ray intersection
|
|
136
|
+
Pt11 = Pt11.perpendicular(Pt10, Pt15);
|
|
142
137
|
}
|
|
143
138
|
|
|
144
139
|
// set a minimum position for the text
|
|
@@ -216,13 +211,38 @@ export class CircularDimensionBase extends DimensionBase {
|
|
|
216
211
|
if (DIMTAD !== 0 && textAndDimlineAligned) {
|
|
217
212
|
lineLength = lineLength + approxTextWidth + DIMTXT; // Add text offset to line length
|
|
218
213
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
214
|
+
const isDiametric = this.dimType.getBaseDimType() === 3;
|
|
215
|
+
// inside diametric dimensions start at Pt15 so the arrow stays connected to the text
|
|
216
|
+
const startPoint = isDiametric ? (isInside ? Pt15 : Pt10) : (DIMTOFL ? Pt10 : Pt15);
|
|
217
|
+
let endPoint = Pt15.project(Pt10.angle(Pt15), isInside ? -lineLength : lineLength);
|
|
218
|
+
const textOnLine = DIMTAD === 0 && textAndDimlineAligned;
|
|
219
|
+
const insideDiametric = isDiametric && isInside;
|
|
220
|
+
|
|
221
|
+
// text is not on the dimension line, so the line continues to the opposite arrow
|
|
222
|
+
if (insideDiametric && !textOnLine) {
|
|
223
|
+
endPoint = Pt10;
|
|
224
|
+
}
|
|
225
|
+
|
|
221
226
|
const line = new Line({ points: [startPoint, endPoint] });
|
|
222
227
|
const arrowDirection = isInside ? Pt15.angle(Pt10) : Pt10.angle(Pt15);
|
|
223
228
|
const arrowHead = this.getArrowHead(Pt15, arrowDirection);
|
|
224
229
|
entities.push(line, arrowHead);
|
|
225
230
|
|
|
231
|
+
if (insideDiametric && textOnLine) {
|
|
232
|
+
// opposite side segment, starting after the text and its gap
|
|
233
|
+
const dirToPt10 = Pt15.angle(Pt10);
|
|
234
|
+
const oppositeStart = textPosition.project(dirToPt10, approxTextWidth * 0.5 + DIMGAP);
|
|
235
|
+
if (Pt15.distance(oppositeStart) < Pt15.distance(Pt10)) {
|
|
236
|
+
entities.push(new Line({ points: [oppositeStart, Pt10] }));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (isDiametric) {
|
|
241
|
+
// diametric dimensions have an arrow at both ends of the diameter
|
|
242
|
+
const oppositeDirection = isInside ? Pt10.angle(Pt15) : Pt15.angle(Pt10);
|
|
243
|
+
entities.push(this.getArrowHead(Pt10, oppositeDirection));
|
|
244
|
+
}
|
|
245
|
+
|
|
226
246
|
// Get the dimension text using the value, position and rotation
|
|
227
247
|
const text = this.getDimensionText(dimension, textPosition, textRotation);
|
|
228
248
|
entities.push(text);
|
|
@@ -13,7 +13,7 @@ export class DiametricDimension extends CircularDimensionBase {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor(data) {
|
|
15
15
|
super(data);
|
|
16
|
-
this.dimType.
|
|
16
|
+
this.dimType.setBaseType(3); // Diametric dimension - preserve flags loaded from DXF group 70
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -52,6 +52,34 @@ export class DiametricDimension extends CircularDimensionBase {
|
|
|
52
52
|
return points;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Get the grip points for interactive editing on the canvas.
|
|
57
|
+
* Pt10/Pt15 are the two diametrically-opposite circle attach points (NOT the centre -
|
|
58
|
+
* unlike RadialDimension, getPointsFromSelection() reassigns both when Pt11 moves), derived
|
|
59
|
+
* from Pt11 via getPointsFromSelection() - must be recomputed from the unchanged centre/radius
|
|
60
|
+
* (reconstructed as their midpoint/half-distance), not left stale.
|
|
61
|
+
* @return {Array<{point: Point, apply: Function}>} grips
|
|
62
|
+
*/
|
|
63
|
+
getGrips() {
|
|
64
|
+
const Pt10 = this.getPointBySequence(this.points, 10); // circle attach point
|
|
65
|
+
const Pt15 = this.getPointBySequence(this.points, 15); // opposite circle attach point
|
|
66
|
+
const Pt11 = this.getPointBySequence(this.points, 11); // text position
|
|
67
|
+
|
|
68
|
+
if (!Pt10 || !Pt15 || !Pt11) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const centre = Pt10.midPoint(Pt15);
|
|
73
|
+
const radius = centre.distance(Pt10);
|
|
74
|
+
|
|
75
|
+
const apply = (newPoint) => {
|
|
76
|
+
const tempEntity = { points: [centre], getRadius: () => radius };
|
|
77
|
+
return this.constructor.getPointsFromSelection([tempEntity], newPoint);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return [{ point: Pt11, apply: apply }];
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
/**
|
|
56
84
|
* Write the entity to file in the dxf format
|
|
57
85
|
* @param {DXFFile} file
|
|
@@ -54,7 +54,14 @@ export class Dimension extends DimensionBase {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
if (data) {
|
|
57
|
-
const
|
|
57
|
+
const DimensionType = this.dimensionMap[DimType.getBaseType(this.dimType.getBaseDimType())];
|
|
58
|
+
|
|
59
|
+
if (!DimensionType) {
|
|
60
|
+
// dimType 5 (Angular 3 point) and 6 (Ordinate) are valid DXF types but not yet implemented
|
|
61
|
+
throw new Error(`${this.type} - ${Strings.Error.INVALIDTYPE}: ${this.dimType.getBaseDimType()}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const entity = new DimensionType(data);
|
|
58
65
|
|
|
59
66
|
// find the block linked to this dimension
|
|
60
67
|
const linkedBlockIndex = DesignCore.Scene.entities.find('BLOCK', 'name', data[2]);
|
|
@@ -100,6 +107,8 @@ export class Dimension extends DimensionBase {
|
|
|
100
107
|
this.dimType.setDimType(1);
|
|
101
108
|
// select a second point to define the dimension
|
|
102
109
|
const op1 = new PromptOptions(Strings.Input.END, [Input.Type.POINT]);
|
|
110
|
+
// avoid polar/ortho tracking a line from the first point while picking the second
|
|
111
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
103
112
|
const pt14 = await DesignCore.Scene.inputManager.requestInput(op1);
|
|
104
113
|
if (pt14 === undefined) return;
|
|
105
114
|
// Create a temporary line using the selected points
|
|
@@ -172,6 +181,8 @@ export class Dimension extends DimensionBase {
|
|
|
172
181
|
op2 = new PromptOptions(`${Strings.Input.DIMENSION}`, [Input.Type.POINT], options);
|
|
173
182
|
}
|
|
174
183
|
|
|
184
|
+
// avoid polar/ortho tracking a line from the last defining point while placing the dimension
|
|
185
|
+
DesignCore.Scene.inputManager.inputPoint = null;
|
|
175
186
|
const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
176
187
|
if (input2 === undefined) return;
|
|
177
188
|
|
|
@@ -187,10 +198,16 @@ export class Dimension extends DimensionBase {
|
|
|
187
198
|
const Pt14 = this.selectedEntities[0].points[1];
|
|
188
199
|
const linearDimTypeNumber = this.getLinearDimensionType(Pt13, Pt14, Pt11);
|
|
189
200
|
this.dimType.setDimType(linearDimTypeNumber);
|
|
201
|
+
|
|
202
|
+
// Rotated dimensions use a fixed angle - choose horizontal or vertical based on the pick point
|
|
203
|
+
if (linearDimTypeNumber === 0) {
|
|
204
|
+
this.setProperty(Property.Names.LINEARDIMANGLE, this.getRotatedDimensionAngle(Pt13, Pt14, Pt11));
|
|
205
|
+
}
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
const dimensionType = this.dimensionMap[this.dimType.getBaseDimType()]; // TODO: use this.dimensionMap.name?
|
|
193
|
-
this.
|
|
209
|
+
const angle = this.getProperty(Property.Names.LINEARDIMANGLE);
|
|
210
|
+
this.points.push(...dimensionType.getPointsFromSelection(this.selectedEntities, Pt11, angle));
|
|
194
211
|
}
|
|
195
212
|
}
|
|
196
213
|
|
|
@@ -246,7 +263,15 @@ export class Dimension extends DimensionBase {
|
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
|
|
249
|
-
|
|
266
|
+
// Dimension is a router entity with no buildDimension() of its own -
|
|
267
|
+
// commit an instance of the resolved subtype (e.g. RotatedDimension), not this
|
|
268
|
+
const DimensionType = this.dimensionMap[this.dimType.getBaseDimType()];
|
|
269
|
+
const entity = new DimensionType({
|
|
270
|
+
points: this.points,
|
|
271
|
+
dimensionStyle: this.getProperty(Property.Names.DIMENSIONSTYLE),
|
|
272
|
+
linearDimAngle: this.getProperty(Property.Names.LINEARDIMANGLE),
|
|
273
|
+
});
|
|
274
|
+
DesignCore.Scene.inputManager.executeCommand(entity);
|
|
250
275
|
} catch (err) {
|
|
251
276
|
Logging.instance.error(`${this.type} - ${err}`);
|
|
252
277
|
}
|
|
@@ -276,6 +301,28 @@ export class Dimension extends DimensionBase {
|
|
|
276
301
|
}
|
|
277
302
|
}
|
|
278
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Determine the fixed dimension line angle for a Rotated dimension (horizontal or vertical)
|
|
306
|
+
* @param {Point} Pt13 - start point
|
|
307
|
+
* @param {Point} Pt14 - end point
|
|
308
|
+
* @param {Point} Pt11 - pick point used to choose the axis
|
|
309
|
+
* @return {number} angle in degrees - 0 = horizontal, 90 = vertical
|
|
310
|
+
*/
|
|
311
|
+
getRotatedDimensionAngle(Pt13, Pt14, Pt11) {
|
|
312
|
+
const dx = Pt14.x - Pt13.x;
|
|
313
|
+
const dy = Pt14.y - Pt13.y;
|
|
314
|
+
|
|
315
|
+
// axis-aligned edges only have one non-degenerate orientation
|
|
316
|
+
if (Utils.round(dx) === 0) return 90;
|
|
317
|
+
if (Utils.round(dy) === 0) return 0;
|
|
318
|
+
|
|
319
|
+
// Diagonal edge: pick the axis the pick point deviates from the edge the most along
|
|
320
|
+
const iX = (Math.abs(Pt11.x - Pt13.x) + Math.abs(Pt14.x - Pt11.x)) - Math.abs(dx);
|
|
321
|
+
const iY = (Math.abs(Pt11.y - Pt13.y) + Math.abs(Pt14.y - Pt11.y)) - Math.abs(dy);
|
|
322
|
+
|
|
323
|
+
return iX >= iY ? 90 : 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
/**
|
|
280
327
|
* Preview the entity during creation
|
|
281
328
|
*/
|
|
@@ -286,20 +333,26 @@ export class Dimension extends DimensionBase {
|
|
|
286
333
|
const Pt11 = DesignCore.Mouse.pointOnScene();
|
|
287
334
|
Pt11.sequence = 11;
|
|
288
335
|
|
|
336
|
+
let angle;
|
|
289
337
|
if (dimTypeNumber === 0 || dimTypeNumber === 1) {
|
|
290
338
|
// for linear dimensions, determine if aligned or rotated based on mouse position
|
|
291
339
|
const Pt13 = this.selectedEntities[0].points[0];
|
|
292
340
|
const Pt14 = this.selectedEntities[0].points[1];
|
|
293
341
|
dimTypeNumber = this.getLinearDimensionType(Pt13, Pt14, Pt11);
|
|
342
|
+
|
|
343
|
+
// Rotated dimensions use a fixed angle - choose horizontal or vertical based on the pick point
|
|
344
|
+
if (dimTypeNumber === 0) {
|
|
345
|
+
angle = this.getRotatedDimensionAngle(Pt13, Pt14, Pt11);
|
|
346
|
+
}
|
|
294
347
|
}
|
|
295
348
|
// get the dimension class
|
|
296
349
|
const dimensionType = this.dimensionMap[dimTypeNumber];
|
|
297
350
|
// get the dimension type as a string
|
|
298
351
|
const dimensionTypeString = dimensionType.register().command;
|
|
299
352
|
// get the points for the dimension
|
|
300
|
-
const points = dimensionType.getPointsFromSelection(this.selectedEntities, Pt11);
|
|
353
|
+
const points = dimensionType.getPointsFromSelection(this.selectedEntities, Pt11, angle);
|
|
301
354
|
// create the temporary dimension
|
|
302
|
-
DesignCore.Scene.previewEntities.create(dimensionTypeString, { points: points, dimensionStyle: this.getProperty(Property.Names.DIMENSIONSTYLE) });
|
|
355
|
+
DesignCore.Scene.previewEntities.create(dimensionTypeString, { points: points, dimensionStyle: this.getProperty(Property.Names.DIMENSIONSTYLE), linearDimAngle: angle });
|
|
303
356
|
}
|
|
304
357
|
}
|
|
305
358
|
}
|
|
@@ -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)) {
|