@design-core/core 0.0.7 → 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/.github/workflows/DesignCoreQA.yml +3 -0
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +7 -7
- package/core/dimensions/angularDimension.js +32 -32
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +19 -19
- package/core/dimensions/diametricDimension.js +10 -10
- package/core/dimensions/dimension.js +42 -42
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +17 -17
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +9 -9
- package/core/dimensions/radialDimension.js +8 -8
- package/core/dimensions/rotatedDimension.js +8 -8
- package/core/entities/arc.js +22 -22
- package/core/entities/arctext.js +13 -13
- package/core/entities/circle.js +7 -7
- package/core/entities/hatch.js +28 -28
- package/core/entities/insert.js +5 -5
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +3 -3
- package/core/entities/{basePolyline.js → polylineBase.js} +18 -18
- package/core/entities/text.js +4 -4
- package/core/lib/boundingBox.js +11 -11
- package/core/lib/canvas.js +6 -4
- package/core/lib/clipboard.js +2 -2
- package/core/lib/dxf/dxf.js +11 -11
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +19 -19
- package/core/lib/inputManager.js +1 -1
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +1 -1
- package/core/lib/polylineUtils.js +9 -9
- package/core/lib/renderers/cairoRenderer.js +5 -5
- package/core/lib/renderers/canvasRenderer.js +4 -4
- package/core/lib/renderers/pdfRenderer.js +7 -7
- package/core/lib/renderers/rendererBase.js +1 -1
- package/core/lib/scene.js +1 -1
- package/core/lib/selectionManager.js +18 -18
- package/core/lib/utils.js +5 -5
- package/core/properties/property.js +1 -1
- package/core/properties/propertyManager.js +37 -37
- package/core/tables/block.js +48 -46
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +6 -6
- package/core/tools/chamferFilletBase.js +4 -4
- package/core/tools/copy.js +8 -8
- 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 +6 -6
- package/core/tools/extend.js +13 -13
- package/core/tools/fillet.js +6 -6
- package/core/tools/matchProp.js +1 -1
- package/core/tools/mirror.js +11 -11
- package/core/tools/move.js +5 -5
- package/core/tools/offset.js +29 -29
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +16 -16
- package/core/tools/scale.js +6 -6
- package/core/tools/trim.js +15 -15
- package/cspell.json +321 -0
- package/package.json +1 -1
- package/test/dimensions/alignedDimension.test.js +3 -3
- package/test/dimensions/angularDimension.test.js +7 -7
- package/test/dimensions/diametricDimension.test.js +9 -9
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +12 -12
- package/test/dimensions/{baseLinearDimension.test.js → linearDimensionBase.test.js} +7 -7
- package/test/dimensions/radialDimension.test.js +9 -9
- package/test/dimensions/rotatedDimension.test.js +4 -4
- package/test/entities/arc.test.js +10 -10
- package/test/entities/arctext.test.js +6 -6
- package/test/entities/circle.test.js +18 -18
- package/test/entities/hatch.test.js +39 -39
- package/test/entities/insert.test.js +9 -9
- package/test/entities/point.test.js +3 -3
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +131 -62
- package/test/entities/text.test.js +19 -19
- package/test/integration/exportIntegration.test.js +3 -3
- package/test/integration/userInputIntegration.test.js +5 -5
- package/test/lib/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +9 -9
- package/test/lib/inputManager.test.js +4 -4
- package/test/lib/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/snapping.test.js +1 -1
- package/test/properties/propertyManager.test.js +22 -22
- package/test/renderers/pdfRenderer.test.js +2 -2
- package/test/tables/block.test.js +63 -14
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +3 -3
- package/test/test-helpers/test-helpers.js +6 -6
- package/test/tools/chamfer.test.js +60 -60
- package/test/tools/chamferFilletBase.test.js +36 -36
- package/test/tools/copy.test.js +6 -6
- 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 +2 -2
- package/test/tools/extend.test.js +84 -84
- package/test/tools/fillet.test.js +62 -62
- package/test/tools/matchProp.test.js +6 -6
- package/test/tools/mirror.test.js +26 -26
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +65 -65
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +10 -10
- package/test/tools/scale.test.js +28 -28
- package/test/tools/trim.test.js +109 -109
- package/test/tools/zoom.test.js +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DimensionBase } from '../../core/dimensions/dimensionBase.js';
|
|
2
2
|
import { Point } from '../../core/entities/point.js';
|
|
3
3
|
import { Core } from '../../core/core/core.js';
|
|
4
4
|
import { Text } from '../../core/entities/text.js';
|
|
@@ -9,11 +9,11 @@ import { jest } from '@jest/globals';
|
|
|
9
9
|
// initialise core
|
|
10
10
|
new Core();
|
|
11
11
|
|
|
12
|
-
describe('
|
|
12
|
+
describe('DimensionBase', () => {
|
|
13
13
|
let baseDim;
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
baseDim = new
|
|
16
|
+
baseDim = new DimensionBase({});
|
|
17
17
|
baseDim.points = [new Point(1, 2, 0), new Point(3, 4, 0)];
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -164,7 +164,7 @@ describe('BaseDimension', () => {
|
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
test('constructor throws on invalid dimType', () => {
|
|
167
|
-
expect(() => new
|
|
167
|
+
expect(() => new DimensionBase({ 70: 999 })).toThrow(/Invalid Dimension Type/);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
test('getDimensionValue uses textOverride', () => {
|
|
@@ -241,11 +241,11 @@ describe('BaseDimension', () => {
|
|
|
241
241
|
|
|
242
242
|
describe('draw()', () => {
|
|
243
243
|
/**
|
|
244
|
-
* Return a
|
|
245
|
-
* @return {
|
|
244
|
+
* Return a DimensionBase with a stubbed buildDimension() that produces one Line.
|
|
245
|
+
* @return {DimensionBase}
|
|
246
246
|
*/
|
|
247
247
|
function makeDim() {
|
|
248
|
-
const dim = new
|
|
248
|
+
const dim = new DimensionBase({});
|
|
249
249
|
dim.points = [new Point(0, 0), new Point(10, 0)];
|
|
250
250
|
dim.buildDimension = jest.fn(() => {
|
|
251
251
|
return [new Line({ points: [new Point(0, 0), new Point(10, 0)] })];
|
|
@@ -256,21 +256,21 @@ describe('BaseDimension', () => {
|
|
|
256
256
|
test('draw() returns the block items array', () => {
|
|
257
257
|
const dim = makeDim();
|
|
258
258
|
const result = dim.draw({});
|
|
259
|
-
expect(result).toBe(dim.block.
|
|
259
|
+
expect(result).toBe(dim.block.entities);
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
test('draw() calls refresh() (via buildDimension) when block is empty', () => {
|
|
263
263
|
const dim = makeDim();
|
|
264
|
-
expect(dim.block.
|
|
264
|
+
expect(dim.block.entities).toHaveLength(0);
|
|
265
265
|
dim.draw({});
|
|
266
266
|
expect(dim.buildDimension).toHaveBeenCalledTimes(1);
|
|
267
|
-
expect(dim.block.
|
|
267
|
+
expect(dim.block.entities.length).toBeGreaterThan(0);
|
|
268
268
|
});
|
|
269
269
|
|
|
270
270
|
test('draw() does not call refresh() when block already has items', () => {
|
|
271
271
|
const dim = makeDim();
|
|
272
272
|
// pre-populate the block
|
|
273
|
-
dim.block.
|
|
273
|
+
dim.block.addEntity(new Line({ points: [new Point(0, 0), new Point(1, 1)] }));
|
|
274
274
|
dim.draw({});
|
|
275
275
|
expect(dim.buildDimension).not.toHaveBeenCalled();
|
|
276
276
|
});
|
|
@@ -292,7 +292,7 @@ describe('BaseDimension', () => {
|
|
|
292
292
|
test('draw() block items have BYBLOCK colour after refresh', () => {
|
|
293
293
|
const dim = makeDim();
|
|
294
294
|
dim.draw({});
|
|
295
|
-
for (const item of dim.block.
|
|
295
|
+
for (const item of dim.block.entities) {
|
|
296
296
|
expect(item.entityColour.aci).toBe(0); // BYBLOCK = ACI 0
|
|
297
297
|
}
|
|
298
298
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AlignedDimension } from '../../core/dimensions/alignedDimension.js';
|
|
2
|
-
import {
|
|
2
|
+
import { LinearDimensionBase } from '../../core/dimensions/linearDimensionBase.js';
|
|
3
3
|
import { Point } from '../../core/entities/point.js';
|
|
4
4
|
import { DesignCore } from '../../core/designCore.js';
|
|
5
5
|
import { Core } from '../../core/core/core.js';
|
|
@@ -9,16 +9,16 @@ new Core();
|
|
|
9
9
|
|
|
10
10
|
// ─── constructor ─────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
test('
|
|
13
|
-
const dim = new
|
|
14
|
-
expect(dim).toBeInstanceOf(
|
|
12
|
+
test('LinearDimensionBase can be instantiated', () => {
|
|
13
|
+
const dim = new LinearDimensionBase();
|
|
14
|
+
expect(dim).toBeInstanceOf(LinearDimensionBase);
|
|
15
15
|
expect(dim.points).toBeDefined();
|
|
16
16
|
expect(Array.isArray(dim.points)).toBe(true);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
// ─── preview ─────────────────────────────────────────────────────────────────
|
|
20
20
|
|
|
21
|
-
test('
|
|
21
|
+
test('LinearDimensionBase.preview - 0 points does nothing', () => {
|
|
22
22
|
const dim = new AlignedDimension();
|
|
23
23
|
DesignCore.Scene.auxiliaryEntities.clear();
|
|
24
24
|
DesignCore.Scene.previewEntities.clear();
|
|
@@ -28,7 +28,7 @@ test('BaseLinearDimension.preview - 0 points does nothing', () => {
|
|
|
28
28
|
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test('
|
|
31
|
+
test('LinearDimensionBase.preview - 1 point adds RubberBand to auxiliaryEntities', () => {
|
|
32
32
|
const dim = new AlignedDimension();
|
|
33
33
|
dim.points = [new Point(0, 0)];
|
|
34
34
|
|
|
@@ -44,7 +44,7 @@ test('BaseLinearDimension.preview - 1 point adds RubberBand to auxiliaryEntities
|
|
|
44
44
|
DesignCore.Scene.auxiliaryEntities.add = origAdd;
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
test('
|
|
47
|
+
test('LinearDimensionBase.preview - >1 points creates temp entity', () => {
|
|
48
48
|
const dim = new AlignedDimension();
|
|
49
49
|
dim.points = [new Point(0, 0), new Point(10, 0)];
|
|
50
50
|
|
|
@@ -17,28 +17,28 @@ const scenarios = [
|
|
|
17
17
|
|
|
18
18
|
{ desc: 'Radial dimension from circle selection - Text outside circle',
|
|
19
19
|
input: [new SingleSelection(0, new Point()), new Point(20, 0)],
|
|
20
|
-
|
|
20
|
+
selectedEntities: [new Circle({ points: [new Point(0, 0), new Point(10, 0)] })],
|
|
21
21
|
expectedDimType: 4,
|
|
22
22
|
dimensionValue: 10,
|
|
23
23
|
dimensionEntities: 6,
|
|
24
24
|
},
|
|
25
25
|
{ desc: 'Radial dimension from circle selection - Text inside circle',
|
|
26
26
|
input: [new SingleSelection(0, new Point()), new Point(0, 0)],
|
|
27
|
-
|
|
27
|
+
selectedEntities: [new Circle({ points: [new Point(0, 0), new Point(10, 0)] })],
|
|
28
28
|
expectedDimType: 4,
|
|
29
29
|
dimensionValue: 10,
|
|
30
30
|
dimensionEntities: 5,
|
|
31
31
|
},
|
|
32
32
|
{ desc: 'Radial dimension from arc selection',
|
|
33
33
|
input: [new SingleSelection(0, new Point()), new Point(20, 10)],
|
|
34
|
-
|
|
34
|
+
selectedEntities: [new Arc({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20)] })],
|
|
35
35
|
expectedDimType: 4,
|
|
36
36
|
dimensionValue: 20,
|
|
37
37
|
dimensionEntities: 6,
|
|
38
38
|
},
|
|
39
39
|
{ desc: 'Radial dimension from polyline selection',
|
|
40
40
|
input: [new SingleSelection(0, new Point(30, 10)), new Point(30, 10)],
|
|
41
|
-
|
|
41
|
+
selectedEntities: [new Polyline({ points: [new Point(0, 0), new Point(20, 0, 1), new Point(20, 20)] })],
|
|
42
42
|
expectedDimType: 4,
|
|
43
43
|
dimensionValue: 10,
|
|
44
44
|
dimensionEntities: 6,
|
|
@@ -46,7 +46,7 @@ const scenarios = [
|
|
|
46
46
|
];
|
|
47
47
|
|
|
48
48
|
test.each(scenarios)('RadialDimension.execute handles $desc', async (scenario) => {
|
|
49
|
-
const { input,
|
|
49
|
+
const { input, selectedEntities, expectedDimType, dimensionValue, dimensionEntities } = scenario;
|
|
50
50
|
|
|
51
51
|
await withMockInput(DesignCore.Scene, input, async () => {
|
|
52
52
|
const dim = new RadialDimension();
|
|
@@ -66,7 +66,7 @@ test.each(scenarios)('RadialDimension.execute handles $desc', async (scenario) =
|
|
|
66
66
|
expect(entity.getProperty('string')).toContain('R');
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
}, {
|
|
69
|
+
}, { selectedEntities });
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
test('constructor sets default properties', () => {
|
|
@@ -77,14 +77,14 @@ test('constructor sets default properties', () => {
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
test('getPointsFromSelection returns correct sequenced points', () => {
|
|
80
|
-
const
|
|
80
|
+
const centre = new Point(0, 0);
|
|
81
81
|
const textPos = new Point(10, 0);
|
|
82
82
|
// Mock item with getRadius
|
|
83
|
-
const item = { points: [
|
|
83
|
+
const item = { points: [centre], getRadius: () => 10 };
|
|
84
84
|
const result = RadialDimension.getPointsFromSelection([item], textPos);
|
|
85
85
|
expect(Array.isArray(result)).toBe(true);
|
|
86
86
|
expect(result.length).toBe(3);
|
|
87
|
-
expect(result[0].sequence).toBe(10); //
|
|
87
|
+
expect(result[0].sequence).toBe(10); // centre
|
|
88
88
|
expect(result[1].sequence).toBe(15); // radius point
|
|
89
89
|
expect(result[2].sequence).toBe(11); // text position
|
|
90
90
|
});
|
|
@@ -13,20 +13,20 @@ const scenarios = [
|
|
|
13
13
|
|
|
14
14
|
{ desc: 'Rotated dimension from point selection (angle 0)',
|
|
15
15
|
input: [0, new Point(), new Point(10, 0), new Point(5, 5)],
|
|
16
|
-
|
|
16
|
+
selectedEntities: [],
|
|
17
17
|
expectedDimType: 0,
|
|
18
18
|
dimensionValue: 10,
|
|
19
19
|
},
|
|
20
20
|
{ desc: 'Rotated dimension from point selection (angle 90)',
|
|
21
21
|
input: [90, new Point(), new Point(0, 10), new Point(5, 5)],
|
|
22
|
-
|
|
22
|
+
selectedEntities: [],
|
|
23
23
|
expectedDimType: 0,
|
|
24
24
|
dimensionValue: 10,
|
|
25
25
|
},
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
test.each(scenarios)('Dimension.execute handles $desc', async (scenario) => {
|
|
29
|
-
const { input,
|
|
29
|
+
const { input, selectedEntities, expectedDimType, dimensionValue } = scenario;
|
|
30
30
|
|
|
31
31
|
await withMockInput(DesignCore.Scene, input, async () => {
|
|
32
32
|
const dim = new RotatedDimension();
|
|
@@ -43,7 +43,7 @@ test.each(scenarios)('Dimension.execute handles $desc', async (scenario) => {
|
|
|
43
43
|
expect(Number(entity.getProperty('string'))).toBeCloseTo(dimensionValue);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
}, {
|
|
46
|
+
}, { selectedEntities });
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
test('constructor sets default properties', () => {
|
|
@@ -58,7 +58,7 @@ describe('Arc.execute input validation and notifications', () => {
|
|
|
58
58
|
test('Notifies on invalid point input (duplicate)', async () => {
|
|
59
59
|
const input1 = new Point(0, 0);
|
|
60
60
|
const input2 = new Point(10, 0);
|
|
61
|
-
const input3 = new Point(0, 0); // Same as
|
|
61
|
+
const input3 = new Point(0, 0); // Same as centre
|
|
62
62
|
await withMockInput(DesignCore.Scene, [input1, input2, input3, new Point(10, 10)], async () => {
|
|
63
63
|
const arc = new Arc({});
|
|
64
64
|
await arc.execute();
|
|
@@ -450,8 +450,8 @@ test('Arc.fromPolylinePoints precision drift is negligible', () => {
|
|
|
450
450
|
const polyPts = arc.toPolylinePoints();
|
|
451
451
|
const rebuilt = new Arc({});
|
|
452
452
|
rebuilt.fromPolylinePoints(polyPts);
|
|
453
|
-
const
|
|
454
|
-
expect(
|
|
453
|
+
const centreDrift = Math.hypot(rebuilt.points[0].x - arc.points[0].x, rebuilt.points[0].y - arc.points[0].y);
|
|
454
|
+
expect(centreDrift).toBeLessThan(1e-10);
|
|
455
455
|
expect(Math.abs(rebuilt.getProperty('radius') - arc.getProperty('radius'))).toBeLessThan(1e-10);
|
|
456
456
|
});
|
|
457
457
|
|
|
@@ -489,10 +489,10 @@ test('Arc.snaps returns no tangent when fromPoint is inside the arc radius', ()
|
|
|
489
489
|
|
|
490
490
|
describe('Arc.fromDxf', () => {
|
|
491
491
|
test('projects start and end points from DXF angle codes', () => {
|
|
492
|
-
const
|
|
493
|
-
const result = Arc.fromDxf({ points: [
|
|
492
|
+
const centre = new Point(0, 0);
|
|
493
|
+
const result = Arc.fromDxf({ points: [centre], 40: 10, 50: 0, 51: 90 });
|
|
494
494
|
expect(result.points.length).toBe(3);
|
|
495
|
-
expect(result.points[0]).toBe(
|
|
495
|
+
expect(result.points[0]).toBe(centre);
|
|
496
496
|
expect(result.points[1].x).toBeCloseTo(10); // project(0°, 10)
|
|
497
497
|
expect(result.points[1].y).toBeCloseTo(0);
|
|
498
498
|
expect(result.points[2].x).toBeCloseTo(0); // project(90°, 10)
|
|
@@ -513,15 +513,15 @@ describe('Arc.fromDxf', () => {
|
|
|
513
513
|
expect(result[6]).toBe('ByLayer');
|
|
514
514
|
});
|
|
515
515
|
|
|
516
|
-
test('returns data unchanged when no
|
|
516
|
+
test('returns data unchanged when no centre point', () => {
|
|
517
517
|
const data = { 40: 10, 50: 0, 51: 90 };
|
|
518
518
|
expect(Arc.fromDxf(data)).toBe(data);
|
|
519
519
|
});
|
|
520
520
|
|
|
521
521
|
test('arc constructed via fromDxf matches arc constructed directly', () => {
|
|
522
|
-
const
|
|
523
|
-
const fromDxfArc = new Arc(Arc.fromDxf({ points: [
|
|
524
|
-
const directArc = new Arc({ points: [
|
|
522
|
+
const centre = new Point(100, 100);
|
|
523
|
+
const fromDxfArc = new Arc(Arc.fromDxf({ points: [centre], 40: 50, 50: 0, 51: 90 }));
|
|
524
|
+
const directArc = new Arc({ points: [centre, new Point(150, 100), new Point(100, 150)] });
|
|
525
525
|
expect(fromDxfArc.getProperty('radius')).toBeCloseTo(directArc.getProperty('radius'));
|
|
526
526
|
expect(fromDxfArc.startAngle()).toBeCloseTo(directArc.startAngle());
|
|
527
527
|
expect(fromDxfArc.endAngle()).toBeCloseTo(directArc.endAngle());
|
|
@@ -16,14 +16,14 @@ const arcInputScenarios = [
|
|
|
16
16
|
{
|
|
17
17
|
desc: 'two points and an angle',
|
|
18
18
|
input: [new SingleSelection(0, new Point(5, 0)), 2.5, 'Test Arc'],
|
|
19
|
-
|
|
19
|
+
selectedEntities: [new Arc({ points: [new Point(), new Point(100, 0), new Point(-100, 0)] })],
|
|
20
20
|
expectedTextHeight: 2.5,
|
|
21
21
|
expectedText: 'Test Arc',
|
|
22
22
|
},
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
test.each(arcInputScenarios)('ArcText.execute handles $desc', async (scenario) => {
|
|
26
|
-
const { input,
|
|
26
|
+
const { input, selectedEntities, expectedTextHeight, expectedText } = scenario;
|
|
27
27
|
|
|
28
28
|
await withMockInput(DesignCore.Scene, input, async () => {
|
|
29
29
|
const arcText = new ArcAlignedText();
|
|
@@ -37,7 +37,7 @@ test.each(arcInputScenarios)('ArcText.execute handles $desc', async (scenario) =
|
|
|
37
37
|
expect(arcText.getProperty('radius')).toBe(100);
|
|
38
38
|
expect(arcText.startAngle()).toBe(0);
|
|
39
39
|
expect(arcText.endAngle()).toBeCloseTo(3.14159);
|
|
40
|
-
}, {
|
|
40
|
+
}, { selectedEntities });
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test('ArcAlignedCharacter', () => {
|
|
@@ -105,7 +105,7 @@ test('ArcText getArcAlignedCharacters', () => {
|
|
|
105
105
|
// offsetFromLeft 0 - mm
|
|
106
106
|
// textReversed 0 = forward, 1 = reversed
|
|
107
107
|
// textOrientation 1 = outward, 2 = inward
|
|
108
|
-
// textAlignment 1 = fit to arc, 2 = left align, 3 = right align, 4 =
|
|
108
|
+
// textAlignment 1 = fit to arc, 2 = left align, 3 = right align, 4 = centre
|
|
109
109
|
// ArcSide convex = 1, concave = 2
|
|
110
110
|
let chars = [];
|
|
111
111
|
|
|
@@ -387,7 +387,7 @@ test('ArcText snaps with empty string returns only centre snap', () => {
|
|
|
387
387
|
});
|
|
388
388
|
|
|
389
389
|
test('ArcText closestPoint returns distance 0.1 when within height * 0.35', () => {
|
|
390
|
-
// Single char 'T'
|
|
390
|
+
// Single char 'T' centre-aligned sits at (0, 101.25); height = 2.5, threshold = 0.875
|
|
391
391
|
const arcText = new ArcAlignedText({ points: [new Point(0, 0)], string: 'T', radius: 100 });
|
|
392
392
|
const chars = arcText.getArcAlignedCharacters();
|
|
393
393
|
const charPos = chars[0].position;
|
|
@@ -408,7 +408,7 @@ test('ArcText getArcAlignedCharacters fit-to-arc single character places at midp
|
|
|
408
408
|
});
|
|
409
409
|
|
|
410
410
|
test('ArcText getArcAlignedCharacters single character is placed at arc midpoint', () => {
|
|
411
|
-
// Single char with
|
|
411
|
+
// Single char with centre alignment: charOffsetAngles = [0], stringStartPoint = arcMidPoint
|
|
412
412
|
// arcMidAngle(0, PI) = PI/2 → project(PI/2, 101.25) = (0, 101.25), angle = PI/2 - PI/2 = 0
|
|
413
413
|
const arcText = new ArcAlignedText({ points: [new Point(0, 0)], string: 'X', radius: 100 });
|
|
414
414
|
const chars = arcText.getArcAlignedCharacters();
|
|
@@ -128,22 +128,22 @@ test('Test Circle.toPolylinePoints', () => {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
test('Circle.execute creates circle from diameter input', async () => {
|
|
131
|
-
const
|
|
131
|
+
const centre = new Point(0, 0);
|
|
132
132
|
|
|
133
|
-
await withMockInput(DesignCore.Scene, [
|
|
133
|
+
await withMockInput(DesignCore.Scene, [centre, 'Diameter', 20], async () => {
|
|
134
134
|
const circle = new Circle({});
|
|
135
135
|
await circle.execute();
|
|
136
136
|
|
|
137
|
-
expect(circle.points[0]).toBe(
|
|
137
|
+
expect(circle.points[0]).toBe(centre);
|
|
138
138
|
expect(circle.getRadius()).toBeCloseTo(10);
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
test('Circle.execute re-prompts on zero or negative radius', async () => {
|
|
143
|
-
const
|
|
143
|
+
const centre = new Point(0, 0);
|
|
144
144
|
|
|
145
145
|
// 0 and -5 are rejected; 10 is accepted
|
|
146
|
-
await withMockInput(DesignCore.Scene, [
|
|
146
|
+
await withMockInput(DesignCore.Scene, [centre, 0, -5, 10], async () => {
|
|
147
147
|
const circle = new Circle({});
|
|
148
148
|
await circle.execute();
|
|
149
149
|
|
|
@@ -152,10 +152,10 @@ test('Circle.execute re-prompts on zero or negative radius', async () => {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
test('Circle.execute re-prompts on zero or negative diameter', async () => {
|
|
155
|
-
const
|
|
155
|
+
const centre = new Point(0, 0);
|
|
156
156
|
|
|
157
157
|
// 'Diameter', then 0 and -20 rejected, then 20 accepted (radius = 10)
|
|
158
|
-
await withMockInput(DesignCore.Scene, [
|
|
158
|
+
await withMockInput(DesignCore.Scene, [centre, 'Diameter', 0, -20, 20], async () => {
|
|
159
159
|
const circle = new Circle({});
|
|
160
160
|
await circle.execute();
|
|
161
161
|
|
|
@@ -175,8 +175,8 @@ test('Circle constructor loads radius from DXF group code 40 via fromDxf', () =>
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
test('Circle.execute creates circle from numeric radius input', async () => {
|
|
178
|
-
const
|
|
179
|
-
await withMockInput(DesignCore.Scene, [
|
|
178
|
+
const centre = new Point(0, 0);
|
|
179
|
+
await withMockInput(DesignCore.Scene, [centre, 25], async () => {
|
|
180
180
|
const circle = new Circle({});
|
|
181
181
|
await circle.execute();
|
|
182
182
|
expect(circle.getRadius()).toBeCloseTo(25);
|
|
@@ -185,8 +185,8 @@ test('Circle.execute creates circle from numeric radius input', async () => {
|
|
|
185
185
|
|
|
186
186
|
test('Circle.execute re-prompts on zero-distance point input', async () => {
|
|
187
187
|
const notifySpy = jest.spyOn(DesignCore.Core, 'notify').mockImplementation(() => {});
|
|
188
|
-
const
|
|
189
|
-
await withMockInput(DesignCore.Scene, [
|
|
188
|
+
const centre = new Point(0, 0);
|
|
189
|
+
await withMockInput(DesignCore.Scene, [centre, new Point(0, 0), new Point(10, 0)], async () => {
|
|
190
190
|
const circle = new Circle({});
|
|
191
191
|
await circle.execute();
|
|
192
192
|
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.NONZERO));
|
|
@@ -296,10 +296,10 @@ test('Circle.snaps returns two tangent snaps from outside', () => {
|
|
|
296
296
|
|
|
297
297
|
describe('Circle.fromDxf', () => {
|
|
298
298
|
test('projects edge point from DXF radius code', () => {
|
|
299
|
-
const
|
|
300
|
-
const result = Circle.fromDxf({ points: [
|
|
299
|
+
const centre = new Point(0, 0);
|
|
300
|
+
const result = Circle.fromDxf({ points: [centre], 40: 10 });
|
|
301
301
|
expect(result.points.length).toBe(2);
|
|
302
|
-
expect(result.points[0]).toBe(
|
|
302
|
+
expect(result.points[0]).toBe(centre);
|
|
303
303
|
expect(result.points[1].x).toBeCloseTo(10);
|
|
304
304
|
expect(result.points[1].y).toBeCloseTo(0);
|
|
305
305
|
});
|
|
@@ -309,7 +309,7 @@ describe('Circle.fromDxf', () => {
|
|
|
309
309
|
expect(result[8]).toBe('MyLayer');
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
-
test('returns data unchanged when no
|
|
312
|
+
test('returns data unchanged when no centre point', () => {
|
|
313
313
|
const data = { 40: 10 };
|
|
314
314
|
expect(Circle.fromDxf(data)).toBe(data);
|
|
315
315
|
});
|
|
@@ -320,9 +320,9 @@ describe('Circle.fromDxf', () => {
|
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
test('circle constructed via fromDxf matches circle constructed directly', () => {
|
|
323
|
-
const
|
|
324
|
-
const fromDxfCircle = new Circle(Circle.fromDxf({ points: [
|
|
325
|
-
const directCircle = new Circle({ points: [
|
|
323
|
+
const centre = new Point(100, 100);
|
|
324
|
+
const fromDxfCircle = new Circle(Circle.fromDxf({ points: [centre], 40: 50 }));
|
|
325
|
+
const directCircle = new Circle({ points: [centre, new Point(150, 100)] });
|
|
326
326
|
expect(fromDxfCircle.getRadius()).toBeCloseTo(directCircle.getRadius());
|
|
327
327
|
expect(fromDxfCircle.points[0].x).toBeCloseTo(directCircle.points[0].x);
|
|
328
328
|
expect(fromDxfCircle.points[0].y).toBeCloseTo(directCircle.points[0].y);
|
|
@@ -2,7 +2,7 @@ import { Hatch } from '../../core/entities/hatch.js';
|
|
|
2
2
|
import { Point } from '../../core/entities/point.js';
|
|
3
3
|
import { DesignCore } from '../../core/designCore.js';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { PolylineBase } from '../../core/entities/polylineBase.js';
|
|
6
6
|
import { Circle } from '../../core/entities/circle.js';
|
|
7
7
|
import { Line } from '../../core/entities/line.js';
|
|
8
8
|
import { Polyline } from '../../core/entities/polyline.js';
|
|
@@ -17,7 +17,7 @@ import { Core } from '../../core/core/core.js';
|
|
|
17
17
|
new Core();
|
|
18
18
|
|
|
19
19
|
const points = [new Point(100, 100, 1), new Point(200, 100, 1)];
|
|
20
|
-
const boundaryShape = new
|
|
20
|
+
const boundaryShape = new PolylineBase({ points: points });
|
|
21
21
|
|
|
22
22
|
const hatch = new Hatch({ handle: '1' });
|
|
23
23
|
hatch.setProperty('childEntities', [boundaryShape]);
|
|
@@ -56,10 +56,10 @@ test.each(hatchInputScenarios)('Hatch.execute handles $desc', async (scenario) =
|
|
|
56
56
|
|
|
57
57
|
await withMockInput(DesignCore.Scene, [true], async () => {
|
|
58
58
|
// Manual mock for selected items
|
|
59
|
-
DesignCore.Scene.selectionManager.
|
|
59
|
+
DesignCore.Scene.selectionManager.selectedEntities = [];
|
|
60
60
|
|
|
61
61
|
for (let i = 0; i < boundaryItems.length; i++) {
|
|
62
|
-
DesignCore.Scene.selectionManager.
|
|
62
|
+
DesignCore.Scene.selectionManager.selectedEntities.push(boundaryItems[i]);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const hatch = new Hatch({ patternName: pattern, scale: scale, angle: angle });
|
|
@@ -448,60 +448,60 @@ test('Test Hatch.processBoundaryData', () => {
|
|
|
448
448
|
|
|
449
449
|
test('Test Hatch.processSelection', () => {
|
|
450
450
|
// create selected items defining a square
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
451
|
+
let selectedEntities = [];
|
|
452
|
+
selectedEntities.push(new Line({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
453
|
+
selectedEntities.push(new Line({ points: [new Point(200, 100), new Point(200, 200)] }));
|
|
454
|
+
selectedEntities.push(new Line({ points: [new Point(200, 200), new Point(100, 200)] }));
|
|
455
|
+
selectedEntities.push(new Line({ points: [new Point(100, 200), new Point(100, 100)] }));
|
|
456
456
|
|
|
457
457
|
let hatch = new Hatch();
|
|
458
|
-
let boundaryData = hatch.processSelection(
|
|
458
|
+
let boundaryData = hatch.processSelection(selectedEntities);
|
|
459
459
|
expect(boundaryData.length).toEqual(1);
|
|
460
460
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
461
461
|
|
|
462
462
|
|
|
463
463
|
// create selected items defining a circle
|
|
464
|
-
|
|
465
|
-
|
|
464
|
+
selectedEntities = [];
|
|
465
|
+
selectedEntities.push(new Circle({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
466
466
|
|
|
467
467
|
hatch = new Hatch();
|
|
468
|
-
boundaryData = hatch.processSelection(
|
|
468
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
469
469
|
expect(boundaryData.length).toEqual(1);
|
|
470
470
|
expect(boundaryData[0].points.length).toEqual(2);
|
|
471
471
|
|
|
472
472
|
// create selected items defining a square (polyline)
|
|
473
|
-
|
|
474
|
-
|
|
473
|
+
selectedEntities = [];
|
|
474
|
+
selectedEntities.push(new Polyline({
|
|
475
475
|
points: [new Point(100, 100), new Point(200, 100), new Point(200, 200),
|
|
476
476
|
new Point(100, 200), new Point(100, 100)],
|
|
477
477
|
}));
|
|
478
478
|
|
|
479
479
|
hatch = new Hatch();
|
|
480
|
-
boundaryData = hatch.processSelection(
|
|
480
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
481
481
|
expect(boundaryData.length).toEqual(1);
|
|
482
482
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
483
483
|
|
|
484
484
|
// create selected items defining a pill shape
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
485
|
+
selectedEntities = [];
|
|
486
|
+
selectedEntities.push(new Line({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
487
|
+
selectedEntities.push(new Arc({ points: [new Point(200, 150), new Point(200, 100), new Point(200, 200)] }));
|
|
488
|
+
selectedEntities.push(new Line({ points: [new Point(200, 200), new Point(100, 200)] }));
|
|
489
|
+
selectedEntities.push(new Arc({ points: [new Point(100, 150), new Point(100, 200), new Point(100, 100)] }));
|
|
490
490
|
|
|
491
491
|
hatch = new Hatch();
|
|
492
|
-
boundaryData = hatch.processSelection(
|
|
492
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
493
493
|
expect(boundaryData.length).toEqual(1);
|
|
494
494
|
// 2 lines + 2 arcs: each shared connection point is merged (not duplicated),
|
|
495
495
|
// so the result is 4 unique vertices, not 5
|
|
496
496
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
497
497
|
|
|
498
498
|
// Test a selection with invalid items
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
499
|
+
selectedEntities = [];
|
|
500
|
+
selectedEntities.push(new Text({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
501
|
+
selectedEntities.push(new Circle({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
502
502
|
|
|
503
503
|
hatch = new Hatch();
|
|
504
|
-
boundaryData = hatch.processSelection(
|
|
504
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
505
505
|
expect(boundaryData.length).toEqual(1);
|
|
506
506
|
expect(boundaryData[0].points.length).toEqual(2);
|
|
507
507
|
});
|
|
@@ -539,7 +539,7 @@ function makeMockCtx() {
|
|
|
539
539
|
|
|
540
540
|
test('Test Hatch.draw solid fill renders filled boundary', () => {
|
|
541
541
|
const solidHatch = new Hatch({ patternName: 'SOLID' });
|
|
542
|
-
solidHatch.setProperty('childEntities', [new
|
|
542
|
+
solidHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
543
543
|
|
|
544
544
|
const ctx = makeMockCtx();
|
|
545
545
|
solidHatch.draw(ctx);
|
|
@@ -549,7 +549,7 @@ test('Test Hatch.draw solid fill renders filled boundary', () => {
|
|
|
549
549
|
|
|
550
550
|
test('Test Hatch.draw unknown pattern does not stroke pattern lines', () => {
|
|
551
551
|
const unknownHatch = new Hatch({ patternName: 'ANSI31' });
|
|
552
|
-
unknownHatch.setProperty('childEntities', [new
|
|
552
|
+
unknownHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
553
553
|
// Override pattern to a nonexistent name
|
|
554
554
|
unknownHatch.setProperty('patternName', 'NOTAPATTERN');
|
|
555
555
|
|
|
@@ -567,7 +567,7 @@ test('Test Hatch.draw tight bound line count - square boundary', () => {
|
|
|
567
567
|
// yIncrement = ceil(70.71 / 3.175) = 23 → loop i=-23..22 = 46 raw lines
|
|
568
568
|
// i=-23 (|ly|=73.025 > halfY=70.71) misses the square → 45 clipped segments
|
|
569
569
|
const squareHatch = new Hatch({ patternName: 'ANSI31' });
|
|
570
|
-
squareHatch.setProperty('childEntities', [new
|
|
570
|
+
squareHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
571
571
|
const ctx = makeMockCtx();
|
|
572
572
|
squareHatch.draw(ctx);
|
|
573
573
|
expect(ctx.getMoveCalls()).toBe(45);
|
|
@@ -580,7 +580,7 @@ test('Test Hatch.draw tight bound line count - flat boundary', () => {
|
|
|
580
580
|
// yIncrement = ceil(38.89 / 3.175) = 13 → loop i=-13..12 = 26 raw lines
|
|
581
581
|
// i=-13 (|ly|=41.275 > halfY=38.89) misses the flat box → 25 clipped segments
|
|
582
582
|
const flatHatch = new Hatch({ patternName: 'ANSI31' });
|
|
583
|
-
flatHatch.setProperty('childEntities', [new
|
|
583
|
+
flatHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 10), new Point(0, 10)] })]);
|
|
584
584
|
const ctx = makeMockCtx();
|
|
585
585
|
flatHatch.draw(ctx);
|
|
586
586
|
expect(ctx.getMoveCalls()).toBe(25);
|
|
@@ -589,10 +589,10 @@ test('Test Hatch.draw tight bound line count - flat boundary', () => {
|
|
|
589
589
|
test('Test Hatch.draw tight bound produces fewer lines for flat boundary', () => {
|
|
590
590
|
// A flat boundary (100x10) should produce fewer lines than a square (100x100).
|
|
591
591
|
const squareHatch = new Hatch({ patternName: 'ANSI31' });
|
|
592
|
-
squareHatch.setProperty('childEntities', [new
|
|
592
|
+
squareHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
593
593
|
|
|
594
594
|
const flatHatch = new Hatch({ patternName: 'ANSI31' });
|
|
595
|
-
flatHatch.setProperty('childEntities', [new
|
|
595
|
+
flatHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 10), new Point(0, 10)] })]);
|
|
596
596
|
|
|
597
597
|
const squareCtx = makeMockCtx();
|
|
598
598
|
squareHatch.draw(squareCtx);
|
|
@@ -614,14 +614,14 @@ test('Hatch.buildPatternCache no childEntities sets cachedPattern to empty array
|
|
|
614
614
|
|
|
615
615
|
test('Hatch.buildPatternCache solid fill sets cachedPattern to empty array', () => {
|
|
616
616
|
const h = new Hatch({ patternName: 'SOLID' });
|
|
617
|
-
h.setProperty('childEntities', [new
|
|
617
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
618
618
|
h.buildPatternCache();
|
|
619
619
|
expect(h.cachedPattern).toEqual([]);
|
|
620
620
|
});
|
|
621
621
|
|
|
622
622
|
test('Hatch.buildPatternCache unknown pattern sets cachedPattern to empty array', () => {
|
|
623
623
|
const h = new Hatch({ patternName: 'HONEY' });
|
|
624
|
-
h.setProperty('childEntities', [new
|
|
624
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
625
625
|
h.setProperty('patternName', 'NOTAPATTERN');
|
|
626
626
|
h.buildPatternCache();
|
|
627
627
|
expect(h.cachedPattern).toEqual([]);
|
|
@@ -631,9 +631,9 @@ test('Hatch.buildPatternCache unknown pattern sets cachedPattern to empty array'
|
|
|
631
631
|
|
|
632
632
|
/**
|
|
633
633
|
* Build a 100×100 square boundary
|
|
634
|
-
* @return {Array} array containing one
|
|
634
|
+
* @return {Array} array containing one PolylineBase boundary
|
|
635
635
|
*/
|
|
636
|
-
const makeSquare = () => [new
|
|
636
|
+
const makeSquare = () => [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })];
|
|
637
637
|
|
|
638
638
|
/**
|
|
639
639
|
* Sum all segment counts across all families in a cached hatch
|
|
@@ -731,9 +731,9 @@ test('Hatch.buildPatternCache scale change invalidates and rebuilds cache with u
|
|
|
731
731
|
|
|
732
732
|
/**
|
|
733
733
|
* A 20×20 inner square hole centred in the 100×100 outer square
|
|
734
|
-
* @return {
|
|
734
|
+
* @return {PolylineBase} inner boundary polyline
|
|
735
735
|
*/
|
|
736
|
-
const makeInnerSquare = () => new
|
|
736
|
+
const makeInnerSquare = () => new PolylineBase({ points: [new Point(40, 40), new Point(60, 40), new Point(60, 60), new Point(40, 60)] });
|
|
737
737
|
|
|
738
738
|
test('Hatch.buildPatternCache ANSI31 nested boundary: no segment midpoint inside inner hole', () => {
|
|
739
739
|
// Even-odd rule: a ray from inside the hole crosses 2 boundaries → even → not hatched
|
|
@@ -951,7 +951,7 @@ test('Hatch.setProperty patternName updates pattern and solid flag via setter',
|
|
|
951
951
|
test('Hatch.setProperty points translation shifts child entity positions', () => {
|
|
952
952
|
const h = new Hatch({ patternName: 'ANSI31' });
|
|
953
953
|
// h.points = [Point(0,0), Point(1,1)] — 45° angle set by constructor
|
|
954
|
-
h.setProperty('childEntities', [new
|
|
954
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
955
955
|
|
|
956
956
|
// New points: same 45° angle (delta angle = 0 → no rotation), origin shifted by (5, 0)
|
|
957
957
|
h.setProperty('points', [new Point(5, 0), new Point(6, 1)]);
|