@design-core/core 0.0.6 → 50.1.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/.eslintrc.json +4 -0
- package/.github/workflows/DesignCoreQA.yml +3 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +12 -11
- package/core/dimensions/angularDimension.js +38 -37
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
- package/core/dimensions/diametricDimension.js +14 -13
- package/core/dimensions/dimension.js +62 -55
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
- package/core/dimensions/radialDimension.js +12 -11
- package/core/dimensions/rotatedDimension.js +20 -19
- package/core/entities/arc.js +123 -156
- package/core/entities/arctext.js +222 -184
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +346 -273
- package/core/entities/insert.js +68 -83
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +7 -7
- package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +190 -201
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/boundingBox.js +11 -11
- package/core/lib/canvas.js +232 -168
- package/core/lib/clipboard.js +9 -3
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +15 -14
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +33 -26
- package/core/lib/inputManager.js +36 -12
- package/core/lib/intersect.js +25 -0
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +9 -2
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +28 -6
- package/core/lib/selectionManager.js +20 -20
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +26 -7
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +60 -44
- package/core/tables/block.js +50 -81
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +117 -143
- package/core/tools/chamferFilletBase.js +138 -37
- package/core/tools/copy.js +11 -11
- package/core/tools/copybase.js +3 -3
- package/core/tools/copyclip.js +3 -3
- package/core/tools/cornerEntity.js +2 -2
- package/core/tools/cutclip.js +3 -3
- package/core/tools/explode.js +7 -7
- package/core/tools/extend.js +138 -25
- package/core/tools/fillet.js +92 -189
- package/core/tools/matchProp.js +3 -12
- package/core/tools/mirror.js +15 -14
- package/core/tools/move.js +8 -8
- package/core/tools/offset.js +31 -33
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +19 -16
- package/core/tools/scale.js +7 -7
- package/core/tools/trim.js +271 -29
- package/core/tools/zoom.js +1 -1
- package/cspell.json +321 -0
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +17 -10
- package/test/dimensions/angularDimension.test.js +13 -13
- package/test/dimensions/diametricDimension.test.js +15 -15
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
- package/test/dimensions/linearDimensionBase.test.js +62 -0
- package/test/dimensions/radialDimension.test.js +15 -15
- package/test/dimensions/rotatedDimension.test.js +18 -11
- package/test/entities/arc.test.js +83 -86
- package/test/entities/arctext.test.js +184 -57
- package/test/entities/circle.test.js +68 -96
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +569 -96
- package/test/entities/insert.test.js +99 -4
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/point.test.js +3 -3
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +302 -77
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +5 -5
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +744 -125
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +81 -20
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +72 -130
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +25 -25
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +92 -13
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +158 -6
- package/test/tools/chamfer.test.js +273 -51
- package/test/tools/chamferFilletBase.test.js +125 -36
- package/test/tools/copy.test.js +8 -8
- package/test/tools/copybase.test.js +2 -2
- package/test/tools/copyclip.test.js +2 -2
- package/test/tools/cornerEntity.test.js +8 -8
- package/test/tools/cutclip.test.js +2 -2
- package/test/tools/erase.test.js +6 -6
- package/test/tools/explode.test.js +5 -5
- package/test/tools/extend.test.js +373 -42
- package/test/tools/fillet.test.js +230 -49
- package/test/tools/matchProp.test.js +11 -11
- package/test/tools/mirror.test.js +39 -39
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +222 -55
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +39 -9
- package/test/tools/scale.test.js +33 -33
- package/test/tools/trim.test.js +579 -53
- package/test/tools/zoom.test.js +12 -12
- package/core/lib/selectionWindow.js +0 -83
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PolylineBase } from '../../core/entities/polylineBase.js';
|
|
2
2
|
import { Point } from '../../core/entities/point';
|
|
3
3
|
import { Polyline } from '../../core/entities/polyline.js';
|
|
4
4
|
import { Line } from '../../core/entities/line.js';
|
|
@@ -30,7 +30,7 @@ test.each(inputScenarios)('Polyline.execute handles $desc', async (scenario) =>
|
|
|
30
30
|
const { inputs, expectedPoints } = scenario;
|
|
31
31
|
|
|
32
32
|
await withMockInput(DesignCore.Scene, inputs, async () => {
|
|
33
|
-
const polyline = new
|
|
33
|
+
const polyline = new PolylineBase({});
|
|
34
34
|
await polyline.execute();
|
|
35
35
|
|
|
36
36
|
expect(polyline.points.length).toBe(expectedPoints);
|
|
@@ -46,11 +46,23 @@ test.each(inputScenarios)('Polyline.execute handles $desc', async (scenario) =>
|
|
|
46
46
|
}, { extraMethods: { actionCommand: () => {} } });
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
test('Test
|
|
49
|
+
test('Test PolylineBase.draw calls renderer.drawShape with toPolylinePoints', () => {
|
|
50
|
+
const polyline = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 5)] });
|
|
51
|
+
let capturedPoints;
|
|
52
|
+
const mockRenderer = {
|
|
53
|
+
drawShape(points) {
|
|
54
|
+
capturedPoints = points;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
polyline.draw(mockRenderer);
|
|
58
|
+
expect(capturedPoints).toEqual(polyline.toPolylinePoints());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Test PolylineBase.getClosestSegment', () => {
|
|
50
62
|
// Polyline with a line and an arc segment
|
|
51
63
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10)];
|
|
52
64
|
points[1].bulge = 1; // Arc from (10,0) to (10,10)
|
|
53
|
-
const polyline = new
|
|
65
|
+
const polyline = new PolylineBase({ points });
|
|
54
66
|
|
|
55
67
|
// Closest to the line segment
|
|
56
68
|
const testPoint1 = new Point(5, 2);
|
|
@@ -65,11 +77,11 @@ test('Test BasePolyline.getClosestSegment', () => {
|
|
|
65
77
|
|
|
66
78
|
// ─── closing segment (closed polylines) ──────────────────────────────────────
|
|
67
79
|
|
|
68
|
-
test('
|
|
80
|
+
test('PolylineBase.getClosestSegmentIndex returns closing-segment index for closed polyline', () => {
|
|
69
81
|
// Square: P0=(0,0) P1=(10,0) P2=(10,10) P3=(0,10), closed flag set.
|
|
70
82
|
// Closing segment is P3→P0. A point near the left edge (0,5) is closest to that segment.
|
|
71
83
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
72
|
-
const poly = new
|
|
84
|
+
const poly = new PolylineBase({ points });
|
|
73
85
|
poly.flags.setFlagValue(1);
|
|
74
86
|
|
|
75
87
|
const closestIdx = poly.getClosestSegmentIndex(new Point(0, 5));
|
|
@@ -77,9 +89,9 @@ test('BasePolyline.getClosestSegmentIndex returns closing-segment index for clos
|
|
|
77
89
|
expect(closestIdx).toBe(4);
|
|
78
90
|
});
|
|
79
91
|
|
|
80
|
-
test('
|
|
92
|
+
test('PolylineBase.getClosestSegment returns closing segment as a Line for closed polyline', () => {
|
|
81
93
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
82
|
-
const poly = new
|
|
94
|
+
const poly = new PolylineBase({ points });
|
|
83
95
|
poly.flags.setFlagValue(1);
|
|
84
96
|
|
|
85
97
|
const seg = poly.getClosestSegment(new Point(0, 5));
|
|
@@ -91,21 +103,21 @@ test('BasePolyline.getClosestSegment returns closing segment as a Line for close
|
|
|
91
103
|
expect(seg.points[1].y).toBeCloseTo(0);
|
|
92
104
|
});
|
|
93
105
|
|
|
94
|
-
test('
|
|
106
|
+
test('PolylineBase.areConsecutiveSegments returns false for non-adjacent segments on a closed polyline', () => {
|
|
95
107
|
// 4-point closed poly: segments 1 and 3 are NOT adjacent (bug: old code returned true).
|
|
96
108
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
97
|
-
const poly = new
|
|
109
|
+
const poly = new PolylineBase({ points });
|
|
98
110
|
poly.flags.setFlagValue(1);
|
|
99
111
|
|
|
100
112
|
expect(poly.areConsecutiveSegments(1, 3)).toBe(false);
|
|
101
113
|
});
|
|
102
114
|
|
|
103
|
-
test('
|
|
115
|
+
test('PolylineBase.areConsecutiveSegments returns true for closing segment and its neighbours', () => {
|
|
104
116
|
// 4-point closed poly, N=4 (closing segment index).
|
|
105
117
|
// Seg N-1=3 ends at P3, closing seg starts at P3 → consecutive (diff=1).
|
|
106
118
|
// Closing seg ends at P0, seg 1 starts at P0 → consecutive (wrap case).
|
|
107
119
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
108
|
-
const poly = new
|
|
120
|
+
const poly = new PolylineBase({ points });
|
|
109
121
|
poly.flags.setFlagValue(1);
|
|
110
122
|
|
|
111
123
|
expect(poly.areConsecutiveSegments(3, 4)).toBe(true); // diff = 1
|
|
@@ -113,11 +125,11 @@ test('BasePolyline.areConsecutiveSegments returns true for closing segment and i
|
|
|
113
125
|
expect(poly.areConsecutiveSegments(1, 4)).toBe(true); // wrap (order reversed)
|
|
114
126
|
});
|
|
115
127
|
|
|
116
|
-
test('
|
|
128
|
+
test('PolylineBase.closestPointOnSegment handles closing-segment index correctly', () => {
|
|
117
129
|
// 4-point square, closing segment = P3=(0,10)→P0=(0,0). Index = 4.
|
|
118
130
|
// Closest point on that segment to (0, 5) should be (0, 5).
|
|
119
131
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
120
|
-
const poly = new
|
|
132
|
+
const poly = new PolylineBase({ points });
|
|
121
133
|
poly.flags.setFlagValue(1);
|
|
122
134
|
|
|
123
135
|
const closest = poly.closestPointOnSegment(new Point(0, 5), 4);
|
|
@@ -126,9 +138,9 @@ test('BasePolyline.closestPointOnSegment handles closing-segment index correctly
|
|
|
126
138
|
});
|
|
127
139
|
|
|
128
140
|
|
|
129
|
-
test('Test
|
|
141
|
+
test('Test PolylineBase.closestPoint', () => {
|
|
130
142
|
const points = [new Point(100, 100), new Point(200, 100, -1), new Point(200, 50)];
|
|
131
|
-
const polyline = new
|
|
143
|
+
const polyline = new PolylineBase({ points: points });
|
|
132
144
|
// line segment
|
|
133
145
|
const point1 = new Point(150, 85);
|
|
134
146
|
const closest1 = polyline.closestPoint(point1);
|
|
@@ -144,14 +156,14 @@ test('Test BasePolyline.closestPoint', () => {
|
|
|
144
156
|
expect(closest2[1]).toBeCloseTo(10.857);
|
|
145
157
|
});
|
|
146
158
|
|
|
147
|
-
test('Test
|
|
148
|
-
let polyline = new
|
|
159
|
+
test('Test PolylineBase.boundingBox', () => {
|
|
160
|
+
let polyline = new PolylineBase({ points: [new Point(101, 102), new Point(201, 202)] });
|
|
149
161
|
expect(polyline.boundingBox().xMin).toBeCloseTo(101);
|
|
150
162
|
expect(polyline.boundingBox().xMax).toBeCloseTo(201);
|
|
151
163
|
expect(polyline.boundingBox().yMin).toBeCloseTo(102);
|
|
152
164
|
expect(polyline.boundingBox().yMax).toBeCloseTo(202);
|
|
153
165
|
|
|
154
|
-
polyline = new
|
|
166
|
+
polyline = new PolylineBase({ points: [new Point(101, 102), new Point(-201, 202)] });
|
|
155
167
|
expect(polyline.boundingBox().xMin).toBeCloseTo(-201);
|
|
156
168
|
expect(polyline.boundingBox().xMax).toBeCloseTo(101);
|
|
157
169
|
expect(polyline.boundingBox().yMin).toBeCloseTo(102);
|
|
@@ -161,7 +173,7 @@ test('Test BasePolyline.boundingBox', () => {
|
|
|
161
173
|
let points = [new Point(101, 102), new Point(200, 102), new Point(200, 0)];
|
|
162
174
|
// set the bulge
|
|
163
175
|
points[1].bulge = -1;
|
|
164
|
-
polyline = new
|
|
176
|
+
polyline = new PolylineBase({ points: points });
|
|
165
177
|
expect(polyline.boundingBox().xMin).toBeCloseTo(101);
|
|
166
178
|
expect(polyline.boundingBox().xMax).toBeCloseTo(251);
|
|
167
179
|
expect(polyline.boundingBox().yMin).toBeCloseTo(0);
|
|
@@ -170,19 +182,19 @@ test('Test BasePolyline.boundingBox', () => {
|
|
|
170
182
|
points = [new Point(101, 102), new Point(200, 102), new Point(200, 0)];
|
|
171
183
|
// set the bulge
|
|
172
184
|
points[1].bulge = -1;
|
|
173
|
-
polyline = new
|
|
185
|
+
polyline = new PolylineBase({ points: points });
|
|
174
186
|
expect(polyline.boundingBox().xMin).toBeCloseTo(101);
|
|
175
187
|
expect(polyline.boundingBox().xMax).toBeCloseTo(251);
|
|
176
188
|
expect(polyline.boundingBox().yMin).toBeCloseTo(0);
|
|
177
189
|
expect(polyline.boundingBox().yMax).toBeCloseTo(102);
|
|
178
190
|
});
|
|
179
191
|
|
|
180
|
-
test('Test
|
|
192
|
+
test('Test PolylineBase.getBulgeFromSegment', () => {
|
|
181
193
|
// start point: 100,0
|
|
182
|
-
//
|
|
194
|
+
// centre: 100,50
|
|
183
195
|
// radius: 50
|
|
184
196
|
const points = [new Point(), new Point(100, 0)];
|
|
185
|
-
const polyline = new
|
|
197
|
+
const polyline = new PolylineBase({ points: points });
|
|
186
198
|
|
|
187
199
|
// zero delta angle:
|
|
188
200
|
// bulge: 0
|
|
@@ -322,7 +334,7 @@ AcDbPolyline
|
|
|
322
334
|
6
|
|
323
335
|
ByLayer
|
|
324
336
|
39
|
|
325
|
-
|
|
337
|
+
1
|
|
326
338
|
90
|
|
327
339
|
3
|
|
328
340
|
70
|
|
@@ -350,7 +362,7 @@ ByLayer
|
|
|
350
362
|
expect(file.contents).toEqual(dxfString);
|
|
351
363
|
|
|
352
364
|
// create new entity from entity data to ensure all props are loaded
|
|
353
|
-
const newPolyline = new Polyline(polyline);
|
|
365
|
+
const newPolyline = new Polyline({ handle: polyline.getProperty('handle'), points: polyline.points });
|
|
354
366
|
file = new File();
|
|
355
367
|
newPolyline.dxf(file);
|
|
356
368
|
|
|
@@ -419,10 +431,10 @@ SEQEND
|
|
|
419
431
|
expect(file.contents).toEqual(dxfString);
|
|
420
432
|
});
|
|
421
433
|
|
|
422
|
-
test('Test
|
|
434
|
+
test('Test PolylineBase.toPolylinePoints', () => {
|
|
423
435
|
const points = [new Point(100, 100), new Point(200, 100), new Point(200, 50)];
|
|
424
436
|
points[1].bulge = -1;
|
|
425
|
-
const polyline = new
|
|
437
|
+
const polyline = new PolylineBase({ points: points });
|
|
426
438
|
|
|
427
439
|
const polylinePoints = polyline.toPolylinePoints();
|
|
428
440
|
expect(polylinePoints[0].x).toBe(100);
|
|
@@ -438,10 +450,10 @@ test('Test BasePolyline.toPolylinePoints', () => {
|
|
|
438
450
|
expect(polylinePoints[2].bulge).toBe(0);
|
|
439
451
|
});
|
|
440
452
|
|
|
441
|
-
test('Test
|
|
453
|
+
test('Test PolylineBase.toPolylinePoints returns defensive copy for open polyline', () => {
|
|
442
454
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10)];
|
|
443
455
|
points[1].bulge = -1;
|
|
444
|
-
const polyline = new
|
|
456
|
+
const polyline = new PolylineBase({ points: points });
|
|
445
457
|
|
|
446
458
|
const result = polyline.toPolylinePoints();
|
|
447
459
|
// Mutate the returned array and one of the returned points
|
|
@@ -454,13 +466,13 @@ test('Test BasePolyline.toPolylinePoints returns defensive copy for open polylin
|
|
|
454
466
|
expect(polyline.points[1].bulge).toBe(-1);
|
|
455
467
|
});
|
|
456
468
|
|
|
457
|
-
test('Test
|
|
469
|
+
test('Test PolylineBase.toPolylinePoints closed polyline appends closure point', () => {
|
|
458
470
|
// A triangle closed via the Close command (flag bit 1 set, no duplicate
|
|
459
471
|
// point stored in this.points).
|
|
460
472
|
const points = [new Point(0, 0), new Point(10, 0), new Point(5, 10)];
|
|
461
473
|
const flags = new Flags();
|
|
462
474
|
flags.addValue(1); // closed flag
|
|
463
|
-
const polyline = new
|
|
475
|
+
const polyline = new PolylineBase({ points: points, flags: flags });
|
|
464
476
|
|
|
465
477
|
const polylinePoints = polyline.toPolylinePoints();
|
|
466
478
|
// Should have 4 points: the 3 vertices plus the closure point
|
|
@@ -494,7 +506,7 @@ test('Polyline.execute handles Close option', async () => {
|
|
|
494
506
|
test('Polyline.execute supports Undo in line mode', async () => {
|
|
495
507
|
const inputs = [new Point(0, 0), new Point(10, 0), new Point(20, 0), 'Undo', new Point(10, 10), 'Close'];
|
|
496
508
|
await withMockInput(DesignCore.Scene, inputs, async () => {
|
|
497
|
-
const polyline = new
|
|
509
|
+
const polyline = new PolylineBase({});
|
|
498
510
|
await polyline.execute();
|
|
499
511
|
expect(polyline.points.length).toBe(3);
|
|
500
512
|
expect(polyline.points[0]).toEqual(inputs[0]);
|
|
@@ -507,7 +519,7 @@ test('Polyline.execute supports Undo in line mode', async () => {
|
|
|
507
519
|
test('Polyline.execute supports Undo in arc mode', async () => {
|
|
508
520
|
const inputs = [new Point(0, 0), new Point(10, 0), 'Arc', new Point(10, 10), new Point(20, 20), 'Undo', 'Line', new Point(10, 20), 'Close'];
|
|
509
521
|
await withMockInput(DesignCore.Scene, inputs, async () => {
|
|
510
|
-
const polyline = new
|
|
522
|
+
const polyline = new PolylineBase({});
|
|
511
523
|
await polyline.execute();
|
|
512
524
|
expect(polyline.points.length).toBe(4);
|
|
513
525
|
expect(polyline.points[0]).toEqual(inputs[0]);
|
|
@@ -525,7 +537,7 @@ test('Polyline.execute supports Undo in arc mode', async () => {
|
|
|
525
537
|
test('Polyline.execute supports Arc/Line mode switching', async () => {
|
|
526
538
|
const inputs = [new Point(0, 0), new Point(10, 0), 'Arc', new Point(10, 10), 'Line', new Point(20, 10), 'Close'];
|
|
527
539
|
await withMockInput(DesignCore.Scene, inputs, async () => {
|
|
528
|
-
const polyline = new
|
|
540
|
+
const polyline = new PolylineBase({});
|
|
529
541
|
await polyline.execute();
|
|
530
542
|
expect(polyline.points.length).toBe(4);
|
|
531
543
|
expect(polyline.points[0]).toEqual(inputs[0]);
|
|
@@ -541,29 +553,29 @@ test('Polyline.execute supports Arc/Line mode switching', async () => {
|
|
|
541
553
|
|
|
542
554
|
// ─── Auto-close when first and last point are the same ───────────────────────
|
|
543
555
|
|
|
544
|
-
test('
|
|
556
|
+
test('PolylineBase constructor auto-closes when last point matches first', () => {
|
|
545
557
|
// Simulate a polyline loaded with a redundant closing point (e.g. from DXF data)
|
|
546
558
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 0)];
|
|
547
|
-
const polyline = new
|
|
559
|
+
const polyline = new PolylineBase({ points });
|
|
548
560
|
|
|
549
561
|
// Duplicate point should be removed and the closed flag set
|
|
550
562
|
expect(polyline.points.length).toBe(3);
|
|
551
563
|
expect(polyline.flags.hasFlag(1)).toBe(true);
|
|
552
564
|
});
|
|
553
565
|
|
|
554
|
-
test('
|
|
566
|
+
test('PolylineBase constructor does not auto-close when fewer than 3 unique points remain after pop', () => {
|
|
555
567
|
// [A, B, A] — only 2 unique points after removing the duplicate; must not auto-close
|
|
556
568
|
const points = [new Point(0, 0), new Point(10, 0), new Point(0, 0)];
|
|
557
|
-
const polyline = new
|
|
569
|
+
const polyline = new PolylineBase({ points });
|
|
558
570
|
|
|
559
571
|
expect(polyline.points.length).toBe(3);
|
|
560
572
|
expect(polyline.flags.hasFlag(1)).toBe(false);
|
|
561
573
|
});
|
|
562
574
|
|
|
563
|
-
test('
|
|
575
|
+
test('PolylineBase constructor does not auto-close with only 2 identical points', () => {
|
|
564
576
|
// Only 2 unique points — not enough to form a closed polygon
|
|
565
577
|
const points = [new Point(0, 0), new Point(0, 0)];
|
|
566
|
-
const polyline = new
|
|
578
|
+
const polyline = new PolylineBase({ points });
|
|
567
579
|
|
|
568
580
|
expect(polyline.points.length).toBe(2);
|
|
569
581
|
expect(polyline.flags.hasFlag(1)).toBe(false);
|
|
@@ -581,14 +593,14 @@ test('Polyline.execute auto-closes when start point is re-entered', async () =>
|
|
|
581
593
|
// The constructor will have auto-closed the entity when pt4 == pt1
|
|
582
594
|
if (entity.points.length === 4 && entity.points.at(-1).isSame(pt1)) {
|
|
583
595
|
// simulate what the constructor does: a new entity created from this data would auto-close
|
|
584
|
-
const rebuilt = new
|
|
596
|
+
const rebuilt = new PolylineBase({ points: [...entity.points] });
|
|
585
597
|
if (rebuilt.flags.hasFlag(1)) autoClosedCount++;
|
|
586
598
|
}
|
|
587
599
|
return 0;
|
|
588
600
|
};
|
|
589
601
|
|
|
590
602
|
await withMockInput(DesignCore.Scene, [pt1, pt2, pt3, pt4], async () => {
|
|
591
|
-
const polyline = new
|
|
603
|
+
const polyline = new PolylineBase({});
|
|
592
604
|
await polyline.execute();
|
|
593
605
|
// execute does not exit early — the duplicate point is on the instance
|
|
594
606
|
expect(polyline.points.length).toBe(4);
|
|
@@ -597,10 +609,10 @@ test('Polyline.execute auto-closes when start point is re-entered', async () =>
|
|
|
597
609
|
}, { extraMethods: { actionCommand: mockActionCommand } });
|
|
598
610
|
});
|
|
599
611
|
|
|
600
|
-
test('
|
|
601
|
-
const poly = new
|
|
612
|
+
test('PolylineBase.fromPolylinePoints round-trip preserves open polyline', () => {
|
|
613
|
+
const poly = new PolylineBase({ points: [new Point(0, 0, 0.5), new Point(10, 0), new Point(10, 10)] });
|
|
602
614
|
const polyPts = poly.toPolylinePoints();
|
|
603
|
-
const rebuilt = new
|
|
615
|
+
const rebuilt = new PolylineBase({});
|
|
604
616
|
rebuilt.fromPolylinePoints(polyPts);
|
|
605
617
|
expect(rebuilt.points.length).toBe(3);
|
|
606
618
|
expect(rebuilt.points[0].x).toBe(0);
|
|
@@ -610,16 +622,16 @@ test('BasePolyline.fromPolylinePoints round-trip preserves open polyline', () =>
|
|
|
610
622
|
expect(rebuilt.points[2].y).toBe(10);
|
|
611
623
|
});
|
|
612
624
|
|
|
613
|
-
test('
|
|
625
|
+
test('PolylineBase.fromPolylinePoints round-trip strips closure point for closed polyline', () => {
|
|
614
626
|
const flags = new Flags();
|
|
615
627
|
flags.addValue(1);
|
|
616
|
-
const poly = new
|
|
628
|
+
const poly = new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)], flags: flags });
|
|
617
629
|
const polyPts = poly.toPolylinePoints();
|
|
618
630
|
// toPolylinePoints appends a closure copy of the first point
|
|
619
631
|
expect(polyPts.length).toBe(5);
|
|
620
632
|
const flags2 = new Flags();
|
|
621
633
|
flags2.addValue(1);
|
|
622
|
-
const rebuilt = new
|
|
634
|
+
const rebuilt = new PolylineBase({ flags: flags2 });
|
|
623
635
|
rebuilt.fromPolylinePoints(polyPts);
|
|
624
636
|
expect(rebuilt.points.length).toBe(4);
|
|
625
637
|
expect(rebuilt.points[0].x).toBe(0);
|
|
@@ -629,12 +641,12 @@ test('BasePolyline.fromPolylinePoints round-trip strips closure point for closed
|
|
|
629
641
|
|
|
630
642
|
// ─── closestPoint: closed polyline closing segment ───────────────────────────
|
|
631
643
|
|
|
632
|
-
test('
|
|
644
|
+
test('PolylineBase.closestPoint finds closest point on closing segment of closed polyline', () => {
|
|
633
645
|
// Square: P0=(0,0), P1=(10,0), P2=(10,10), P3=(0,10), closed flag set.
|
|
634
646
|
// Closing segment is P3=(0,10) → P0=(0,0).
|
|
635
647
|
// A test point on the left edge (0, 5) must resolve to (0, 5) on the closing segment.
|
|
636
648
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
637
|
-
const poly = new
|
|
649
|
+
const poly = new PolylineBase({ points });
|
|
638
650
|
poly.flags.setFlagValue(1);
|
|
639
651
|
|
|
640
652
|
const [closest, dist] = poly.closestPoint(new Point(0, 5));
|
|
@@ -643,11 +655,11 @@ test('BasePolyline.closestPoint finds closest point on closing segment of closed
|
|
|
643
655
|
expect(dist).toBeCloseTo(0);
|
|
644
656
|
});
|
|
645
657
|
|
|
646
|
-
test('
|
|
658
|
+
test('PolylineBase.closestPoint closing segment is preferred over other segments', () => {
|
|
647
659
|
// The test point (-1, 5) is clearly nearest the closing segment (left edge),
|
|
648
660
|
// not the bottom, right, or top edges.
|
|
649
661
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
650
|
-
const poly = new
|
|
662
|
+
const poly = new PolylineBase({ points });
|
|
651
663
|
poly.flags.setFlagValue(1);
|
|
652
664
|
|
|
653
665
|
const [closest, dist] = poly.closestPoint(new Point(-1, 5));
|
|
@@ -656,14 +668,14 @@ test('BasePolyline.closestPoint closing segment is preferred over other segments
|
|
|
656
668
|
expect(dist).toBeCloseTo(1);
|
|
657
669
|
});
|
|
658
670
|
|
|
659
|
-
test('
|
|
671
|
+
test('PolylineBase.closestPoint for open polyline does not include closing segment', () => {
|
|
660
672
|
// For point (-1, 1) on the open square:
|
|
661
673
|
// – Nearest on seg P0→P1 (y=0): foot (-1,0) clips to (0,0), dist = √2 ≈ 1.41
|
|
662
674
|
// – Nearest on seg P2→P3 (y=10): foot (-1,10) clips to (0,10), dist ≈ 9.06
|
|
663
675
|
// – If the closing segment existed (x=0, y∈[0,10]): foot (0,1), dist = 1 ← closer!
|
|
664
676
|
// An open polyline must NOT check the closing segment, so it returns (0,0).
|
|
665
677
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
666
|
-
const poly = new
|
|
678
|
+
const poly = new PolylineBase({ points });
|
|
667
679
|
// flag NOT set — open polyline
|
|
668
680
|
|
|
669
681
|
const [closest] = poly.closestPoint(new Point(-1, 1));
|
|
@@ -671,11 +683,80 @@ test('BasePolyline.closestPoint for open polyline does not include closing segme
|
|
|
671
683
|
expect(closest.y).toBeCloseTo(0);
|
|
672
684
|
});
|
|
673
685
|
|
|
674
|
-
|
|
686
|
+
// ─── isPointOnSegment ────────────────────────────────────────────────────────
|
|
687
|
+
|
|
688
|
+
const lineSegmentCases = [
|
|
689
|
+
{ desc: 'midpoint', point: new Point(5, 0), expected: true },
|
|
690
|
+
{ desc: 'start endpoint', point: new Point(0, 0), expected: true },
|
|
691
|
+
{ desc: 'end endpoint', point: new Point(10, 0), expected: true },
|
|
692
|
+
{ desc: 'perpendicular off-line', point: new Point(5, 5), expected: false },
|
|
693
|
+
{ desc: 'collinear beyond end', point: new Point(15, 0), expected: false },
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
test.each(lineSegmentCases)('PolylineBase.isPointOnSegment line segment – $desc', ({ point, expected }) => {
|
|
697
|
+
const A = new Point(0, 0);
|
|
698
|
+
const B = new Point(10, 0);
|
|
699
|
+
const poly = new PolylineBase({ points: [A, B] });
|
|
700
|
+
expect(poly.isPointOnSegment(point, A, B)).toBe(expected);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// CCW 180° semicircle from (10,0) to (-10,0), bulge=1, centre=(0,0), radius=10.
|
|
704
|
+
// Arc spans angles [0°, 180°] — the upper semicircle.
|
|
705
|
+
const ccwArcCases = [
|
|
706
|
+
{ desc: 'arc midpoint (0,10) at 90°', point: new Point(0, 10), expected: true },
|
|
707
|
+
{ desc: 'start endpoint (10,0)', point: new Point(10, 0), expected: true },
|
|
708
|
+
{ desc: 'end endpoint (-10,0)', point: new Point(-10, 0), expected: true },
|
|
709
|
+
{ desc: 'lower semicircle (0,-10)', point: new Point(0, -10), expected: false },
|
|
710
|
+
];
|
|
711
|
+
|
|
712
|
+
test.each(ccwArcCases)('PolylineBase.isPointOnSegment CCW arc (bulge=1, 180°) – $desc', ({ point, expected }) => {
|
|
713
|
+
const A = new Point(10, 0);
|
|
714
|
+
A.bulge = 1;
|
|
715
|
+
const B = new Point(-10, 0);
|
|
716
|
+
const poly = new PolylineBase({ points: [A, B] });
|
|
717
|
+
expect(poly.isPointOnSegment(point, A, B)).toBe(expected);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// CW 180° semicircle from (10,0) to (-10,0), bulge=-1, centre=(0,0), radius=10.
|
|
721
|
+
// Arc spans angles ≤0° or ≥180° — the lower semicircle.
|
|
722
|
+
const cwArcCases = [
|
|
723
|
+
{ desc: 'arc midpoint (0,-10) at -90°', point: new Point(0, -10), expected: true },
|
|
724
|
+
{ desc: 'start endpoint (10,0)', point: new Point(10, 0), expected: true },
|
|
725
|
+
{ desc: 'end endpoint (-10,0)', point: new Point(-10, 0), expected: true },
|
|
726
|
+
{ desc: 'upper semicircle (0,10)', point: new Point(0, 10), expected: false },
|
|
727
|
+
];
|
|
728
|
+
|
|
729
|
+
test.each(cwArcCases)('PolylineBase.isPointOnSegment CW arc (bulge=-1, 180°) – $desc', ({ point, expected }) => {
|
|
730
|
+
const A = new Point(10, 0);
|
|
731
|
+
A.bulge = -1;
|
|
732
|
+
const B = new Point(-10, 0);
|
|
733
|
+
const poly = new PolylineBase({ points: [A, B] });
|
|
734
|
+
expect(poly.isPointOnSegment(point, A, B)).toBe(expected);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// CCW 180° arc from (0,10) to (0,-10), bulge=1, centre=(0,0), radius=10.
|
|
738
|
+
// startAngle=90° > endAngle=-90° so the check wraps: angles ≥90° OR ≤-90°.
|
|
739
|
+
// This covers the left semicircle (through (-10,0)).
|
|
740
|
+
const ccwArcWrapCases = [
|
|
741
|
+
{ desc: 'left midpoint (-10,0) at 180°', point: new Point(-10, 0), expected: true },
|
|
742
|
+
{ desc: 'start endpoint (0,10)', point: new Point(0, 10), expected: true },
|
|
743
|
+
{ desc: 'end endpoint (0,-10)', point: new Point(0, -10), expected: true },
|
|
744
|
+
{ desc: 'right side (10,0) at 0°', point: new Point(10, 0), expected: false },
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
test.each(ccwArcWrapCases)('PolylineBase.isPointOnSegment CCW arc wrapping past ±180° (bulge=1, startAngle=90° > endAngle=-90°) – $desc', ({ point, expected }) => {
|
|
748
|
+
const A = new Point(0, 10);
|
|
749
|
+
A.bulge = 1;
|
|
750
|
+
const B = new Point(0, -10);
|
|
751
|
+
const poly = new PolylineBase({ points: [A, B] });
|
|
752
|
+
expect(poly.isPointOnSegment(point, A, B)).toBe(expected);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test('PolylineBase.closestPoint is consistent with getClosestSegmentIndex for closed polyline', () => {
|
|
675
756
|
// For a closed square, both methods should agree that the closing segment
|
|
676
757
|
// is nearest for a point on the left edge.
|
|
677
758
|
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
678
|
-
const poly = new
|
|
759
|
+
const poly = new PolylineBase({ points });
|
|
679
760
|
poly.flags.setFlagValue(1);
|
|
680
761
|
|
|
681
762
|
const testPoint = new Point(-2, 5);
|
|
@@ -687,3 +768,57 @@ test('BasePolyline.closestPoint is consistent with getClosestSegmentIndex for cl
|
|
|
687
768
|
expect(closest.x).toBeCloseTo(0);
|
|
688
769
|
expect(closest.y).toBeCloseTo(5);
|
|
689
770
|
});
|
|
771
|
+
|
|
772
|
+
// ─── preview ─────────────────────────────────────────────────────────────────
|
|
773
|
+
|
|
774
|
+
test('PolylineBase.preview - 0 points does nothing', () => {
|
|
775
|
+
const poly = new PolylineBase({});
|
|
776
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
777
|
+
DesignCore.Scene.previewEntities.clear();
|
|
778
|
+
|
|
779
|
+
expect(() => poly.preview()).not.toThrow();
|
|
780
|
+
expect(DesignCore.Scene.auxiliaryEntities.count()).toBe(0);
|
|
781
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
test('PolylineBase.preview - LINE mode creates temp entity', () => {
|
|
785
|
+
const poly = new PolylineBase({});
|
|
786
|
+
poly.points.push(new Point(0, 0));
|
|
787
|
+
|
|
788
|
+
const origCreate = DesignCore.Scene.previewEntities.create;
|
|
789
|
+
const calls = [];
|
|
790
|
+
DesignCore.Scene.previewEntities.create = (type, data) => calls.push({ type, data });
|
|
791
|
+
|
|
792
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
793
|
+
poly.preview();
|
|
794
|
+
|
|
795
|
+
expect(calls.length).toBeGreaterThanOrEqual(1);
|
|
796
|
+
// LINE mode: no rubber-band auxiliary entity
|
|
797
|
+
expect(DesignCore.Scene.auxiliaryEntities.count()).toBe(0);
|
|
798
|
+
|
|
799
|
+
DesignCore.Scene.previewEntities.create = origCreate;
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test('PolylineBase.preview - ARC mode adds rubber band and creates arc temp entity', () => {
|
|
803
|
+
const poly = new PolylineBase({});
|
|
804
|
+
poly.points.push(new Point(0, 0));
|
|
805
|
+
poly.points.push(new Point(10, 0));
|
|
806
|
+
poly.inputMode = poly.modes.ARC;
|
|
807
|
+
|
|
808
|
+
const origAdd = DesignCore.Scene.auxiliaryEntities.add;
|
|
809
|
+
const origCreate = DesignCore.Scene.previewEntities.create;
|
|
810
|
+
const auxAdded = [];
|
|
811
|
+
const tempCreated = [];
|
|
812
|
+
DesignCore.Scene.auxiliaryEntities.add = (item) => auxAdded.push(item);
|
|
813
|
+
DesignCore.Scene.previewEntities.create = (type, data) => tempCreated.push({ type, data });
|
|
814
|
+
|
|
815
|
+
poly.preview();
|
|
816
|
+
|
|
817
|
+
// ARC mode: rubber-band drawn in auxiliaryEntities
|
|
818
|
+
expect(auxAdded.length).toBeGreaterThanOrEqual(1);
|
|
819
|
+
// ARC mode: arc preview drawn in previewEntities
|
|
820
|
+
expect(tempCreated.length).toBeGreaterThanOrEqual(1);
|
|
821
|
+
|
|
822
|
+
DesignCore.Scene.auxiliaryEntities.add = origAdd;
|
|
823
|
+
DesignCore.Scene.previewEntities.create = origCreate;
|
|
824
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Rectangle } from '../../core/entities/rectangle.js';
|
|
2
|
+
import { Core } from '../../core/core/core.js';
|
|
3
|
+
|
|
4
|
+
// initialise core
|
|
5
|
+
new Core();
|
|
6
|
+
|
|
7
|
+
// ─── register ────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
test('Rectangle.register returns correct command object', () => {
|
|
10
|
+
expect(Rectangle.register()).toEqual({ command: 'Rectangle', shortcut: 'REC', type: 'Entity' });
|
|
11
|
+
});
|
|
@@ -59,7 +59,7 @@ AcDbTrace
|
|
|
59
59
|
expect(file.contents).toEqual(dxfString);
|
|
60
60
|
|
|
61
61
|
// create new entity from entity data to ensure all props are loaded
|
|
62
|
-
const newSolid = new Solid(solid);
|
|
62
|
+
const newSolid = new Solid({ handle: solid.getProperty('handle'), points: solid.points });
|
|
63
63
|
file = new File();
|
|
64
64
|
newSolid.dxf(file);
|
|
65
65
|
expect(file.contents).toEqual(dxfString);
|