@design-core/core 0.0.5 → 0.0.7
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/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +33 -284
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- 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/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- 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 +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +36 -13
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- 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 +27 -5
- package/core/lib/selectionManager.js +2 -2
- 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 +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +129 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- 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/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- 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/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- 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 +3 -3
- 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 +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +296 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
|
@@ -61,9 +61,9 @@ test.each(scenarios)('DiametricDimension.execute handles $desc', async (scenario
|
|
|
61
61
|
for (const entity of dimensionBlockEntities) {
|
|
62
62
|
if (entity.type === 'Text') {
|
|
63
63
|
// remove all none numberic characters except . and -
|
|
64
|
-
const numbersOnly = entity.string.replace(/[^0-9.-]+/g, '');
|
|
64
|
+
const numbersOnly = entity.getProperty('string').replace(/[^0-9.-]+/g, '');
|
|
65
65
|
expect(Number(numbersOnly)).toBeCloseTo(dimensionValue);
|
|
66
|
-
expect(entity.string).toContain('Ø');
|
|
66
|
+
expect(entity.getProperty('string')).toContain('Ø');
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}, { selectedItems });
|
|
@@ -90,13 +90,13 @@ test('getPointsFromSelection returns correct sequenced points', () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
test('DiametricDimension.preview runs without error and calls createTempItem', () => {
|
|
93
|
-
const origCreateTempItem = DesignCore.Scene.
|
|
93
|
+
const origCreateTempItem = DesignCore.Scene.previewEntities.create;
|
|
94
94
|
const origPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
95
95
|
const origSeletionSet = DesignCore.Scene.selectionManager.selectionSet.selectionSet.length;
|
|
96
96
|
|
|
97
97
|
// Manual mock for createTempItem
|
|
98
98
|
const createTempItemCalls = [];
|
|
99
|
-
DesignCore.Scene.
|
|
99
|
+
DesignCore.Scene.previewEntities.create = function(type, obj) {
|
|
100
100
|
createTempItemCalls.push([type, obj]);
|
|
101
101
|
};
|
|
102
102
|
// Manual mock for pointOnScene
|
|
@@ -130,7 +130,7 @@ test('DiametricDimension.preview runs without error and calls createTempItem', (
|
|
|
130
130
|
expect(createTempItemCalls.some((call) => call[0] === dim2.type)).toBe(true);
|
|
131
131
|
|
|
132
132
|
// Restore
|
|
133
|
-
DesignCore.Scene.
|
|
133
|
+
DesignCore.Scene.previewEntities.create = origCreateTempItem;
|
|
134
134
|
DesignCore.Mouse.pointOnScene = origPointOnScene;
|
|
135
135
|
DesignCore.Scene.selectionManager.selectionSet.selectionSet.length = origSeletionSet;
|
|
136
136
|
});
|
|
@@ -196,7 +196,7 @@ AcDbDiametricDimension
|
|
|
196
196
|
expect(file.contents).toEqual(dxfString);
|
|
197
197
|
|
|
198
198
|
// create new entity from entity data to ensure all props are loaded
|
|
199
|
-
const newDimension = new DiametricDimension(dimension);
|
|
199
|
+
const newDimension = new DiametricDimension({ handle: dimension.getProperty('handle'), points: dimension.points, ...dimension });
|
|
200
200
|
file = new File();
|
|
201
201
|
newDimension.dxf(file);
|
|
202
202
|
expect(file.contents).toEqual(dxfString);
|
|
@@ -61,9 +61,9 @@ test.each(scenarios)('RadialDimension.execute handles $desc', async (scenario) =
|
|
|
61
61
|
for (const entity of dimensionBlockEntities) {
|
|
62
62
|
if (entity.type === 'Text') {
|
|
63
63
|
// remove all non-numeric characters except . and -
|
|
64
|
-
const numbersOnly = entity.string.replace(/[^0-9.-]+/g, '');
|
|
64
|
+
const numbersOnly = entity.getProperty('string').replace(/[^0-9.-]+/g, '');
|
|
65
65
|
expect(Number(numbersOnly)).toBeCloseTo(dimensionValue);
|
|
66
|
-
expect(entity.string).toContain('R');
|
|
66
|
+
expect(entity.getProperty('string')).toContain('R');
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}, { selectedItems });
|
|
@@ -90,13 +90,13 @@ test('getPointsFromSelection returns correct sequenced points', () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
test('RadialDimension.preview runs without error and calls createTempItem', () => {
|
|
93
|
-
const origCreateTempItem = DesignCore.Scene.
|
|
93
|
+
const origCreateTempItem = DesignCore.Scene.previewEntities.create;
|
|
94
94
|
const origPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
95
95
|
const origSeletionSet = DesignCore.Scene.selectionManager.selectionSet.selectionSet.length;
|
|
96
96
|
|
|
97
97
|
// Manual mock for createTempItem
|
|
98
98
|
const createTempItemCalls = [];
|
|
99
|
-
DesignCore.Scene.
|
|
99
|
+
DesignCore.Scene.previewEntities.create = function(type, obj) {
|
|
100
100
|
createTempItemCalls.push([type, obj]);
|
|
101
101
|
};
|
|
102
102
|
// Manual mock for pointOnScene
|
|
@@ -130,7 +130,7 @@ test('RadialDimension.preview runs without error and calls createTempItem', () =
|
|
|
130
130
|
expect(createTempItemCalls.some((call) => call[0] === dim2.type)).toBe(true);
|
|
131
131
|
|
|
132
132
|
// Restore
|
|
133
|
-
DesignCore.Scene.
|
|
133
|
+
DesignCore.Scene.previewEntities.create = origCreateTempItem;
|
|
134
134
|
DesignCore.Mouse.pointOnScene = origPointOnScene;
|
|
135
135
|
DesignCore.Scene.selectionManager.selectionSet.selectionSet.length = origSeletionSet;
|
|
136
136
|
});
|
|
@@ -196,7 +196,7 @@ AcDbRadialDimension
|
|
|
196
196
|
expect(file.contents).toEqual(dxfString);
|
|
197
197
|
|
|
198
198
|
// create new entity from entity data to ensure all props are loaded
|
|
199
|
-
const newDimension = new RadialDimension(dimension);
|
|
199
|
+
const newDimension = new RadialDimension({ handle: dimension.getProperty('handle'), points: dimension.points, ...dimension });
|
|
200
200
|
file = new File();
|
|
201
201
|
newDimension.dxf(file);
|
|
202
202
|
expect(file.contents).toEqual(dxfString);
|
|
@@ -40,7 +40,7 @@ test.each(scenarios)('Dimension.execute handles $desc', async (scenario) => {
|
|
|
40
40
|
// check if the text value matches the expected dimension value
|
|
41
41
|
for (const entity of dimensionBlockEntities) {
|
|
42
42
|
if (entity.type === 'Text') {
|
|
43
|
-
expect(Number(entity.string)).toBeCloseTo(dimensionValue);
|
|
43
|
+
expect(Number(entity.getProperty('string'))).toBeCloseTo(dimensionValue);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}, { selectedItems });
|
|
@@ -113,23 +113,29 @@ describe('RotatedDimension.getPointsFromSelection', () => {
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test('RotatedDimension.preview runs without error and calls createTempItem', () => {
|
|
116
|
-
const
|
|
116
|
+
const origAddAux = DesignCore.Scene.auxiliaryEntities.add;
|
|
117
|
+
const origCreateTempItem = DesignCore.Scene.previewEntities.create;
|
|
117
118
|
const origPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
118
119
|
// Manual mock for createTempItem
|
|
119
120
|
const createTempItemCalls = [];
|
|
120
|
-
DesignCore.Scene.
|
|
121
|
+
DesignCore.Scene.previewEntities.create = function(type, obj) {
|
|
121
122
|
createTempItemCalls.push([type, obj]);
|
|
122
123
|
};
|
|
124
|
+
// Manual mock for auxiliaryEntities.add
|
|
125
|
+
const addAuxCalls = [];
|
|
126
|
+
DesignCore.Scene.auxiliaryEntities.add = function(item) {
|
|
127
|
+
addAuxCalls.push(item);
|
|
128
|
+
};
|
|
123
129
|
// Manual mock for pointOnScene
|
|
124
130
|
DesignCore.Mouse.pointOnScene = function() {
|
|
125
131
|
return new Point(5, 5);
|
|
126
132
|
};
|
|
127
133
|
|
|
128
|
-
// Test with 1 point (should
|
|
134
|
+
// Test with 1 point (should add a RubberBand to auxiliaryEntities)
|
|
129
135
|
const dim1 = new RotatedDimension();
|
|
130
136
|
dim1.points = [new Point(0, 0)];
|
|
131
137
|
expect(() => dim1.preview()).not.toThrow();
|
|
132
|
-
expect(
|
|
138
|
+
expect(addAuxCalls.length).toBeGreaterThanOrEqual(1);
|
|
133
139
|
|
|
134
140
|
// Test with >1 point (should call createTempItem with this.type)
|
|
135
141
|
const dim2 = new RotatedDimension();
|
|
@@ -138,7 +144,8 @@ test('RotatedDimension.preview runs without error and calls createTempItem', ()
|
|
|
138
144
|
expect(createTempItemCalls.some((call) => call[0] === dim2.type)).toBe(true);
|
|
139
145
|
|
|
140
146
|
// Restore
|
|
141
|
-
DesignCore.Scene.
|
|
147
|
+
DesignCore.Scene.auxiliaryEntities.add = origAddAux;
|
|
148
|
+
DesignCore.Scene.previewEntities.create = origCreateTempItem;
|
|
142
149
|
DesignCore.Mouse.pointOnScene = origPointOnScene;
|
|
143
150
|
});
|
|
144
151
|
|
|
@@ -217,7 +224,7 @@ AcDbRotatedDimension
|
|
|
217
224
|
expect(file.contents).toEqual(dxfString);
|
|
218
225
|
|
|
219
226
|
// create new entity from entity data to ensure all props are loaded
|
|
220
|
-
const newDimension = new RotatedDimension(dimension);
|
|
227
|
+
const newDimension = new RotatedDimension({ handle: dimension.getProperty('handle'), points: dimension.points, ...dimension });
|
|
221
228
|
file = new File();
|
|
222
229
|
newDimension.dxf(file);
|
|
223
230
|
expect(file.contents).toEqual(dxfString);
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { Core } from '../../core/core/core.js';
|
|
2
|
+
import { Arc } from '../../core/entities/arc.js';
|
|
3
|
+
import { Point } from '../../core/entities/point.js';
|
|
4
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
5
|
+
|
|
6
|
+
import { File, withMockInput } from '../test-helpers/test-helpers.js';
|
|
1
7
|
import { jest } from '@jest/globals';
|
|
2
8
|
import { Strings } from '../../core/lib/strings.js';
|
|
3
9
|
|
|
10
|
+
// initialise core
|
|
11
|
+
new Core();
|
|
12
|
+
|
|
4
13
|
describe('Arc.execute input validation and notifications', () => {
|
|
5
14
|
let notifySpy;
|
|
6
15
|
beforeEach(() => {
|
|
@@ -69,16 +78,6 @@ describe('Arc.execute input validation and notifications', () => {
|
|
|
69
78
|
});
|
|
70
79
|
});
|
|
71
80
|
});
|
|
72
|
-
import { Core } from '../../core/core/core.js';
|
|
73
|
-
import { Arc } from '../../core/entities/arc.js';
|
|
74
|
-
import { Point } from '../../core/entities/point.js';
|
|
75
|
-
import { DesignCore } from '../../core/designCore.js';
|
|
76
|
-
import { AddState, RemoveState } from '../../core/lib/stateManager.js';
|
|
77
|
-
|
|
78
|
-
import { File, withMockInput } from '../test-helpers/test-helpers.js';
|
|
79
|
-
|
|
80
|
-
// initialise core
|
|
81
|
-
new Core();
|
|
82
81
|
|
|
83
82
|
const arcInputScenarios = [
|
|
84
83
|
{
|
|
@@ -95,7 +94,7 @@ const arcInputScenarios = [
|
|
|
95
94
|
input1: new Point(0, 0),
|
|
96
95
|
input2: new Point(10, 0),
|
|
97
96
|
input3: new Point(10, 10),
|
|
98
|
-
expectedPt2: (input1, input2, input3) => input3,
|
|
97
|
+
expectedPt2: (input1, input2, input3) => input1.project(input1.angle(input3), input1.distance(input2)),
|
|
99
98
|
expectedStart: 0,
|
|
100
99
|
expectedEnd: Math.PI / 4,
|
|
101
100
|
},
|
|
@@ -274,7 +273,7 @@ ByLayer
|
|
|
274
273
|
30
|
|
275
274
|
0.0
|
|
276
275
|
39
|
|
277
|
-
|
|
276
|
+
1
|
|
278
277
|
40
|
|
279
278
|
100
|
|
280
279
|
100
|
|
@@ -288,7 +287,7 @@ AcDbArc
|
|
|
288
287
|
expect(file.contents).toEqual(dxfString);
|
|
289
288
|
|
|
290
289
|
// create new entity from entity data to ensure all props are loaded
|
|
291
|
-
const newArc = new Arc(arc);
|
|
290
|
+
const newArc = new Arc({ handle: arc.getProperty('handle'), points: arc.points, direction: arc.getProperty('direction') });
|
|
292
291
|
file = new File();
|
|
293
292
|
newArc.dxf(file);
|
|
294
293
|
expect(file.contents).toEqual(dxfString);
|
|
@@ -327,58 +326,14 @@ test('Arc constructor handles missing/invalid data', () => {
|
|
|
327
326
|
expect(() => new Arc({ points: [null, undefined, {}] })).not.toThrow();
|
|
328
327
|
});
|
|
329
328
|
|
|
330
|
-
test('Arc.trim returns empty for no intersections', () => {
|
|
331
|
-
const arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
|
|
332
|
-
expect(arc.trim([])).toEqual([]);
|
|
333
|
-
expect(arc.trim()).toEqual([]);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
test('Arc.trim returns remove + add states when trimming between two intersections', () => {
|
|
338
|
-
const arc = new Arc({ points: [new Point(), new Point(10, 0), new Point(-10, 0)], handle: 'B2' });
|
|
339
|
-
// Intersection points on the arc.
|
|
340
|
-
const i1 = new Point(7.71, 7.71);
|
|
341
|
-
const i2 = new Point(-7.71, 7.71);
|
|
342
|
-
const intersections = [i1, i2];
|
|
343
|
-
|
|
344
|
-
// set the mouse position to mid point of the arc
|
|
345
|
-
DesignCore.Mouse.pointOnScene = () => new Point(0, 10);
|
|
346
|
-
|
|
347
|
-
const changes = arc.trim(intersections);
|
|
348
|
-
|
|
349
|
-
expect(Array.isArray(changes)).toBe(true);
|
|
350
|
-
expect(changes.length).toBeGreaterThanOrEqual(2);
|
|
351
|
-
|
|
352
|
-
expect(changes[0]).toBeInstanceOf(AddState);
|
|
353
|
-
expect(changes[1]).toBeInstanceOf(AddState);
|
|
354
|
-
expect(changes[2]).toBeInstanceOf(RemoveState);
|
|
355
|
-
|
|
356
|
-
// Verify trimmed arcs have unique handles (not the original)
|
|
357
|
-
expect(changes[0].entity.handle).toBeUndefined();
|
|
358
|
-
expect(changes[1].entity.handle).toBeUndefined();
|
|
359
|
-
|
|
360
|
-
expect(changes[0].entity.points[0].x).toBe(0);
|
|
361
|
-
expect(changes[0].entity.points[0].y).toBe(0);
|
|
362
|
-
|
|
363
|
-
expect(changes[0].entity.points[1].x).toBeCloseTo(10);
|
|
364
|
-
expect(changes[0].entity.points[1].y).toBeCloseTo(0);
|
|
365
|
-
expect(changes[0].entity.points[2].x).toBeCloseTo(7.71);
|
|
366
|
-
expect(changes[0].entity.points[2].y).toBeCloseTo(7.71);
|
|
367
|
-
|
|
368
|
-
expect(changes[1].entity.points[1].x).toBeCloseTo(-7.71);
|
|
369
|
-
expect(changes[1].entity.points[1].y).toBeCloseTo(7.71);
|
|
370
|
-
expect(changes[1].entity.points[2].x).toBe(-10);
|
|
371
|
-
expect(changes[1].entity.points[2].y).toBe(0);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
329
|
test('Arc.getRadius returns the arc radius', () => {
|
|
375
330
|
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
376
331
|
expect(arc.getRadius()).toBe(10);
|
|
377
332
|
});
|
|
378
333
|
|
|
379
|
-
test('Arc constructor loads from DXF group codes', () => {
|
|
380
|
-
const arc = new Arc({ points: [new Point(100, 100)], 40: 50, 50: 0, 51: 90 });
|
|
381
|
-
expect(arc.radius).toBe(50);
|
|
334
|
+
test('Arc constructor loads from DXF group codes via fromDxf', () => {
|
|
335
|
+
const arc = new Arc(Arc.fromDxf({ points: [new Point(100, 100)], 40: 50, 50: 0, 51: 90 }));
|
|
336
|
+
expect(arc.getProperty('radius')).toBe(50);
|
|
382
337
|
expect(arc.points[1].x).toBeCloseTo(150);
|
|
383
338
|
expect(arc.points[1].y).toBeCloseTo(100);
|
|
384
339
|
expect(arc.points[2].x).toBeCloseTo(100);
|
|
@@ -387,7 +342,7 @@ test('Arc constructor loads from DXF group codes', () => {
|
|
|
387
342
|
|
|
388
343
|
test('Arc constructor loads direction from data', () => {
|
|
389
344
|
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: -1 });
|
|
390
|
-
expect(arc.direction).toBe(-1);
|
|
345
|
+
expect(arc.getProperty('direction')).toBe(-1);
|
|
391
346
|
});
|
|
392
347
|
|
|
393
348
|
test('Arc.toPolylinePoints handles a full circle (360 degrees)', () => {
|
|
@@ -401,9 +356,9 @@ test('Arc.toPolylinePoints handles a full circle (360 degrees)', () => {
|
|
|
401
356
|
});
|
|
402
357
|
|
|
403
358
|
test('Arc.preview does not throw with 1, 2 or 3 points', () => {
|
|
404
|
-
const origCreate = DesignCore.Scene.
|
|
359
|
+
const origCreate = DesignCore.Scene.previewEntities.create;
|
|
405
360
|
const origMouse = DesignCore.Mouse.pointOnScene;
|
|
406
|
-
DesignCore.Scene.
|
|
361
|
+
DesignCore.Scene.previewEntities.create = () => {};
|
|
407
362
|
DesignCore.Mouse.pointOnScene = () => new Point(5, 5);
|
|
408
363
|
|
|
409
364
|
const arc1 = new Arc({});
|
|
@@ -418,20 +373,30 @@ test('Arc.preview does not throw with 1, 2 or 3 points', () => {
|
|
|
418
373
|
arc3.points = [new Point(0, 0), new Point(10, 0), new Point(0, 10)];
|
|
419
374
|
expect(() => arc3.preview()).not.toThrow();
|
|
420
375
|
|
|
421
|
-
DesignCore.Scene.
|
|
376
|
+
DesignCore.Scene.previewEntities.create = origCreate;
|
|
422
377
|
DesignCore.Mouse.pointOnScene = origMouse;
|
|
423
378
|
});
|
|
424
379
|
|
|
425
|
-
test('Arc.draw
|
|
426
|
-
const ctx = { arc: jest.fn(), stroke: jest.fn() };
|
|
380
|
+
test('Arc.draw calls renderer.drawShape with toPolylinePoints', () => {
|
|
427
381
|
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: 1 });
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
382
|
+
let capturedPoints;
|
|
383
|
+
const mockRenderer = {
|
|
384
|
+
drawShape(points) {
|
|
385
|
+
capturedPoints = points;
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
arc.draw(mockRenderer);
|
|
389
|
+
expect(capturedPoints).toEqual(arc.toPolylinePoints());
|
|
390
|
+
});
|
|
431
391
|
|
|
432
|
-
|
|
392
|
+
test('Arc.draw (cw) calls renderer.drawShape with toPolylinePoints', () => {
|
|
433
393
|
const arcCw = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: -1 });
|
|
434
|
-
|
|
394
|
+
let capturedPoints;
|
|
395
|
+
const mockRenderer = { drawShape(points) {
|
|
396
|
+
capturedPoints = points;
|
|
397
|
+
} };
|
|
398
|
+
arcCw.draw(mockRenderer);
|
|
399
|
+
expect(capturedPoints).toEqual(arcCw.toPolylinePoints());
|
|
435
400
|
});
|
|
436
401
|
|
|
437
402
|
test('Arc.snaps returns end and centre snap points', () => {
|
|
@@ -454,7 +419,7 @@ test('Arc.execute handles negative angle (clockwise)', async () => {
|
|
|
454
419
|
const arc = new Arc({});
|
|
455
420
|
await arc.execute();
|
|
456
421
|
expect(arc.points.length).toBe(3);
|
|
457
|
-
expect(arc.direction).toBe(-1);
|
|
422
|
+
expect(arc.getProperty('direction')).toBe(-1);
|
|
458
423
|
});
|
|
459
424
|
});
|
|
460
425
|
|
|
@@ -468,35 +433,26 @@ test('Arc.execute notifies on angle > 360', async () => {
|
|
|
468
433
|
notifySpy.mockRestore();
|
|
469
434
|
});
|
|
470
435
|
|
|
471
|
-
test('Arc.trim returns empty when only intersection is at an endpoint', () => {
|
|
472
|
-
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)] });
|
|
473
|
-
// intersection exactly at the start point — filtered out, nothing to trim
|
|
474
|
-
const changes = arc.trim([new Point(10, 0)]);
|
|
475
|
-
expect(changes).toEqual([]);
|
|
476
|
-
});
|
|
477
|
-
|
|
478
436
|
test('Arc.fromPolylinePoints round-trip preserves centre, radius and angles', () => {
|
|
479
437
|
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
480
|
-
arc.radius = 10;
|
|
481
438
|
const polyPts = arc.toPolylinePoints();
|
|
482
439
|
const rebuilt = new Arc({});
|
|
483
440
|
rebuilt.fromPolylinePoints(polyPts);
|
|
484
441
|
expect(rebuilt.points[0].x).toBeCloseTo(0, 10);
|
|
485
442
|
expect(rebuilt.points[0].y).toBeCloseTo(0, 10);
|
|
486
|
-
expect(rebuilt.radius).toBeCloseTo(10, 10);
|
|
443
|
+
expect(rebuilt.getProperty('radius')).toBeCloseTo(10, 10);
|
|
487
444
|
expect(rebuilt.startAngle()).toBeCloseTo(arc.startAngle(), 10);
|
|
488
445
|
expect(rebuilt.endAngle()).toBeCloseTo(arc.endAngle(), 10);
|
|
489
446
|
});
|
|
490
447
|
|
|
491
448
|
test('Arc.fromPolylinePoints precision drift is negligible', () => {
|
|
492
449
|
const arc = new Arc({ points: [new Point(50, 50), new Point(50, 50).project(0.7, 30), new Point(50, 50).project(2.1, 30)] });
|
|
493
|
-
arc.radius = 30;
|
|
494
450
|
const polyPts = arc.toPolylinePoints();
|
|
495
451
|
const rebuilt = new Arc({});
|
|
496
452
|
rebuilt.fromPolylinePoints(polyPts);
|
|
497
453
|
const centerDrift = Math.hypot(rebuilt.points[0].x - arc.points[0].x, rebuilt.points[0].y - arc.points[0].y);
|
|
498
454
|
expect(centerDrift).toBeLessThan(1e-10);
|
|
499
|
-
expect(Math.abs(rebuilt.radius - arc.radius)).toBeLessThan(1e-10);
|
|
455
|
+
expect(Math.abs(rebuilt.getProperty('radius') - arc.getProperty('radius'))).toBeLessThan(1e-10);
|
|
500
456
|
});
|
|
501
457
|
|
|
502
458
|
test('Arc.snaps nearest does not fire when mouse is too far from arc', () => {
|
|
@@ -531,6 +487,47 @@ test('Arc.snaps returns no tangent when fromPoint is inside the arc radius', ()
|
|
|
531
487
|
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
532
488
|
});
|
|
533
489
|
|
|
490
|
+
describe('Arc.fromDxf', () => {
|
|
491
|
+
test('projects start and end points from DXF angle codes', () => {
|
|
492
|
+
const center = new Point(0, 0);
|
|
493
|
+
const result = Arc.fromDxf({ points: [center], 40: 10, 50: 0, 51: 90 });
|
|
494
|
+
expect(result.points.length).toBe(3);
|
|
495
|
+
expect(result.points[0]).toBe(center);
|
|
496
|
+
expect(result.points[1].x).toBeCloseTo(10); // project(0°, 10)
|
|
497
|
+
expect(result.points[1].y).toBeCloseTo(0);
|
|
498
|
+
expect(result.points[2].x).toBeCloseTo(0); // project(90°, 10)
|
|
499
|
+
expect(result.points[2].y).toBeCloseTo(10);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test('uses direction from data[73], defaults to 1', () => {
|
|
503
|
+
const result1 = Arc.fromDxf({ points: [new Point(0, 0)], 40: 10, 50: 0, 51: 90, 73: -1 });
|
|
504
|
+
expect(result1.direction).toBe(-1);
|
|
505
|
+
|
|
506
|
+
const result2 = Arc.fromDxf({ points: [new Point(0, 0)], 40: 10, 50: 0, 51: 90 });
|
|
507
|
+
expect(result2.direction).toBe(1);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test('preserves all other data properties', () => {
|
|
511
|
+
const result = Arc.fromDxf({ points: [new Point(0, 0)], 40: 10, 50: 0, 51: 90, 8: 'MyLayer', 6: 'ByLayer' });
|
|
512
|
+
expect(result[8]).toBe('MyLayer');
|
|
513
|
+
expect(result[6]).toBe('ByLayer');
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test('returns data unchanged when no center point', () => {
|
|
517
|
+
const data = { 40: 10, 50: 0, 51: 90 };
|
|
518
|
+
expect(Arc.fromDxf(data)).toBe(data);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test('arc constructed via fromDxf matches arc constructed directly', () => {
|
|
522
|
+
const center = new Point(100, 100);
|
|
523
|
+
const fromDxfArc = new Arc(Arc.fromDxf({ points: [center], 40: 50, 50: 0, 51: 90 }));
|
|
524
|
+
const directArc = new Arc({ points: [center, new Point(150, 100), new Point(100, 150)] });
|
|
525
|
+
expect(fromDxfArc.getProperty('radius')).toBeCloseTo(directArc.getProperty('radius'));
|
|
526
|
+
expect(fromDxfArc.startAngle()).toBeCloseTo(directArc.startAngle());
|
|
527
|
+
expect(fromDxfArc.endAngle()).toBeCloseTo(directArc.endAngle());
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
534
531
|
test('Arc.snaps returns perpendicular snap when radial point is on the arc', () => {
|
|
535
532
|
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: 1 });
|
|
536
533
|
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|