@design-core/core 0.0.7 → 51.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 +210 -40
- package/core/entities/insert.js +11 -6
- 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 +13 -6
- 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/entityProperties.js +2 -1
- package/core/properties/flags.js +12 -0
- package/core/properties/property.js +8 -3
- package/core/properties/propertyManager.js +52 -37
- package/core/tables/block.js +50 -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 +323 -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 +362 -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/clipboard.test.js +73 -0
- 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/flags.test.js +24 -0
- package/test/properties/propertyManager.test.js +93 -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 +15 -17
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Hatch } from '../../core/entities/hatch.js';
|
|
2
|
+
import { PatternLine } from '../../core/lib/patterns.js';
|
|
2
3
|
import { Point } from '../../core/entities/point.js';
|
|
3
4
|
import { DesignCore } from '../../core/designCore.js';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { PolylineBase } from '../../core/entities/polylineBase.js';
|
|
6
7
|
import { Circle } from '../../core/entities/circle.js';
|
|
7
8
|
import { Line } from '../../core/entities/line.js';
|
|
8
9
|
import { Polyline } from '../../core/entities/polyline.js';
|
|
@@ -10,6 +11,8 @@ import { Arc } from '../../core/entities/arc.js';
|
|
|
10
11
|
import { Text } from '../../core/entities/text.js';
|
|
11
12
|
|
|
12
13
|
import { File, withMockInput } from '../test-helpers/test-helpers.js';
|
|
14
|
+
import { Logging } from '../../core/lib/logging.js';
|
|
15
|
+
import { jest } from '@jest/globals';
|
|
13
16
|
|
|
14
17
|
import { Core } from '../../core/core/core.js';
|
|
15
18
|
|
|
@@ -17,7 +20,7 @@ import { Core } from '../../core/core/core.js';
|
|
|
17
20
|
new Core();
|
|
18
21
|
|
|
19
22
|
const points = [new Point(100, 100, 1), new Point(200, 100, 1)];
|
|
20
|
-
const boundaryShape = new
|
|
23
|
+
const boundaryShape = new PolylineBase({ points: points });
|
|
21
24
|
|
|
22
25
|
const hatch = new Hatch({ handle: '1' });
|
|
23
26
|
hatch.setProperty('childEntities', [boundaryShape]);
|
|
@@ -56,10 +59,10 @@ test.each(hatchInputScenarios)('Hatch.execute handles $desc', async (scenario) =
|
|
|
56
59
|
|
|
57
60
|
await withMockInput(DesignCore.Scene, [true], async () => {
|
|
58
61
|
// Manual mock for selected items
|
|
59
|
-
DesignCore.Scene.selectionManager.
|
|
62
|
+
DesignCore.Scene.selectionManager.selectedEntities = [];
|
|
60
63
|
|
|
61
64
|
for (let i = 0; i < boundaryItems.length; i++) {
|
|
62
|
-
DesignCore.Scene.selectionManager.
|
|
65
|
+
DesignCore.Scene.selectionManager.selectedEntities.push(boundaryItems[i]);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
const hatch = new Hatch({ patternName: pattern, scale: scale, angle: angle });
|
|
@@ -448,60 +451,60 @@ test('Test Hatch.processBoundaryData', () => {
|
|
|
448
451
|
|
|
449
452
|
test('Test Hatch.processSelection', () => {
|
|
450
453
|
// create selected items defining a square
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
let selectedEntities = [];
|
|
455
|
+
selectedEntities.push(new Line({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
456
|
+
selectedEntities.push(new Line({ points: [new Point(200, 100), new Point(200, 200)] }));
|
|
457
|
+
selectedEntities.push(new Line({ points: [new Point(200, 200), new Point(100, 200)] }));
|
|
458
|
+
selectedEntities.push(new Line({ points: [new Point(100, 200), new Point(100, 100)] }));
|
|
456
459
|
|
|
457
460
|
let hatch = new Hatch();
|
|
458
|
-
let boundaryData = hatch.processSelection(
|
|
461
|
+
let boundaryData = hatch.processSelection(selectedEntities);
|
|
459
462
|
expect(boundaryData.length).toEqual(1);
|
|
460
463
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
461
464
|
|
|
462
465
|
|
|
463
466
|
// create selected items defining a circle
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
selectedEntities = [];
|
|
468
|
+
selectedEntities.push(new Circle({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
466
469
|
|
|
467
470
|
hatch = new Hatch();
|
|
468
|
-
boundaryData = hatch.processSelection(
|
|
471
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
469
472
|
expect(boundaryData.length).toEqual(1);
|
|
470
473
|
expect(boundaryData[0].points.length).toEqual(2);
|
|
471
474
|
|
|
472
475
|
// create selected items defining a square (polyline)
|
|
473
|
-
|
|
474
|
-
|
|
476
|
+
selectedEntities = [];
|
|
477
|
+
selectedEntities.push(new Polyline({
|
|
475
478
|
points: [new Point(100, 100), new Point(200, 100), new Point(200, 200),
|
|
476
479
|
new Point(100, 200), new Point(100, 100)],
|
|
477
480
|
}));
|
|
478
481
|
|
|
479
482
|
hatch = new Hatch();
|
|
480
|
-
boundaryData = hatch.processSelection(
|
|
483
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
481
484
|
expect(boundaryData.length).toEqual(1);
|
|
482
485
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
483
486
|
|
|
484
487
|
// create selected items defining a pill shape
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
selectedEntities = [];
|
|
489
|
+
selectedEntities.push(new Line({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
490
|
+
selectedEntities.push(new Arc({ points: [new Point(200, 150), new Point(200, 100), new Point(200, 200)] }));
|
|
491
|
+
selectedEntities.push(new Line({ points: [new Point(200, 200), new Point(100, 200)] }));
|
|
492
|
+
selectedEntities.push(new Arc({ points: [new Point(100, 150), new Point(100, 200), new Point(100, 100)] }));
|
|
490
493
|
|
|
491
494
|
hatch = new Hatch();
|
|
492
|
-
boundaryData = hatch.processSelection(
|
|
495
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
493
496
|
expect(boundaryData.length).toEqual(1);
|
|
494
497
|
// 2 lines + 2 arcs: each shared connection point is merged (not duplicated),
|
|
495
498
|
// so the result is 4 unique vertices, not 5
|
|
496
499
|
expect(boundaryData[0].points.length).toEqual(4);
|
|
497
500
|
|
|
498
501
|
// Test a selection with invalid items
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
+
selectedEntities = [];
|
|
503
|
+
selectedEntities.push(new Text({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
504
|
+
selectedEntities.push(new Circle({ points: [new Point(100, 100), new Point(200, 100)] }));
|
|
502
505
|
|
|
503
506
|
hatch = new Hatch();
|
|
504
|
-
boundaryData = hatch.processSelection(
|
|
507
|
+
boundaryData = hatch.processSelection(selectedEntities);
|
|
505
508
|
expect(boundaryData.length).toEqual(1);
|
|
506
509
|
expect(boundaryData[0].points.length).toEqual(2);
|
|
507
510
|
});
|
|
@@ -539,7 +542,7 @@ function makeMockCtx() {
|
|
|
539
542
|
|
|
540
543
|
test('Test Hatch.draw solid fill renders filled boundary', () => {
|
|
541
544
|
const solidHatch = new Hatch({ patternName: 'SOLID' });
|
|
542
|
-
solidHatch.setProperty('childEntities', [new
|
|
545
|
+
solidHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
543
546
|
|
|
544
547
|
const ctx = makeMockCtx();
|
|
545
548
|
solidHatch.draw(ctx);
|
|
@@ -549,7 +552,7 @@ test('Test Hatch.draw solid fill renders filled boundary', () => {
|
|
|
549
552
|
|
|
550
553
|
test('Test Hatch.draw unknown pattern does not stroke pattern lines', () => {
|
|
551
554
|
const unknownHatch = new Hatch({ patternName: 'ANSI31' });
|
|
552
|
-
unknownHatch.setProperty('childEntities', [new
|
|
555
|
+
unknownHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
553
556
|
// Override pattern to a nonexistent name
|
|
554
557
|
unknownHatch.setProperty('patternName', 'NOTAPATTERN');
|
|
555
558
|
|
|
@@ -567,7 +570,7 @@ test('Test Hatch.draw tight bound line count - square boundary', () => {
|
|
|
567
570
|
// yIncrement = ceil(70.71 / 3.175) = 23 → loop i=-23..22 = 46 raw lines
|
|
568
571
|
// i=-23 (|ly|=73.025 > halfY=70.71) misses the square → 45 clipped segments
|
|
569
572
|
const squareHatch = new Hatch({ patternName: 'ANSI31' });
|
|
570
|
-
squareHatch.setProperty('childEntities', [new
|
|
573
|
+
squareHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
571
574
|
const ctx = makeMockCtx();
|
|
572
575
|
squareHatch.draw(ctx);
|
|
573
576
|
expect(ctx.getMoveCalls()).toBe(45);
|
|
@@ -580,7 +583,7 @@ test('Test Hatch.draw tight bound line count - flat boundary', () => {
|
|
|
580
583
|
// yIncrement = ceil(38.89 / 3.175) = 13 → loop i=-13..12 = 26 raw lines
|
|
581
584
|
// i=-13 (|ly|=41.275 > halfY=38.89) misses the flat box → 25 clipped segments
|
|
582
585
|
const flatHatch = new Hatch({ patternName: 'ANSI31' });
|
|
583
|
-
flatHatch.setProperty('childEntities', [new
|
|
586
|
+
flatHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 10), new Point(0, 10)] })]);
|
|
584
587
|
const ctx = makeMockCtx();
|
|
585
588
|
flatHatch.draw(ctx);
|
|
586
589
|
expect(ctx.getMoveCalls()).toBe(25);
|
|
@@ -589,10 +592,10 @@ test('Test Hatch.draw tight bound line count - flat boundary', () => {
|
|
|
589
592
|
test('Test Hatch.draw tight bound produces fewer lines for flat boundary', () => {
|
|
590
593
|
// A flat boundary (100x10) should produce fewer lines than a square (100x100).
|
|
591
594
|
const squareHatch = new Hatch({ patternName: 'ANSI31' });
|
|
592
|
-
squareHatch.setProperty('childEntities', [new
|
|
595
|
+
squareHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
593
596
|
|
|
594
597
|
const flatHatch = new Hatch({ patternName: 'ANSI31' });
|
|
595
|
-
flatHatch.setProperty('childEntities', [new
|
|
598
|
+
flatHatch.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 10), new Point(0, 10)] })]);
|
|
596
599
|
|
|
597
600
|
const squareCtx = makeMockCtx();
|
|
598
601
|
squareHatch.draw(squareCtx);
|
|
@@ -614,14 +617,14 @@ test('Hatch.buildPatternCache no childEntities sets cachedPattern to empty array
|
|
|
614
617
|
|
|
615
618
|
test('Hatch.buildPatternCache solid fill sets cachedPattern to empty array', () => {
|
|
616
619
|
const h = new Hatch({ patternName: 'SOLID' });
|
|
617
|
-
h.setProperty('childEntities', [new
|
|
620
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
618
621
|
h.buildPatternCache();
|
|
619
622
|
expect(h.cachedPattern).toEqual([]);
|
|
620
623
|
});
|
|
621
624
|
|
|
622
625
|
test('Hatch.buildPatternCache unknown pattern sets cachedPattern to empty array', () => {
|
|
623
626
|
const h = new Hatch({ patternName: 'HONEY' });
|
|
624
|
-
h.setProperty('childEntities', [new
|
|
627
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })]);
|
|
625
628
|
h.setProperty('patternName', 'NOTAPATTERN');
|
|
626
629
|
h.buildPatternCache();
|
|
627
630
|
expect(h.cachedPattern).toEqual([]);
|
|
@@ -631,9 +634,9 @@ test('Hatch.buildPatternCache unknown pattern sets cachedPattern to empty array'
|
|
|
631
634
|
|
|
632
635
|
/**
|
|
633
636
|
* Build a 100×100 square boundary
|
|
634
|
-
* @return {Array} array containing one
|
|
637
|
+
* @return {Array} array containing one PolylineBase boundary
|
|
635
638
|
*/
|
|
636
|
-
const makeSquare = () => [new
|
|
639
|
+
const makeSquare = () => [new PolylineBase({ points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100)] })];
|
|
637
640
|
|
|
638
641
|
/**
|
|
639
642
|
* Sum all segment counts across all families in a cached hatch
|
|
@@ -731,9 +734,9 @@ test('Hatch.buildPatternCache scale change invalidates and rebuilds cache with u
|
|
|
731
734
|
|
|
732
735
|
/**
|
|
733
736
|
* A 20×20 inner square hole centred in the 100×100 outer square
|
|
734
|
-
* @return {
|
|
737
|
+
* @return {PolylineBase} inner boundary polyline
|
|
735
738
|
*/
|
|
736
|
-
const makeInnerSquare = () => new
|
|
739
|
+
const makeInnerSquare = () => new PolylineBase({ points: [new Point(40, 40), new Point(60, 40), new Point(60, 60), new Point(40, 60)] });
|
|
737
740
|
|
|
738
741
|
test('Hatch.buildPatternCache ANSI31 nested boundary: no segment midpoint inside inner hole', () => {
|
|
739
742
|
// Even-odd rule: a ray from inside the hole crosses 2 boundaries → even → not hatched
|
|
@@ -951,7 +954,7 @@ test('Hatch.setProperty patternName updates pattern and solid flag via setter',
|
|
|
951
954
|
test('Hatch.setProperty points translation shifts child entity positions', () => {
|
|
952
955
|
const h = new Hatch({ patternName: 'ANSI31' });
|
|
953
956
|
// h.points = [Point(0,0), Point(1,1)] — 45° angle set by constructor
|
|
954
|
-
h.setProperty('childEntities', [new
|
|
957
|
+
h.setProperty('childEntities', [new PolylineBase({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] })]);
|
|
955
958
|
|
|
956
959
|
// New points: same 45° angle (delta angle = 0 → no rotation), origin shifted by (5, 0)
|
|
957
960
|
h.setProperty('points', [new Point(5, 0), new Point(6, 1)]);
|
|
@@ -1066,6 +1069,40 @@ test('Hatch.draw HONEY dashed family sets lineDashOffset and strokes once per se
|
|
|
1066
1069
|
expect(ctx.getStrokeCalls()).toBe(expectedSegs);
|
|
1067
1070
|
});
|
|
1068
1071
|
|
|
1072
|
+
// ── unknown pattern ───────────────────────────────────────────────────────────
|
|
1073
|
+
|
|
1074
|
+
describe('unknown hatch pattern', () => {
|
|
1075
|
+
test('logs a warning when pattern name is not recognised', () => {
|
|
1076
|
+
const warnSpy = jest.spyOn(Logging.instance, 'warn').mockImplementation(() => {});
|
|
1077
|
+
try {
|
|
1078
|
+
new Hatch({ patternName: 'NOTAPATTERN' });
|
|
1079
|
+
expect(warnSpy).toHaveBeenCalledWith('Hatch: pattern \'NOTAPATTERN\' not found');
|
|
1080
|
+
} finally {
|
|
1081
|
+
warnSpy.mockRestore();
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
test('retains the original pattern name when pattern is not recognised', () => {
|
|
1086
|
+
const warnSpy = jest.spyOn(Logging.instance, 'warn').mockImplementation(() => {});
|
|
1087
|
+
try {
|
|
1088
|
+
const h = new Hatch({ patternName: 'NOTAPATTERN' });
|
|
1089
|
+
expect(h.getProperty('patternName')).toBe('NOTAPATTERN');
|
|
1090
|
+
} finally {
|
|
1091
|
+
warnSpy.mockRestore();
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
test('does not log a warning for a known pattern', () => {
|
|
1096
|
+
const warnSpy = jest.spyOn(Logging.instance, 'warn').mockImplementation(() => {});
|
|
1097
|
+
try {
|
|
1098
|
+
new Hatch({ patternName: 'ANSI31' });
|
|
1099
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1100
|
+
} finally {
|
|
1101
|
+
warnSpy.mockRestore();
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1069
1106
|
// ── processSelection: uncloseable items ──────────────────────────────────────
|
|
1070
1107
|
|
|
1071
1108
|
test('Hatch.processSelection returns empty array when items cannot form a closed loop', () => {
|
|
@@ -1077,3 +1114,289 @@ test('Hatch.processSelection returns empty array when items cannot form a closed
|
|
|
1077
1114
|
const h = new Hatch();
|
|
1078
1115
|
expect(h.processSelection(disconnected)).toEqual([]);
|
|
1079
1116
|
});
|
|
1117
|
+
|
|
1118
|
+
// ── patternLines: library (76=1 default) ─────────────────────────────────────
|
|
1119
|
+
|
|
1120
|
+
test('Hatch.patternLines is non-enumerable', () => {
|
|
1121
|
+
const h = new Hatch({ patternName: 'ANSI31' });
|
|
1122
|
+
const descriptor = Object.getOwnPropertyDescriptor(h, 'patternLines');
|
|
1123
|
+
expect(descriptor.enumerable).toBe(false);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
test('Hatch.patternLines populated from library for known predefined pattern', () => {
|
|
1127
|
+
const h = new Hatch({ patternName: 'ANSI31' });
|
|
1128
|
+
expect(h.patternLines.length).toBeGreaterThan(0);
|
|
1129
|
+
expect(h.patternLines[0]).toBeInstanceOf(PatternLine);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
test('Hatch.patternLines is empty for unknown pattern', () => {
|
|
1133
|
+
const warnSpy = jest.spyOn(Logging.instance, 'warn').mockImplementation(() => {});
|
|
1134
|
+
try {
|
|
1135
|
+
const h = new Hatch({ patternName: 'NOTAPATTERN' });
|
|
1136
|
+
expect(h.patternLines).toEqual([]);
|
|
1137
|
+
} finally {
|
|
1138
|
+
warnSpy.mockRestore();
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
test('Hatch.patternLines is empty for SOLID pattern', () => {
|
|
1143
|
+
const h = new Hatch({ patternName: 'SOLID' });
|
|
1144
|
+
expect(h.patternLines).toEqual([]);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
test('Hatch.patternLines rebuilt when patternName changes via setProperty', () => {
|
|
1148
|
+
const h = new Hatch({ patternName: 'ANSI31' });
|
|
1149
|
+
const originalLength = h.patternLines.length;
|
|
1150
|
+
expect(originalLength).toBeGreaterThan(0);
|
|
1151
|
+
|
|
1152
|
+
h.setProperty('patternName', 'HONEY');
|
|
1153
|
+
expect(h.patternLines.length).toBeGreaterThan(0);
|
|
1154
|
+
// HONEY has 3 families; ANSI31 has 1
|
|
1155
|
+
expect(h.patternLines.length).toBeGreaterThan(originalLength);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
test('Hatch.patternLines becomes empty when patternName set to unknown', () => {
|
|
1159
|
+
const warnSpy = jest.spyOn(Logging.instance, 'warn').mockImplementation(() => {});
|
|
1160
|
+
try {
|
|
1161
|
+
const h = new Hatch({ patternName: 'ANSI31' });
|
|
1162
|
+
h.setProperty('patternName', 'NOTAPATTERN');
|
|
1163
|
+
expect(h.patternLines).toEqual([]);
|
|
1164
|
+
} finally {
|
|
1165
|
+
warnSpy.mockRestore();
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// ── patternLines: user-defined inline (76=0) ─────────────────────────────────
|
|
1170
|
+
|
|
1171
|
+
test('Hatch with 76=0 uses inline pattern lines, not library', () => {
|
|
1172
|
+
const data = {
|
|
1173
|
+
'76': 0,
|
|
1174
|
+
'41': 1,
|
|
1175
|
+
'52': 0,
|
|
1176
|
+
'78': 1,
|
|
1177
|
+
'53': 0,
|
|
1178
|
+
'45': 0,
|
|
1179
|
+
'46': 10, // dy delta (10 inches)
|
|
1180
|
+
'79': 0,
|
|
1181
|
+
};
|
|
1182
|
+
const h = new Hatch(data);
|
|
1183
|
+
expect(h.patternLines.length).toBe(1);
|
|
1184
|
+
expect(h.patternLines[0]).toBeInstanceOf(PatternLine);
|
|
1185
|
+
expect(h.patternLines[0].angle).toBe(0);
|
|
1186
|
+
expect(h.patternLines[0].yDelta).toBeCloseTo(10, 5); // 10 (drawing units)
|
|
1187
|
+
expect(h.patternLines[0].dashes).toEqual([]);
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
test('Hatch with 76=0 correctly scales delta by dividing out the scale factor', () => {
|
|
1191
|
+
// scale=2: DXF stores delta pre-multiplied, so raw dy=20 → 20/2=10 (drawing units)
|
|
1192
|
+
const data = {
|
|
1193
|
+
'76': 0,
|
|
1194
|
+
'41': 2,
|
|
1195
|
+
'52': 0,
|
|
1196
|
+
'78': 1,
|
|
1197
|
+
'53': 0,
|
|
1198
|
+
'45': 0,
|
|
1199
|
+
'46': 20,
|
|
1200
|
+
'79': 0,
|
|
1201
|
+
};
|
|
1202
|
+
const h = new Hatch(data);
|
|
1203
|
+
expect(h.patternLines[0].yDelta).toBeCloseTo(10, 5);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
test('Hatch with 76=0 and 77=1 (double flag) adds a perpendicular family', () => {
|
|
1207
|
+
const data = {
|
|
1208
|
+
'76': 0,
|
|
1209
|
+
'77': 1,
|
|
1210
|
+
'41': 1,
|
|
1211
|
+
'52': 0,
|
|
1212
|
+
'78': 1,
|
|
1213
|
+
'53': 0,
|
|
1214
|
+
'45': 0,
|
|
1215
|
+
'46': 10,
|
|
1216
|
+
'79': 0,
|
|
1217
|
+
};
|
|
1218
|
+
const h = new Hatch(data);
|
|
1219
|
+
expect(h.patternLines.length).toBe(2);
|
|
1220
|
+
expect(h.patternLines[1].angle).toBe(90);
|
|
1221
|
+
expect(h.patternLines[1].yDelta).toBeCloseTo(h.patternLines[0].yDelta, 5);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
test('Hatch with 76=0 and dashes parses and converts to mm', () => {
|
|
1225
|
+
// [5, -5] → [5, -5] (drawing units)
|
|
1226
|
+
const data = {
|
|
1227
|
+
'76': 0,
|
|
1228
|
+
'41': 1,
|
|
1229
|
+
'52': 0,
|
|
1230
|
+
'78': 1,
|
|
1231
|
+
'53': 0,
|
|
1232
|
+
'45': 0,
|
|
1233
|
+
'46': 10,
|
|
1234
|
+
'79': 2,
|
|
1235
|
+
'49': [5, -5],
|
|
1236
|
+
};
|
|
1237
|
+
const h = new Hatch(data);
|
|
1238
|
+
expect(h.patternLines[0].dashes.length).toBe(2);
|
|
1239
|
+
expect(h.patternLines[0].dashes[0]).toBeCloseTo(5, 5);
|
|
1240
|
+
expect(h.patternLines[0].dashes[1]).toBeCloseTo(-5, 5);
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
test('Hatch with 76=0 and multiple line families parses each correctly', () => {
|
|
1244
|
+
const data = {
|
|
1245
|
+
'76': 0,
|
|
1246
|
+
'41': 1,
|
|
1247
|
+
'52': 0,
|
|
1248
|
+
'78': 2,
|
|
1249
|
+
'53': [0, 90],
|
|
1250
|
+
'45': [0, 0],
|
|
1251
|
+
'46': [10, 10],
|
|
1252
|
+
'79': [0, 0],
|
|
1253
|
+
};
|
|
1254
|
+
const h = new Hatch(data);
|
|
1255
|
+
expect(h.patternLines.length).toBe(2);
|
|
1256
|
+
expect(h.patternLines[0].angle).toBe(0);
|
|
1257
|
+
expect(h.patternLines[1].angle).toBe(90);
|
|
1258
|
+
expect(h.patternLines[0].yDelta).toBeCloseTo(10, 5);
|
|
1259
|
+
expect(h.patternLines[1].yDelta).toBeCloseTo(10, 5);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
test('Hatch with 76=0 and no inline families synthesises from spacing (47)', () => {
|
|
1263
|
+
const data = { '76': 0, '41': 1, '52': 45, '47': 5 };
|
|
1264
|
+
const h = new Hatch(data);
|
|
1265
|
+
expect(h.patternLines.length).toBe(1);
|
|
1266
|
+
expect(h.patternLines[0].angle).toBe(0);
|
|
1267
|
+
expect(h.patternLines[0].yDelta).toBeCloseTo(5, 5);
|
|
1268
|
+
expect(h.patternLines[0].dashes).toEqual([]);
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
test('Hatch with 76=0 explicit families: data[53] absolute angle is stored relative to entity angle', () => {
|
|
1272
|
+
// AutoCAD writes data[53]=45 as the absolute line angle when entity angle (52) is 45.
|
|
1273
|
+
// Subtracting entityAngle makes it 0 so buildPatternCache's addition yields the correct 45°.
|
|
1274
|
+
const data = { '76': 0, '41': 1, '52': 45, '78': 1, '53': 45, '45': 0, '46': 10, '79': 0 };
|
|
1275
|
+
const h = new Hatch(data);
|
|
1276
|
+
expect(h.patternLines[0].angle).toBeCloseTo(0, 5); // 45 - 45 = 0 (relative)
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
test('Hatch with 76=0, no inline families, double flag (77=1) synthesises two families', () => {
|
|
1280
|
+
const data = { '76': 0, '77': 1, '41': 1, '52': 0, '47': 5 };
|
|
1281
|
+
const h = new Hatch(data);
|
|
1282
|
+
expect(h.patternLines.length).toBe(2);
|
|
1283
|
+
expect(h.patternLines[0].angle).toBe(0);
|
|
1284
|
+
expect(h.patternLines[1].angle).toBe(90);
|
|
1285
|
+
expect(h.patternLines[1].yDelta).toBeCloseTo(5, 5);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// ── patternLines: custom embedded (76=2) ─────────────────────────────────────
|
|
1289
|
+
|
|
1290
|
+
test('Hatch with 76=2 uses inline pattern lines', () => {
|
|
1291
|
+
const data = {
|
|
1292
|
+
'76': 2,
|
|
1293
|
+
'41': 1,
|
|
1294
|
+
'52': 0,
|
|
1295
|
+
'78': 1,
|
|
1296
|
+
'53': 45,
|
|
1297
|
+
'45': 0,
|
|
1298
|
+
'46': 10,
|
|
1299
|
+
'79': 0,
|
|
1300
|
+
};
|
|
1301
|
+
const h = new Hatch(data);
|
|
1302
|
+
expect(h.patternLines.length).toBe(1);
|
|
1303
|
+
expect(h.patternLines[0].angle).toBe(45);
|
|
1304
|
+
expect(h.patternLines[0].yDelta).toBeCloseTo(10, 5);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
// ── dxf() output: 76 group code ──────────────────────────────────────────────
|
|
1308
|
+
|
|
1309
|
+
test('Hatch.dxf writes 76=1 for library patterns', () => {
|
|
1310
|
+
const h = new Hatch({ patternName: 'ANSI31', handle: '1' });
|
|
1311
|
+
h.setProperty('childEntities', makeSquare());
|
|
1312
|
+
const file = new File();
|
|
1313
|
+
h.dxf(file);
|
|
1314
|
+
const match = file.contents.match(/^76\n(\d+)/m);
|
|
1315
|
+
expect(match).not.toBeNull();
|
|
1316
|
+
expect(match[1]).toBe('1');
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
test('Hatch.dxf writes 76=0 for user-defined patterns', () => {
|
|
1320
|
+
const data = {
|
|
1321
|
+
'76': 0,
|
|
1322
|
+
'41': 1,
|
|
1323
|
+
'52': 0,
|
|
1324
|
+
'78': 1,
|
|
1325
|
+
'53': 0,
|
|
1326
|
+
'45': 0,
|
|
1327
|
+
'46': 10,
|
|
1328
|
+
'79': 0,
|
|
1329
|
+
'handle': '1',
|
|
1330
|
+
};
|
|
1331
|
+
const h = new Hatch(data);
|
|
1332
|
+
h.setProperty('childEntities', makeSquare());
|
|
1333
|
+
const file = new File();
|
|
1334
|
+
h.dxf(file);
|
|
1335
|
+
const match = file.contents.match(/^76\n(\d+)/m);
|
|
1336
|
+
expect(match).not.toBeNull();
|
|
1337
|
+
expect(match[1]).toBe('0');
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
test('Hatch.dxf 76=0 double-flag round-trip writes correct family count', () => {
|
|
1341
|
+
const data = {
|
|
1342
|
+
'76': 0,
|
|
1343
|
+
'77': 1,
|
|
1344
|
+
'41': 1,
|
|
1345
|
+
'52': 0,
|
|
1346
|
+
'78': 1,
|
|
1347
|
+
'53': 0,
|
|
1348
|
+
'45': 0,
|
|
1349
|
+
'46': 10,
|
|
1350
|
+
'79': 0,
|
|
1351
|
+
'handle': '1',
|
|
1352
|
+
};
|
|
1353
|
+
const h = new Hatch(data);
|
|
1354
|
+
h.setProperty('childEntities', makeSquare());
|
|
1355
|
+
const file = new File();
|
|
1356
|
+
h.dxf(file);
|
|
1357
|
+
const match = file.contents.match(/^78\n(\d+)/m);
|
|
1358
|
+
expect(match).not.toBeNull();
|
|
1359
|
+
expect(match[1]).toBe('2');
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
// ── buildPatternCache with inline patterns ────────────────────────────────────
|
|
1363
|
+
|
|
1364
|
+
test('Hatch with 76=0 inline pattern renders segments inside boundary', () => {
|
|
1365
|
+
const data = {
|
|
1366
|
+
'76': 0,
|
|
1367
|
+
'41': 1,
|
|
1368
|
+
'52': 0,
|
|
1369
|
+
'78': 1,
|
|
1370
|
+
'53': 0,
|
|
1371
|
+
'45': 0,
|
|
1372
|
+
'46': 1, // 1 (drawing unit) spacing
|
|
1373
|
+
'79': 0,
|
|
1374
|
+
};
|
|
1375
|
+
const h = new Hatch(data);
|
|
1376
|
+
h.setProperty('childEntities', makeSquare());
|
|
1377
|
+
h.buildPatternCache();
|
|
1378
|
+
|
|
1379
|
+
expect(h.cachedPattern.length).toBe(1);
|
|
1380
|
+
expect(h.cachedPattern[0].segments.length).toBeGreaterThan(0);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
test('Hatch with 76=0 double flag renders two families of segments', () => {
|
|
1384
|
+
const data = {
|
|
1385
|
+
'76': 0,
|
|
1386
|
+
'77': 1,
|
|
1387
|
+
'41': 1,
|
|
1388
|
+
'52': 0,
|
|
1389
|
+
'78': 1,
|
|
1390
|
+
'53': 0,
|
|
1391
|
+
'45': 0,
|
|
1392
|
+
'46': 1,
|
|
1393
|
+
'79': 0,
|
|
1394
|
+
};
|
|
1395
|
+
const h = new Hatch(data);
|
|
1396
|
+
h.setProperty('childEntities', makeSquare());
|
|
1397
|
+
h.buildPatternCache();
|
|
1398
|
+
|
|
1399
|
+
expect(h.cachedPattern.length).toBe(2);
|
|
1400
|
+
expect(h.cachedPattern[0].segments.length).toBeGreaterThan(0);
|
|
1401
|
+
expect(h.cachedPattern[1].segments.length).toBeGreaterThan(0);
|
|
1402
|
+
});
|
|
@@ -13,10 +13,10 @@ new Core();
|
|
|
13
13
|
|
|
14
14
|
const insert = new Insert({ handle: '1', points: [new Point()] });
|
|
15
15
|
const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
|
|
16
|
-
insert.block.
|
|
16
|
+
insert.block.addEntity(line);
|
|
17
17
|
|
|
18
18
|
const rotatedInsert = new Insert({ points: [new Point()], rotation: 45 });
|
|
19
|
-
rotatedInsert.block.
|
|
19
|
+
rotatedInsert.block.addEntity(line);
|
|
20
20
|
|
|
21
21
|
test('Test Insert.snaps', () => {
|
|
22
22
|
const point = new Point(100, 100);
|
|
@@ -51,7 +51,7 @@ test('Test Insert.boundingBox', () => {
|
|
|
51
51
|
expect(insert.boundingBox().yMax).toBeCloseTo(202);
|
|
52
52
|
|
|
53
53
|
const line2 = new Line({ points: [new Point(1001, 1002), new Point(2001, 2002)] });
|
|
54
|
-
insert.block.
|
|
54
|
+
insert.block.addEntity(line2);
|
|
55
55
|
|
|
56
56
|
expect(insert.boundingBox().xMin).toBeCloseTo(101);
|
|
57
57
|
expect(insert.boundingBox().xMax).toBeCloseTo(2001);
|
|
@@ -115,17 +115,17 @@ describe('Test Insert.draw', () => {
|
|
|
115
115
|
test('draw() returns the block items array', () => {
|
|
116
116
|
const ins = new Insert({ points: [new Point(0, 0)] });
|
|
117
117
|
const line = new Line({ points: [new Point(0, 0), new Point(1, 1)] });
|
|
118
|
-
ins.block.
|
|
118
|
+
ins.block.addEntity(line);
|
|
119
119
|
|
|
120
120
|
const result = ins.draw(makeMockCtx());
|
|
121
|
-
expect(result).toBe(ins.block.
|
|
121
|
+
expect(result).toBe(ins.block.entities);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
test('draw() returns empty array when block has no items', () => {
|
|
125
125
|
const ins = new Insert({ points: [new Point(0, 0)] });
|
|
126
126
|
const result = ins.draw(makeMockCtx());
|
|
127
127
|
expect(result).toEqual([]);
|
|
128
|
-
expect(result).toBe(ins.block.
|
|
128
|
+
expect(result).toBe(ins.block.entities);
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
test('draw() calls renderer.applyTransform with the insert point coordinates', () => {
|
|
@@ -159,9 +159,9 @@ describe('Test Insert.draw', () => {
|
|
|
159
159
|
test('draw() returns same items reference regardless of scale', () => {
|
|
160
160
|
const ins = new Insert({ points: [new Point(0, 0)] });
|
|
161
161
|
const line = new Line({ points: [new Point(0, 0), new Point(1, 1)] });
|
|
162
|
-
ins.block.
|
|
163
|
-
expect(ins.draw(makeMockCtx())).toBe(ins.block.
|
|
164
|
-
expect(ins.draw(makeMockCtx())).toBe(ins.block.
|
|
162
|
+
ins.block.addEntity(line);
|
|
163
|
+
expect(ins.draw(makeMockCtx())).toBe(ins.block.entities);
|
|
164
|
+
expect(ins.draw(makeMockCtx())).toBe(ins.block.entities);
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
167
|
|
|
@@ -701,7 +701,7 @@ test('Test Point.bulgeAngle', () => {
|
|
|
701
701
|
|
|
702
702
|
test('Test Point.bulgeRadius', () => {
|
|
703
703
|
// start point: 100,0
|
|
704
|
-
//
|
|
704
|
+
// centre: 100,50
|
|
705
705
|
// radius: 50
|
|
706
706
|
const point = new Point(100, 0);
|
|
707
707
|
|
|
@@ -771,7 +771,7 @@ test('Test Point.bulgeRadius', () => {
|
|
|
771
771
|
|
|
772
772
|
test('Test Point.apothem', () => {
|
|
773
773
|
// start point: 100,0
|
|
774
|
-
//
|
|
774
|
+
// centre: 100,50
|
|
775
775
|
// radius: 50
|
|
776
776
|
const point = new Point(100, 0);
|
|
777
777
|
|
|
@@ -829,7 +829,7 @@ test('Test Point.apothem', () => {
|
|
|
829
829
|
|
|
830
830
|
test('Test Point.bulgeCentrePoint', () => {
|
|
831
831
|
// start point: 100,0
|
|
832
|
-
//
|
|
832
|
+
// centre: 100,50
|
|
833
833
|
// radius: 50
|
|
834
834
|
const point = new Point(100, 0);
|
|
835
835
|
|