@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
|
@@ -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';
|
|
@@ -399,6 +400,11 @@ export class Canvas {
|
|
|
399
400
|
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
400
401
|
this.#paintEntities(selectedEntities, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
|
|
401
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
|
+
|
|
402
408
|
// Paint the auxiliary scene items
|
|
403
409
|
// auxiliary items include things like the selection window, snap points etc
|
|
404
410
|
// these items have their own draw routine
|
|
@@ -462,6 +468,22 @@ export class Canvas {
|
|
|
462
468
|
}
|
|
463
469
|
}
|
|
464
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
|
+
|
|
465
487
|
/**
|
|
466
488
|
* Set the scene context on the renderer before drawing an entity.
|
|
467
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
|
|
|
@@ -70,7 +70,7 @@ const classes = {
|
|
|
70
70
|
// Spline,
|
|
71
71
|
ArcAlignedText,
|
|
72
72
|
Text,
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
Dimension,
|
|
75
75
|
AngularDimension,
|
|
76
76
|
AlignedDimension,
|
|
@@ -78,7 +78,7 @@ const classes = {
|
|
|
78
78
|
LinearDimension,
|
|
79
79
|
RadialDimension,
|
|
80
80
|
RotatedDimension,
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
Block,
|
|
83
83
|
Insert,
|
|
84
84
|
Hatch,
|
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';
|
|
@@ -131,7 +132,9 @@ export class InputManager {
|
|
|
131
132
|
* @param {string} command - command name or shortcut
|
|
132
133
|
*/
|
|
133
134
|
startCommand(command) {
|
|
134
|
-
if (this.activeCommand
|
|
135
|
+
if (this.activeCommand instanceof Tool) {
|
|
136
|
+
this.activeCommand.cancel();
|
|
137
|
+
} else if (this.activeCommand !== undefined) {
|
|
135
138
|
this.reset();
|
|
136
139
|
}
|
|
137
140
|
|
|
@@ -196,6 +199,17 @@ export class InputManager {
|
|
|
196
199
|
* @param {Point} point
|
|
197
200
|
*/
|
|
198
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
|
+
|
|
199
213
|
// left clicks are used for selection of entities and points
|
|
200
214
|
// Selection of entities can only occur if there is no active snap available
|
|
201
215
|
// Selection of entities can only occur if the mouse is over an entity
|
|
@@ -282,8 +296,14 @@ export class InputManager {
|
|
|
282
296
|
this.activeCommand.preview();
|
|
283
297
|
}
|
|
284
298
|
|
|
285
|
-
//
|
|
286
|
-
|
|
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)) {
|
|
287
307
|
this.highlightEntityUnderMouse();
|
|
288
308
|
}
|
|
289
309
|
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { RendererBase } from './rendererBase.js';
|
|
2
2
|
import { Text } from '../../entities/text.js';
|
|
3
3
|
|
|
4
|
+
// Unicode code points mapped to WinAnsi bytes 0x80-0x9F (the range where WinAnsi differs from Latin-1)
|
|
5
|
+
const WIN_ANSI_EXTRAS = new Map([
|
|
6
|
+
[0x20ac, 0x80], [0x201a, 0x82], [0x0192, 0x83], [0x201e, 0x84], [0x2026, 0x85],
|
|
7
|
+
[0x2020, 0x86], [0x2021, 0x87], [0x02c6, 0x88], [0x2030, 0x89], [0x0160, 0x8a],
|
|
8
|
+
[0x2039, 0x8b], [0x0152, 0x8c], [0x017d, 0x8e], [0x2018, 0x91], [0x2019, 0x92],
|
|
9
|
+
[0x201c, 0x93], [0x201d, 0x94], [0x2022, 0x95], [0x2013, 0x96], [0x2014, 0x97],
|
|
10
|
+
[0x02dc, 0x98], [0x2122, 0x99], [0x0161, 0x9a], [0x203a, 0x9b], [0x0153, 0x9c],
|
|
11
|
+
[0x017e, 0x9e], [0x0178, 0x9f],
|
|
12
|
+
]);
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* Pure-JS PDF renderer.
|
|
6
16
|
* Implements the RendererBase interface by accumulating a PDF content stream in memory.
|
|
@@ -361,6 +371,12 @@ export class PdfRenderer extends RendererBase {
|
|
|
361
371
|
return str
|
|
362
372
|
.replace(/\\/g, '\\\\')
|
|
363
373
|
.replace(/\(/g, '\\(')
|
|
364
|
-
.replace(/\)/g, '\\)')
|
|
374
|
+
.replace(/\)/g, '\\)')
|
|
375
|
+
// Keep output ASCII: map Unicode to WinAnsi (Windows-1252) bytes, unsupported characters become '?'
|
|
376
|
+
.replace(/[^\x00-\x7f]/g, (c) => {
|
|
377
|
+
const code = c.charCodeAt(0);
|
|
378
|
+
const byte = WIN_ANSI_EXTRAS.get(code) ?? (code >= 0xa0 && code <= 0xff ? code : 0x3f);
|
|
379
|
+
return '\\' + byte.toString(8).padStart(3, '0');
|
|
380
|
+
});
|
|
365
381
|
}
|
|
366
382
|
}
|
package/core/lib/scene.js
CHANGED
|
@@ -139,6 +139,7 @@ export class Scene {
|
|
|
139
139
|
undo() {
|
|
140
140
|
if (this.stateManager.canUndo()) {
|
|
141
141
|
this.stateManager.undo();
|
|
142
|
+
this.selectionManager.reloadSelectedEntities();
|
|
142
143
|
DesignCore.Canvas.requestPaint();
|
|
143
144
|
} else {
|
|
144
145
|
DesignCore.Core.notify(Strings.Message.NOUNDO);
|
|
@@ -151,6 +152,7 @@ export class Scene {
|
|
|
151
152
|
redo() {
|
|
152
153
|
if (this.stateManager.canRedo()) {
|
|
153
154
|
this.stateManager.redo();
|
|
155
|
+
this.selectionManager.reloadSelectedEntities();
|
|
154
156
|
DesignCore.Canvas.requestPaint();
|
|
155
157
|
} else {
|
|
156
158
|
DesignCore.Core.notify(Strings.Message.NOREDO);
|
|
@@ -156,6 +156,34 @@ export class SelectionManager {
|
|
|
156
156
|
return DesignCore.Scene.entities.findClosest(point);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Find the grip closest to point, within tolerance, across all selected entities.
|
|
161
|
+
* Hit-tests against the real scene entities (not the selectedEntities preview clones) so a
|
|
162
|
+
* grip's apply() always recomputes from the last-committed geometry.
|
|
163
|
+
* @param {Point} point
|
|
164
|
+
* @return {Object|undefined} { entityIndex, grip } or undefined if no grip is close enough
|
|
165
|
+
*/
|
|
166
|
+
findGripUnderMouse(point) {
|
|
167
|
+
const gripAperture = 25; // capture radius in pixels (screen space), matches Snapping's snapAperture
|
|
168
|
+
const delta = gripAperture / DesignCore.Canvas.getScale();
|
|
169
|
+
|
|
170
|
+
let closestGrip;
|
|
171
|
+
let closestDistance = delta;
|
|
172
|
+
|
|
173
|
+
for (const entityIndex of this.selectionSet.selectionSet) {
|
|
174
|
+
const entity = DesignCore.Scene.entities.get(entityIndex);
|
|
175
|
+
for (const grip of entity.getGrips()) {
|
|
176
|
+
const distance = grip.point.distance(point);
|
|
177
|
+
if (distance < closestDistance) {
|
|
178
|
+
closestDistance = distance;
|
|
179
|
+
closestGrip = { entityIndex, grip };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return closestGrip;
|
|
185
|
+
}
|
|
186
|
+
|
|
159
187
|
/**
|
|
160
188
|
* Remove the item at index from the selectionSet and selectedEntities
|
|
161
189
|
* @param {number} index - index of the item
|
package/core/lib/strings.js
CHANGED
|
@@ -99,6 +99,20 @@ export class DimType {
|
|
|
99
99
|
this.userPositionedText = (dimType & 128) !== 0;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Set only the base dimension type (0-6), leaving the block reference, ordinate and
|
|
104
|
+
* user-positioned-text flags untouched - used by subclass constructors to pin their type
|
|
105
|
+
* without clobbering flags already loaded from a DXF group 70 value
|
|
106
|
+
* @param {number} baseType
|
|
107
|
+
*/
|
|
108
|
+
setBaseType(baseType) {
|
|
109
|
+
if (!DimType.isValidDimensionType(baseType)) {
|
|
110
|
+
baseType = 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.dimType = DimType.getBaseType(baseType);
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
/**
|
|
103
117
|
* Check if the dimension has a block reference
|
|
104
118
|
* @return {boolean} true if the dimension has a block reference
|
package/core/tables/dimStyle.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { DXFFile } from '../lib/dxf/dxfFile.js';
|
|
3
|
+
import { DesignCore } from '../designCore.js';
|
|
3
4
|
import { Logging } from '../lib/logging.js';
|
|
4
5
|
import { Flags } from '../properties/flags.js';
|
|
5
6
|
import { Property } from '../properties/property.js';
|
|
@@ -383,6 +384,8 @@ export class DimStyle {
|
|
|
383
384
|
|
|
384
385
|
// DXF Groupcode 340 - (handle of referenced STYLE)
|
|
385
386
|
this.DIMTXSTY = Property.loadValue([data.DIMTXSTY, data[340]], 'Standard');
|
|
387
|
+
// internal - true when DIMTXSTY holds a style handle (loaded from group 340) rather than a style name
|
|
388
|
+
this.DIMTXSTYISHANDLE = Property.loadValue([data.DIMTXSTYISHANDLE, data.DIMTXSTY === undefined && data[340] !== undefined], false);
|
|
386
389
|
|
|
387
390
|
// DXF Groupcode 341 - (handle of referenced BLOCK)
|
|
388
391
|
this.DIMLDRBLK = Property.loadValue([data.DIMLDRBLK, data[341]], 'ClosedFilled');
|
|
@@ -475,6 +478,9 @@ export class DimStyle {
|
|
|
475
478
|
|
|
476
479
|
if (this.hasOwnProperty(valueName)) {
|
|
477
480
|
this[valueName] = value;
|
|
481
|
+
if (valueName === 'DIMTXSTY') {
|
|
482
|
+
this.DIMTXSTYISHANDLE = false;
|
|
483
|
+
}
|
|
478
484
|
} else {
|
|
479
485
|
const err = `Setting Value - Property does not exist: ${valueName}`;
|
|
480
486
|
Logging.instance.warn(`${this.type} - ${err}`);
|
|
@@ -544,6 +550,23 @@ export class DimStyle {
|
|
|
544
550
|
this.DIMCENVALUE = Math.abs(value);
|
|
545
551
|
}
|
|
546
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Get the handle of the text style referenced by DIMTXSTY
|
|
555
|
+
* DIMTXSTY holds a style name, or a style handle when DIMTXSTYISHANDLE is set (loaded from a dxf file)
|
|
556
|
+
* @return {string} handle or undefined if no matching style exists
|
|
557
|
+
*/
|
|
558
|
+
getTextStyleHandle() {
|
|
559
|
+
const styles = DesignCore.StyleManager.getItems();
|
|
560
|
+
const reference = String(this.DIMTXSTY).toUpperCase();
|
|
561
|
+
|
|
562
|
+
const style = styles.find((item) => this.DIMTXSTYISHANDLE ?
|
|
563
|
+
String(item.handle).toUpperCase() === reference :
|
|
564
|
+
item.name.toUpperCase() === reference) ??
|
|
565
|
+
styles.find((item) => item.name.toUpperCase() === 'STANDARD');
|
|
566
|
+
|
|
567
|
+
return style?.handle;
|
|
568
|
+
}
|
|
569
|
+
|
|
547
570
|
/**
|
|
548
571
|
* Write the entity to file in the dxf format
|
|
549
572
|
* @param {DXFFile} file
|
|
@@ -616,8 +639,15 @@ export class DimStyle {
|
|
|
616
639
|
file.writeGroupCode('287', this.DIMFIT);
|
|
617
640
|
file.writeGroupCode('288', this.DIMUPT, DXFFile.Version.R13);
|
|
618
641
|
file.writeGroupCode('289', this.DIMATFIT, DXFFile.Version.R2000);
|
|
619
|
-
|
|
620
|
-
|
|
642
|
+
// 340 and 341 are handles - writing a name leaves AutoCAD unable to resolve the text style (dimension text is not shown)
|
|
643
|
+
const textStyleHandle = this.getTextStyleHandle();
|
|
644
|
+
if (textStyleHandle !== undefined) {
|
|
645
|
+
file.writeGroupCode('340', textStyleHandle, DXFFile.Version.R13);
|
|
646
|
+
}
|
|
647
|
+
// 'ClosedFilled' is the default leader arrowhead and has no block handle, omitting the group selects it
|
|
648
|
+
if (this.DIMLDRBLK !== 'ClosedFilled') {
|
|
649
|
+
file.writeGroupCode('341', this.DIMLDRBLK, DXFFile.Version.R2000);
|
|
650
|
+
}
|
|
621
651
|
// file.writeGroupCode('342', this.DIMBLK);
|
|
622
652
|
// file.writeGroupCode('343', this.DIMBLK1);
|
|
623
653
|
// file.writeGroupCode('344', this.DIMBLK2);
|