@design-core/core 0.0.1 → 0.0.3
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 +14 -7
- package/.github/workflows/DesignCoreQA.yml +3 -1
- package/README.md +11 -4
- package/TODO.md +19 -0
- package/core/core/core.js +49 -42
- package/core/designCore.js +61 -2
- package/core/dimensions/alignedDimension.js +142 -0
- package/core/dimensions/angularDimension.js +550 -0
- package/core/dimensions/baseCircularDimension.js +244 -0
- package/core/dimensions/baseDimension.js +558 -0
- package/core/dimensions/baseLinearDimension.js +371 -0
- package/core/dimensions/diametricDimension.js +82 -0
- package/core/dimensions/dimension.js +298 -0
- package/core/dimensions/linearDimension.js +29 -0
- package/core/dimensions/radialDimension.js +78 -0
- package/core/dimensions/rotatedDimension.js +167 -0
- package/core/entities/arc.js +314 -60
- package/core/entities/arctext.js +642 -0
- package/core/entities/basePolyline.js +553 -94
- package/core/entities/circle.js +181 -54
- package/core/entities/entity.js +154 -74
- package/core/entities/hatch.js +807 -0
- package/core/entities/insert.js +200 -39
- package/core/entities/line.js +196 -134
- package/core/entities/lwpolyline.js +21 -44
- package/core/entities/point.js +194 -29
- package/core/entities/polyline.js +54 -7
- package/core/entities/rectangle.js +43 -13
- package/core/entities/solid.js +43 -6
- package/core/entities/text.js +324 -181
- package/core/lib/boundingBox.js +73 -13
- package/core/lib/canvas.js +256 -65
- package/core/lib/clipboard.js +198 -0
- package/core/lib/colour.js +144 -0
- package/core/lib/colours.js +367 -301
- package/core/lib/commandLine.js +16 -28
- package/core/lib/commandManager.js +115 -58
- package/core/lib/constants.js +23 -0
- package/core/lib/dxf/dxf.js +249 -34
- package/core/lib/dxf/dxfFile.js +23 -38
- package/core/lib/dxf/dxfIterator.js +17 -5
- package/core/lib/dxf/dxfRead.js +23 -7
- package/core/lib/dxf/dxfWrite.js +39 -129
- package/core/lib/dxf/handleManager.js +97 -0
- package/core/lib/dxf/sections/blocks.js +16 -2
- package/core/lib/dxf/sections/entities.js +17 -3
- package/core/lib/dxf/sections/header.js +10 -1
- package/core/lib/dxf/sections/objects.js +36 -0
- package/core/lib/dxf/sections/section.js +43 -5
- package/core/lib/dxf/sections/tables.js +17 -3
- package/core/lib/entityManager.js +178 -0
- package/core/lib/fileio.js +14 -4
- package/core/lib/headers.js +229 -0
- package/core/lib/input.js +46 -0
- package/core/lib/inputManager.js +199 -194
- package/core/lib/intersect.js +341 -468
- package/core/lib/logging.js +25 -1
- package/core/lib/matrix.js +6 -8
- package/core/lib/mouse.js +24 -79
- package/core/lib/patterns.js +690 -0
- package/core/lib/promptOptions.js +133 -0
- package/core/lib/scene.js +71 -174
- package/core/lib/selectionManager.js +74 -48
- package/core/lib/selectionWindow.js +24 -10
- package/core/lib/settings.js +18 -11
- package/core/lib/snapping.js +129 -21
- package/core/lib/stateManager.js +288 -0
- package/core/lib/strings.js +57 -5
- package/core/lib/utils.js +95 -11
- package/core/objects/dictionary.js +43 -0
- package/core/objects/dictionaryManager.js +80 -0
- package/core/properties/dimType.js +117 -0
- package/core/properties/flags.js +66 -0
- package/core/properties/property.js +26 -0
- package/core/properties/propertyManager.js +65 -19
- package/core/tables/appId.js +31 -0
- package/core/tables/appIdManager.js +44 -0
- package/core/tables/block.js +323 -0
- package/core/tables/blockManager.js +48 -0
- package/core/tables/blockRecord.js +29 -0
- package/core/tables/blockRecordManager.js +37 -0
- package/core/tables/dimStyle.js +627 -0
- package/core/tables/dimStyleManager.js +187 -0
- package/core/tables/endblk.js +28 -0
- package/core/tables/layer.js +219 -0
- package/core/tables/layerManager.js +47 -0
- package/core/tables/ltype.js +85 -0
- package/core/tables/ltypeManager.js +65 -0
- package/core/tables/style.js +145 -0
- package/core/tables/styleManager.js +55 -0
- package/core/tables/styleManagerBase.js +110 -0
- package/core/tables/tableManagerBase.js +354 -0
- package/core/tables/ucs.js +28 -0
- package/core/tables/ucsManager.js +44 -0
- package/core/tables/view.js +28 -0
- package/core/tables/viewManager.js +44 -0
- package/core/tables/vport.js +70 -0
- package/core/tables/vportManager.js +65 -0
- package/core/tools/chamfer.js +343 -0
- package/core/tools/chamferFilletBase.js +162 -0
- package/core/tools/copy.js +68 -40
- package/core/tools/copybase.js +65 -0
- package/core/tools/copyclip.js +59 -0
- package/core/tools/cornerEntity.js +138 -0
- package/core/tools/cutclip.js +72 -0
- package/core/tools/distance.js +37 -9
- package/core/tools/erase.js +43 -13
- package/core/tools/explode.js +117 -0
- package/core/tools/extend.js +71 -32
- package/core/tools/fillet.js +336 -0
- package/core/tools/identify.js +33 -6
- package/core/tools/matchProp.js +106 -0
- package/core/tools/mirror.js +141 -0
- package/core/tools/move.js +65 -32
- package/core/tools/offset.js +321 -0
- package/core/tools/pan.js +54 -0
- package/core/tools/pasteclip.js +93 -0
- package/core/tools/purge.js +90 -0
- package/core/tools/rotate.js +105 -70
- package/core/tools/scale.js +166 -0
- package/core/tools/tool.js +14 -4
- package/core/tools/trim.js +71 -32
- package/core/tools/zoom.js +152 -0
- package/package.json +3 -305
- package/test/dimensions/alignedDimension.test.js +211 -0
- package/test/dimensions/angularDimension.test.js +243 -0
- package/test/dimensions/baseDimension.test.js +239 -0
- package/test/dimensions/diametricDimension.test.js +203 -0
- package/test/dimensions/dimension.test.js +102 -0
- package/test/dimensions/radialDimension.test.js +203 -0
- package/test/dimensions/rotatedDimension.test.js +224 -0
- package/test/entities/arc.test.js +463 -41
- package/test/entities/arctext.test.js +329 -0
- package/test/entities/basePolyline.test.js +459 -10
- package/test/entities/circle.test.js +312 -7
- package/test/entities/entity.test.js +28 -9
- package/test/entities/hatch.test.js +606 -0
- package/test/entities/insert.test.js +90 -0
- package/test/entities/line.test.js +182 -6
- package/test/entities/point.test.js +375 -37
- package/test/entities/solid.test.js +156 -0
- package/test/entities/text.test.js +533 -7
- package/test/integration/exportIntegration.reference.dxf +1706 -0
- package/test/integration/exportIntegration.test.js +186 -0
- package/test/integration/importExportIntegration.test.js +126 -0
- package/test/integration/intersectionIntegration.test.js +880 -0
- package/test/integration/userInputIntegration.reference.dxf +1410 -0
- package/test/integration/userInputIntegration.test.js +269 -0
- package/test/lib/boundingBox.test.js +26 -2
- package/test/lib/canvas.test.js +613 -5
- package/test/lib/clipboard.test.js +70 -0
- package/test/lib/colour.test.js +114 -0
- package/test/lib/colours.test.js +63 -18
- package/test/lib/commandLine.test.js +159 -22
- package/test/lib/commandManager.test.js +4 -4
- package/test/lib/constants.test.js +9 -0
- package/test/lib/dxf/dxfFile.test.js +6 -2
- package/test/lib/dxf/dxfIterator.test.js +1 -1
- package/test/lib/dxf/dxfWrite.test.js +173 -0
- package/test/lib/dxf/handleManager.test.js +137 -0
- package/test/lib/dxf/sections/blocks.test.js +5 -5
- package/test/lib/dxf/sections/entities.test.js +4 -4
- package/test/lib/dxf/sections/header.test.js +18 -4
- package/test/lib/dxf/sections/objects.test.js +132 -0
- package/test/lib/dxf/sections/section.test.js +29 -29
- package/test/lib/dxf/sections/tables.test.js +3 -3
- package/test/lib/entityManager.test.js +163 -0
- package/test/lib/headers.test.js +233 -0
- package/test/lib/inputManager.test.js +1144 -5
- package/test/lib/intersect.test.js +454 -331
- package/test/lib/logging.test.js +1 -1
- package/test/lib/matrix.test.js +1 -1
- package/test/lib/mouse.test.js +327 -4
- package/test/lib/patterns.test.js +105 -0
- package/test/lib/promptOptions.test.js +179 -0
- package/test/lib/scene.test.js +16 -52
- package/test/lib/selectionManager.test.js +17 -5
- package/test/lib/settings.test.js +111 -5
- package/test/lib/snapping.test.js +142 -4
- package/test/lib/stateManager.test.js +155 -0
- package/test/lib/utils.test.js +81 -1
- package/test/objects/dictionary.test.js +81 -0
- package/test/objects/dictionaryManager.test.js +34 -0
- package/test/properties/dimType.test.js +62 -0
- package/test/properties/flags.test.js +164 -0
- package/test/properties/property.test.js +13 -0
- package/test/properties/propertyManager.test.js +10 -11
- package/test/tables/appId.test.js +46 -0
- package/test/tables/appIdManager.test.js +72 -0
- package/test/tables/block.test.js +157 -0
- package/test/tables/blockManager.test.js +52 -0
- package/test/tables/blockRecord.test.js +39 -0
- package/test/tables/blockRecordManager.test.js +60 -0
- package/test/tables/dimstyle.test.js +72 -0
- package/test/tables/endblk.test.js +30 -0
- package/test/tables/layer.test.js +95 -0
- package/test/tables/layerManager.test.js +172 -0
- package/test/{styles → tables}/ltype.test.js +7 -7
- package/test/{styles → tables}/ltypeManager.test.js +2 -2
- package/test/tables/style.test.js +44 -0
- package/test/tables/stylemanagerbase.test.js +220 -0
- package/test/tables/tableManagerBase.test.js +194 -0
- package/test/tables/ucs.test.js +37 -0
- package/test/tables/ucsManager.test.js +48 -0
- package/test/tables/view.test.js +37 -0
- package/test/tables/viewManager.test.js +48 -0
- package/test/tables/vport.test.js +117 -0
- package/test/tables/vportManager.test.js +54 -0
- package/test/test-helpers/test-helpers.js +66 -0
- package/test/tools/chamfer.test.js +970 -0
- package/test/tools/chamferFilletBase.test.js +446 -0
- package/test/tools/copy.test.js +22 -14
- package/test/tools/copybase.test.js +93 -0
- package/test/tools/copyclip.test.js +92 -0
- package/test/tools/cornerEntity.test.js +201 -0
- package/test/tools/cutclip.test.js +94 -0
- package/test/tools/distance.test.js +140 -18
- package/test/tools/erase.test.js +12 -12
- package/test/tools/explode.test.js +45 -0
- package/test/tools/extend.test.js +326 -44
- package/test/tools/fillet.test.js +831 -0
- package/test/tools/identify.test.js +115 -16
- package/test/tools/matchProp.test.js +84 -0
- package/test/tools/mirror.test.js +475 -0
- package/test/tools/move.test.js +75 -19
- package/test/tools/offset.test.js +748 -0
- package/test/tools/pan.test.js +72 -0
- package/test/tools/pasteclip.test.js +100 -0
- package/test/tools/purge.test.js +89 -0
- package/test/tools/rotate.test.js +89 -23
- package/test/tools/scale.test.js +529 -0
- package/test/tools/trim.test.js +419 -84
- package/test/tools/zoom.test.js +504 -0
- package/core/entities/alignedDimension.js +0 -279
- package/core/entities/angularDimension.js +0 -194
- package/core/entities/baseDimension.js +0 -227
- package/core/entities/block.js +0 -247
- package/core/entities/diametricDimension.js +0 -133
- package/core/entities/dimension.js +0 -202
- package/core/entities/radialDimension.js +0 -126
- package/core/styles/dimStyle.js +0 -719
- package/core/styles/dimStyleManager.js +0 -47
- package/core/styles/layer.js +0 -133
- package/core/styles/layerManager.js +0 -40
- package/core/styles/ltype.js +0 -86
- package/core/styles/ltypeManager.js +0 -50
- package/core/styles/style.js +0 -158
- package/core/styles/styleManager.js +0 -43
- package/core/styles/styleManagerBase.js +0 -299
- package/test/entities/block.test.js +0 -22
- package/test/styles/dimstyle.test.js +0 -19
- package/test/styles/layerManager.test.js +0 -150
- package/test/styles/style.test.js +0 -33
- package/test/styles/stylemanagerbase.test.js +0 -219
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
import { Point } from '../../core/entities/point.js';
|
|
2
|
+
import { Line } from '../../core/entities/line.js';
|
|
3
|
+
import { Circle } from '../../core/entities/circle.js';
|
|
4
|
+
import { Arc } from '../../core/entities/arc.js';
|
|
5
|
+
import { Polyline } from '../../core/entities/polyline.js';
|
|
6
|
+
import { Lwpolyline } from '../../core/entities/lwpolyline.js';
|
|
7
|
+
import { Solid } from '../../core/entities/solid.js';
|
|
8
|
+
import { Text } from '../../core/entities/text.js';
|
|
9
|
+
import { ArcAlignedText } from '../../core/entities/arctext.js';
|
|
10
|
+
import { Hatch } from '../../core/entities/hatch.js';
|
|
11
|
+
import { AlignedDimension } from '../../core/dimensions/alignedDimension.js';
|
|
12
|
+
import { RotatedDimension } from '../../core/dimensions/rotatedDimension.js';
|
|
13
|
+
import { DiametricDimension } from '../../core/dimensions/diametricDimension.js';
|
|
14
|
+
import { RadialDimension } from '../../core/dimensions/radialDimension.js';
|
|
15
|
+
import { AngularDimension } from '../../core/dimensions/angularDimension.js';
|
|
16
|
+
import { Intersection } from '../../core/lib/intersect.js';
|
|
17
|
+
import { Core } from '../../core/core/core.js';
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
new Core();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create selection bounds from two points
|
|
25
|
+
* @param {Point} p1
|
|
26
|
+
* @param {Point} p2
|
|
27
|
+
* @return {Object} {min: Point, max: Point}
|
|
28
|
+
*/
|
|
29
|
+
function selectionExtremes(p1, p2) {
|
|
30
|
+
return {
|
|
31
|
+
min: new Point(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y)),
|
|
32
|
+
max: new Point(Math.max(p1.x, p2.x), Math.max(p1.y, p2.y)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ========================================================
|
|
37
|
+
// Entity toPolylinePoints format tests
|
|
38
|
+
// ========================================================
|
|
39
|
+
describe('Entity toPolylinePoints', () => {
|
|
40
|
+
test('Line.toPolylinePoints returns 2 points with bulge 0', () => {
|
|
41
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 5)] });
|
|
42
|
+
const pts = line.toPolylinePoints();
|
|
43
|
+
expect(pts.length).toBe(2);
|
|
44
|
+
expect(pts[0].x).toBe(0);
|
|
45
|
+
expect(pts[0].y).toBe(0);
|
|
46
|
+
expect(pts[0].bulge).toBe(0);
|
|
47
|
+
expect(pts[1].x).toBe(10);
|
|
48
|
+
expect(pts[1].y).toBe(5);
|
|
49
|
+
expect(pts[1].bulge).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('Circle.toPolylinePoints returns 3 points with bulge 1', () => {
|
|
53
|
+
const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] });
|
|
54
|
+
const pts = circle.toPolylinePoints();
|
|
55
|
+
expect(pts.length).toBe(3);
|
|
56
|
+
// Two semicircular arcs
|
|
57
|
+
expect(pts[0].bulge).toBe(1);
|
|
58
|
+
expect(pts[1].bulge).toBe(1);
|
|
59
|
+
// Closure point
|
|
60
|
+
expect(pts[2].x).toBe(pts[0].x);
|
|
61
|
+
expect(pts[2].y).toBe(pts[0].y);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('Arc.toPolylinePoints returns 2 points with calculated bulge', () => {
|
|
65
|
+
// CCW arc from (10,0) to (0,10) centred at origin, radius 10
|
|
66
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
67
|
+
const pts = arc.toPolylinePoints();
|
|
68
|
+
expect(pts.length).toBe(2);
|
|
69
|
+
expect(pts[0].bulge).not.toBe(0);
|
|
70
|
+
expect(pts[1].bulge).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('Polyline.toPolylinePoints returns points array', () => {
|
|
74
|
+
const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)] });
|
|
75
|
+
const pts = poly.toPolylinePoints();
|
|
76
|
+
expect(pts.length).toBe(3);
|
|
77
|
+
expect(pts[0].x).toBe(0);
|
|
78
|
+
expect(pts[2].x).toBe(10);
|
|
79
|
+
expect(pts[2].y).toBe(10);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('Lwpolyline.toPolylinePoints returns points array', () => {
|
|
83
|
+
const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(5, 5), new Point(10, 0)] });
|
|
84
|
+
const pts = lwpoly.toPolylinePoints();
|
|
85
|
+
expect(pts.length).toBe(3);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('Solid.toPolylinePoints returns closed polygon', () => {
|
|
89
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(5, 10)] });
|
|
90
|
+
const pts = solid.toPolylinePoints();
|
|
91
|
+
// 3 points + closing point
|
|
92
|
+
expect(pts.length).toBe(4);
|
|
93
|
+
expect(pts[3].x).toBe(pts[0].x);
|
|
94
|
+
expect(pts[3].y).toBe(pts[0].y);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('Solid.toPolylinePoints with 4 points returns 5 points', () => {
|
|
98
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
99
|
+
const pts = solid.toPolylinePoints();
|
|
100
|
+
expect(pts.length).toBe(5);
|
|
101
|
+
expect(pts[4].x).toBe(pts[0].x);
|
|
102
|
+
expect(pts[4].y).toBe(pts[0].y);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('Text.toPolylinePoints returns closed rectangle (5 points)', () => {
|
|
106
|
+
const text = new Text({ points: [new Point(0, 0)] });
|
|
107
|
+
text.boundingRect = { width: 10, height: 5 };
|
|
108
|
+
const pts = text.toPolylinePoints();
|
|
109
|
+
expect(pts.length).toBe(5);
|
|
110
|
+
// Closed polygon: first and last point match
|
|
111
|
+
expect(pts[4].x).toBe(pts[0].x);
|
|
112
|
+
expect(pts[4].y).toBe(pts[0].y);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('ArcAlignedText.toPolylinePoints returns character positions', () => {
|
|
116
|
+
const arcText = new ArcAlignedText({
|
|
117
|
+
points: [new Point(0, 0)],
|
|
118
|
+
string: 'AB',
|
|
119
|
+
radius: 20,
|
|
120
|
+
height: 2.5,
|
|
121
|
+
startAngle: 0,
|
|
122
|
+
endAngle: 180,
|
|
123
|
+
});
|
|
124
|
+
const pts = arcText.toPolylinePoints();
|
|
125
|
+
expect(pts.length).toBe(2);
|
|
126
|
+
// Points should be at character positions along the arc
|
|
127
|
+
pts.forEach((pt) => {
|
|
128
|
+
expect(pt).toBeInstanceOf(Point);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('ArcAlignedText with empty string returns empty points', () => {
|
|
133
|
+
const arcText = new ArcAlignedText({
|
|
134
|
+
points: [new Point(0, 0)],
|
|
135
|
+
string: '',
|
|
136
|
+
radius: 20,
|
|
137
|
+
});
|
|
138
|
+
const pts = arcText.toPolylinePoints();
|
|
139
|
+
expect(pts.length).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('Polyline with bulge segments toPolylinePoints correctly', () => {
|
|
143
|
+
// Polyline with an arc segment (bulge=1 is a semicircle)
|
|
144
|
+
const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(10, 0)] });
|
|
145
|
+
const pts = poly.toPolylinePoints();
|
|
146
|
+
expect(pts.length).toBe(2);
|
|
147
|
+
expect(pts[0].bulge).toBe(1);
|
|
148
|
+
expect(pts[1].bulge).toBe(0);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ========================================================
|
|
153
|
+
// Entity touched (selection) tests - via intersectPolylinePolyline
|
|
154
|
+
// ========================================================
|
|
155
|
+
describe('Entity.touched - selection rectangle intersection', () => {
|
|
156
|
+
test('Line touching selection rectangle', () => {
|
|
157
|
+
const line = new Line({ points: [new Point(0, 0), new Point(20, 20)] });
|
|
158
|
+
// Selection rectangle crossing the line
|
|
159
|
+
const sE = selectionExtremes(new Point(5, 5), new Point(15, 15));
|
|
160
|
+
expect(line.touched(sE)).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('Line not touching selection rectangle', () => {
|
|
164
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
165
|
+
// Selection rectangle above the line
|
|
166
|
+
const sE = selectionExtremes(new Point(0, 5), new Point(10, 15));
|
|
167
|
+
expect(line.touched(sE)).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('Line partially crossing selection rectangle edge', () => {
|
|
171
|
+
// Line crossing only one edge of the rectangle
|
|
172
|
+
const line = new Line({ points: [new Point(-10, 5), new Point(5, 5)] });
|
|
173
|
+
const sE = selectionExtremes(new Point(0, 0), new Point(10, 10));
|
|
174
|
+
expect(line.touched(sE)).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('Circle touching selection rectangle', () => {
|
|
178
|
+
const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] }); // radius=5
|
|
179
|
+
// Rectangle crossing the circle
|
|
180
|
+
const sE = selectionExtremes(new Point(12, 0), new Point(18, 20));
|
|
181
|
+
expect(circle.touched(sE)).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('Circle not touching selection rectangle', () => {
|
|
185
|
+
const circle = new Circle({ points: [new Point(10, 10), new Point(15, 10)] }); // radius=5
|
|
186
|
+
// Rectangle far from circle
|
|
187
|
+
const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
|
|
188
|
+
expect(circle.touched(sE)).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('Circle surrounding selection rectangle (no edge crossing)', () => {
|
|
192
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(100, 0)] }); // radius=100
|
|
193
|
+
// Tiny rectangle inside circle - no edge crossings
|
|
194
|
+
const sE = selectionExtremes(new Point(-1, -1), new Point(1, 1));
|
|
195
|
+
expect(circle.touched(sE)).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('Arc touching selection rectangle', () => {
|
|
199
|
+
// CCW arc from (10,0) to (0,10) centred at origin, radius 10
|
|
200
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
201
|
+
// Rectangle crossing the arc
|
|
202
|
+
const sE = selectionExtremes(new Point(4, 4), new Point(12, 12));
|
|
203
|
+
expect(arc.touched(sE)).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('Arc not touching selection rectangle', () => {
|
|
207
|
+
// CCW arc from (10,0) to (0,10) centred at origin (positive quadrant)
|
|
208
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
209
|
+
// Rectangle in negative quadrant
|
|
210
|
+
const sE = selectionExtremes(new Point(-20, -20), new Point(-10, -10));
|
|
211
|
+
expect(arc.touched(sE)).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('Polyline touching selection rectangle', () => {
|
|
215
|
+
const poly = new Polyline({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20)] });
|
|
216
|
+
const sE = selectionExtremes(new Point(15, -5), new Point(25, 5));
|
|
217
|
+
expect(poly.touched(sE)).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('Polyline not touching selection rectangle', () => {
|
|
221
|
+
const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)] });
|
|
222
|
+
const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
|
|
223
|
+
expect(poly.touched(sE)).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('Lwpolyline touching selection rectangle', () => {
|
|
227
|
+
const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20)] });
|
|
228
|
+
const sE = selectionExtremes(new Point(15, -5), new Point(25, 5));
|
|
229
|
+
expect(lwpoly.touched(sE)).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('Lwpolyline not touching selection rectangle', () => {
|
|
233
|
+
const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
234
|
+
const sE = selectionExtremes(new Point(0, 5), new Point(10, 15));
|
|
235
|
+
expect(lwpoly.touched(sE)).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('Solid touching selection rectangle', () => {
|
|
239
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 20)] });
|
|
240
|
+
const sE = selectionExtremes(new Point(5, -5), new Point(15, 5));
|
|
241
|
+
expect(solid.touched(sE)).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('Solid not touching selection rectangle', () => {
|
|
245
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(10, 0), new Point(5, 10)] });
|
|
246
|
+
const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
|
|
247
|
+
expect(solid.touched(sE)).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('Solid 4-point touching selection rectangle', () => {
|
|
251
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
|
|
252
|
+
// Rectangle crossing the top edge
|
|
253
|
+
const sE = selectionExtremes(new Point(5, 15), new Point(15, 25));
|
|
254
|
+
expect(solid.touched(sE)).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('Text touching selection rectangle', () => {
|
|
258
|
+
const text = new Text({ points: [new Point(0, 0)] });
|
|
259
|
+
text.boundingRect = { width: 20, height: 10 };
|
|
260
|
+
// Rectangle crossing through the text frame
|
|
261
|
+
const sE = selectionExtremes(new Point(-5, -5), new Point(5, 5));
|
|
262
|
+
expect(text.touched(sE)).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test('Text not touching selection rectangle', () => {
|
|
266
|
+
const text = new Text({ points: [new Point(0, 0)] });
|
|
267
|
+
text.boundingRect = { width: 10, height: 5 };
|
|
268
|
+
// Rectangle far from text
|
|
269
|
+
const sE = selectionExtremes(new Point(50, 50), new Point(60, 60));
|
|
270
|
+
expect(text.touched(sE)).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('ArcAlignedText touching selection rectangle', () => {
|
|
274
|
+
const arcText = new ArcAlignedText({
|
|
275
|
+
points: [new Point(0, 0)],
|
|
276
|
+
string: 'Hello',
|
|
277
|
+
radius: 20,
|
|
278
|
+
height: 2.5,
|
|
279
|
+
startAngle: 0,
|
|
280
|
+
endAngle: 180,
|
|
281
|
+
});
|
|
282
|
+
// Get the character positions to determine where to place selection rectangle
|
|
283
|
+
const pts = arcText.toPolylinePoints();
|
|
284
|
+
if (pts.length > 0) {
|
|
285
|
+
const charPos = pts[0];
|
|
286
|
+
const sE = selectionExtremes(
|
|
287
|
+
new Point(charPos.x - 5, charPos.y - 5),
|
|
288
|
+
new Point(charPos.x + 5, charPos.y + 5),
|
|
289
|
+
);
|
|
290
|
+
expect(arcText.touched(sE)).toBe(true);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('ArcAlignedText not touching selection rectangle', () => {
|
|
295
|
+
const arcText = new ArcAlignedText({
|
|
296
|
+
points: [new Point(0, 0)],
|
|
297
|
+
string: 'Test',
|
|
298
|
+
radius: 20,
|
|
299
|
+
height: 2.5,
|
|
300
|
+
startAngle: 0,
|
|
301
|
+
endAngle: 180,
|
|
302
|
+
});
|
|
303
|
+
// Rectangle far away
|
|
304
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
305
|
+
expect(arcText.touched(sE)).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('Polyline with arc segment touching selection rectangle', () => {
|
|
309
|
+
// Semicircular polyline arc from (0,0) to (0,10) bulging right
|
|
310
|
+
const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(0, 10)] });
|
|
311
|
+
// Rectangle to the right of the midpoint where the arc bulges
|
|
312
|
+
const sE = selectionExtremes(new Point(3, 3), new Point(7, 7));
|
|
313
|
+
expect(poly.touched(sE)).toBe(true);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('Closed polyline touching selection rectangle', () => {
|
|
317
|
+
// Closed square polyline
|
|
318
|
+
const poly = new Polyline({
|
|
319
|
+
points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
|
|
320
|
+
});
|
|
321
|
+
// Rectangle crossing the right edge
|
|
322
|
+
const sE = selectionExtremes(new Point(8, 3), new Point(12, 7));
|
|
323
|
+
expect(poly.touched(sE)).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ========================================================
|
|
328
|
+
// Entity-to-entity intersections via intersectPolylinePolyline
|
|
329
|
+
// ========================================================
|
|
330
|
+
describe('Entity-to-entity intersection via intersectPolylinePolyline', () => {
|
|
331
|
+
test('Line vs Line - crossing', () => {
|
|
332
|
+
const line1 = new Line({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
333
|
+
const line2 = new Line({ points: [new Point(0, -10), new Point(0, 10)] });
|
|
334
|
+
const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
|
|
335
|
+
expect(result.status).toBe('Intersection');
|
|
336
|
+
expect(result.points.length).toBe(1);
|
|
337
|
+
expect(result.points[0].x).toBeCloseTo(0);
|
|
338
|
+
expect(result.points[0].y).toBeCloseTo(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('Line vs Line - no intersection', () => {
|
|
342
|
+
const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
343
|
+
const line2 = new Line({ points: [new Point(0, 5), new Point(10, 5)] });
|
|
344
|
+
const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
|
|
345
|
+
expect(result.status).toBe('None');
|
|
346
|
+
expect(result.points.length).toBe(0);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('Line vs Circle', () => {
|
|
350
|
+
const line = new Line({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
351
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
|
|
352
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), circle.toPolylinePoints());
|
|
353
|
+
expect(result.status).toBe('Intersection');
|
|
354
|
+
expect(result.points.length).toBe(2);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('Line vs Arc - intersection', () => {
|
|
358
|
+
// Horizontal line through arc
|
|
359
|
+
const line = new Line({ points: [new Point(-20, 5), new Point(20, 5)] });
|
|
360
|
+
// CCW arc from (10,0) to (0,10) centred at origin, radius 10
|
|
361
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
362
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), arc.toPolylinePoints());
|
|
363
|
+
expect(result.status).toBe('Intersection');
|
|
364
|
+
expect(result.points.length).toBeGreaterThanOrEqual(1);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('Line vs Polyline - multiple intersections', () => {
|
|
368
|
+
const line = new Line({ points: [new Point(5, -5), new Point(5, 25)] });
|
|
369
|
+
const poly = new Polyline({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
370
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), poly.toPolylinePoints());
|
|
371
|
+
expect(result.status).toBe('Intersection');
|
|
372
|
+
expect(result.points.length).toBe(2);
|
|
373
|
+
// Line at x=5 crosses bottom edge (y=0) and top edge (y=10) - but poly is not closed
|
|
374
|
+
// Actually crosses bottom (0,0)-(10,0) and the third segment (10,10)-(0,10)
|
|
375
|
+
expect(result.points[0].x).toBeCloseTo(5);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('Line vs Solid', () => {
|
|
379
|
+
const line = new Line({ points: [new Point(-5, 5), new Point(25, 5)] });
|
|
380
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
|
|
381
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), solid.toPolylinePoints());
|
|
382
|
+
expect(result.status).toBe('Intersection');
|
|
383
|
+
// Line crosses left and right edges of the closed solid
|
|
384
|
+
expect(result.points.length).toBe(2);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('Circle vs Circle - overlapping', () => {
|
|
388
|
+
const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
|
|
389
|
+
const circle2 = new Circle({ points: [new Point(5, 0), new Point(10, 0)] }); // radius=5
|
|
390
|
+
const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
|
|
391
|
+
expect(result.status).toBe('Intersection');
|
|
392
|
+
expect(result.points.length).toBe(2);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('Circle vs Circle - no intersection', () => {
|
|
396
|
+
const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
|
|
397
|
+
const circle2 = new Circle({ points: [new Point(50, 50), new Point(55, 50)] }); // radius=5
|
|
398
|
+
const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
|
|
399
|
+
expect(result.status).toBe('None');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('Circle vs Arc', () => {
|
|
403
|
+
const circle = new Circle({ points: [new Point(10, 0), new Point(15, 0)] }); // radius=5 at (10,0)
|
|
404
|
+
// CCW arc from (10,0) to (0,10) centred at origin, radius 10
|
|
405
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
406
|
+
const result = Intersection.intersectPolylinePolyline(circle.toPolylinePoints(), arc.toPolylinePoints());
|
|
407
|
+
expect(result.status).toBe('Intersection');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('Arc vs Arc - intersecting', () => {
|
|
411
|
+
// Two overlapping arcs: arc1 upper semicircle, arc2 shifted
|
|
412
|
+
const arc1 = new Arc({ points: [new Point(0, 0), new Point(-10, 0), new Point(10, 0)] }); // upper semicircle r=10
|
|
413
|
+
const arc2 = new Arc({ points: [new Point(5, 0), new Point(-5, 0), new Point(15, 0)] }); // upper semicircle r=10 shifted right
|
|
414
|
+
const result = Intersection.intersectPolylinePolyline(arc1.toPolylinePoints(), arc2.toPolylinePoints());
|
|
415
|
+
expect(result.status).toBe('Intersection');
|
|
416
|
+
expect(result.points.length).toBeGreaterThanOrEqual(1);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('Polyline vs Polyline - crossing', () => {
|
|
420
|
+
const poly1 = new Polyline({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
421
|
+
const poly2 = new Polyline({ points: [new Point(0, -10), new Point(0, 10)] });
|
|
422
|
+
const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
|
|
423
|
+
expect(result.status).toBe('Intersection');
|
|
424
|
+
expect(result.points.length).toBe(1);
|
|
425
|
+
expect(result.points[0].x).toBeCloseTo(0);
|
|
426
|
+
expect(result.points[0].y).toBeCloseTo(0);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('Polyline vs Polyline - multi-segment', () => {
|
|
430
|
+
// V-shaped polyline vs horizontal line
|
|
431
|
+
const poly1 = new Polyline({ points: [new Point(0, 10), new Point(10, 0), new Point(20, 10)] });
|
|
432
|
+
const poly2 = new Polyline({ points: [new Point(-5, 5), new Point(25, 5)] });
|
|
433
|
+
const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
|
|
434
|
+
expect(result.status).toBe('Intersection');
|
|
435
|
+
// Crosses both segments of poly1
|
|
436
|
+
expect(result.points.length).toBe(2);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('Lwpolyline vs Line', () => {
|
|
440
|
+
const lwpoly = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
441
|
+
const line = new Line({ points: [new Point(0, -10), new Point(0, 10)] });
|
|
442
|
+
const result = Intersection.intersectPolylinePolyline(lwpoly.toPolylinePoints(), line.toPolylinePoints());
|
|
443
|
+
expect(result.status).toBe('Intersection');
|
|
444
|
+
expect(result.points.length).toBe(1);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('Lwpolyline vs Polyline', () => {
|
|
448
|
+
const lwpoly = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
449
|
+
const poly = new Polyline({ points: [new Point(0, -10), new Point(0, 10)] });
|
|
450
|
+
const result = Intersection.intersectPolylinePolyline(lwpoly.toPolylinePoints(), poly.toPolylinePoints());
|
|
451
|
+
expect(result.status).toBe('Intersection');
|
|
452
|
+
expect(result.points.length).toBe(1);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test('Lwpolyline vs Lwpolyline', () => {
|
|
456
|
+
const lwpoly1 = new Lwpolyline({ points: [new Point(-10, 0), new Point(10, 0)] });
|
|
457
|
+
const lwpoly2 = new Lwpolyline({ points: [new Point(0, -10), new Point(0, 10)] });
|
|
458
|
+
const result = Intersection.intersectPolylinePolyline(lwpoly1.toPolylinePoints(), lwpoly2.toPolylinePoints());
|
|
459
|
+
expect(result.status).toBe('Intersection');
|
|
460
|
+
expect(result.points.length).toBe(1);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test('Polyline with bulge vs Line', () => {
|
|
464
|
+
// Semicircular arc polyline bulging right
|
|
465
|
+
const poly = new Polyline({ points: [new Point(0, 0, 1), new Point(0, 10)] });
|
|
466
|
+
// Vertical line through the middle of the arc
|
|
467
|
+
const line = new Line({ points: [new Point(3, -5), new Point(3, 15)] });
|
|
468
|
+
const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
|
|
469
|
+
expect(result.status).toBe('Intersection');
|
|
470
|
+
// Line should cross the arc in two places
|
|
471
|
+
expect(result.points.length).toBe(2);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('Solid vs Line - crossing closed polygon', () => {
|
|
475
|
+
// Triangle solid
|
|
476
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 20)] });
|
|
477
|
+
// Horizontal line crossing two edges
|
|
478
|
+
const line = new Line({ points: [new Point(-5, 10), new Point(25, 10)] });
|
|
479
|
+
const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), line.toPolylinePoints());
|
|
480
|
+
expect(result.status).toBe('Intersection');
|
|
481
|
+
expect(result.points.length).toBe(2);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test('Solid vs Polyline', () => {
|
|
485
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
|
|
486
|
+
const poly = new Polyline({ points: [new Point(10, -5), new Point(10, 25)] });
|
|
487
|
+
const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), poly.toPolylinePoints());
|
|
488
|
+
expect(result.status).toBe('Intersection');
|
|
489
|
+
expect(result.points.length).toBe(2);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('Solid vs Circle', () => {
|
|
493
|
+
// Square solid with a circle crossing through an edge
|
|
494
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(20, 20), new Point(0, 20)] });
|
|
495
|
+
const circle = new Circle({ points: [new Point(0, 10), new Point(5, 10)] }); // radius=5 at left edge
|
|
496
|
+
const result = Intersection.intersectPolylinePolyline(solid.toPolylinePoints(), circle.toPolylinePoints());
|
|
497
|
+
expect(result.status).toBe('Intersection');
|
|
498
|
+
expect(result.points.length).toBeGreaterThanOrEqual(2);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('Text vs Line', () => {
|
|
502
|
+
const text = new Text({ points: [new Point(0, 0)] });
|
|
503
|
+
text.boundingRect = { width: 20, height: 10 };
|
|
504
|
+
// Line crossing through the text frame
|
|
505
|
+
const line = new Line({ points: [new Point(-5, 5), new Point(25, 5)] });
|
|
506
|
+
const result = Intersection.intersectPolylinePolyline(text.toPolylinePoints(), line.toPolylinePoints());
|
|
507
|
+
expect(result.status).toBe('Intersection');
|
|
508
|
+
// Line enters and exits the text frame
|
|
509
|
+
expect(result.points.length).toBe(2);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test('Text vs Circle', () => {
|
|
513
|
+
const text = new Text({ points: [new Point(0, 0)] });
|
|
514
|
+
text.boundingRect = { width: 20, height: 10 };
|
|
515
|
+
const circle = new Circle({ points: [new Point(0, 5), new Point(5, 5)] }); // radius=5 at left edge
|
|
516
|
+
const result = Intersection.intersectPolylinePolyline(text.toPolylinePoints(), circle.toPolylinePoints());
|
|
517
|
+
expect(result.status).toBe('Intersection');
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// ========================================================
|
|
522
|
+
// Cross-type intersection combinations
|
|
523
|
+
// ========================================================
|
|
524
|
+
describe('Cross-type entity intersections', () => {
|
|
525
|
+
test('Line vs Solid vs Circle - chain verification', () => {
|
|
526
|
+
// Verify that intersections work consistently across entity types
|
|
527
|
+
const line = new Line({ points: [new Point(0, 0), new Point(20, 0)] });
|
|
528
|
+
const solid = new Solid({ points: [new Point(5, -5), new Point(15, -5), new Point(15, 5), new Point(5, 5)] });
|
|
529
|
+
|
|
530
|
+
// Line crosses left and right edges of solid
|
|
531
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), solid.toPolylinePoints());
|
|
532
|
+
expect(result.status).toBe('Intersection');
|
|
533
|
+
expect(result.points.length).toBe(2);
|
|
534
|
+
expect(result.points[0].x).toBeCloseTo(15);
|
|
535
|
+
expect(result.points[0].y).toBeCloseTo(0);
|
|
536
|
+
expect(result.points[1].x).toBeCloseTo(5);
|
|
537
|
+
expect(result.points[1].y).toBeCloseTo(0);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('Polyline with bulge vs Circle', () => {
|
|
541
|
+
// Polyline with arc segment
|
|
542
|
+
const poly = new Polyline({ points: [new Point(0, -5, 1), new Point(0, 5)] });
|
|
543
|
+
// Circle overlapping the arc
|
|
544
|
+
const circle = new Circle({ points: [new Point(5, 0), new Point(10, 0)] }); // radius=5
|
|
545
|
+
const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), circle.toPolylinePoints());
|
|
546
|
+
expect(result.status).toBe('Intersection');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('Closed polyline vs Closed polyline', () => {
|
|
550
|
+
// Two overlapping squares
|
|
551
|
+
const poly1 = new Polyline({
|
|
552
|
+
points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
|
|
553
|
+
});
|
|
554
|
+
const poly2 = new Polyline({
|
|
555
|
+
points: [new Point(5, 5), new Point(15, 5), new Point(15, 15), new Point(5, 15), new Point(5, 5)],
|
|
556
|
+
});
|
|
557
|
+
const result = Intersection.intersectPolylinePolyline(poly1.toPolylinePoints(), poly2.toPolylinePoints());
|
|
558
|
+
expect(result.status).toBe('Intersection');
|
|
559
|
+
// Two overlapping squares intersect at multiple points
|
|
560
|
+
expect(result.points.length).toBeGreaterThanOrEqual(2);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('Arc vs Solid - crossing triangle', () => {
|
|
564
|
+
const arc = new Arc({ points: [new Point(10, 0), new Point(20, 0), new Point(0, 0)] }); // semicircle top
|
|
565
|
+
const solid = new Solid({ points: [new Point(0, 0), new Point(20, 0), new Point(10, 15)] });
|
|
566
|
+
const result = Intersection.intersectPolylinePolyline(arc.toPolylinePoints(), solid.toPolylinePoints());
|
|
567
|
+
expect(result.status).toBe('Intersection');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test('Line vs Lwpolyline', () => {
|
|
571
|
+
const line = new Line({ points: [new Point(0, 5), new Point(20, 5)] });
|
|
572
|
+
const lwpoly = new Lwpolyline({ points: [new Point(10, 0), new Point(10, 10)] });
|
|
573
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), lwpoly.toPolylinePoints());
|
|
574
|
+
expect(result.status).toBe('Intersection');
|
|
575
|
+
expect(result.points.length).toBe(1);
|
|
576
|
+
expect(result.points[0].x).toBeCloseTo(10);
|
|
577
|
+
expect(result.points[0].y).toBeCloseTo(5);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test('Circle vs Lwpolyline', () => {
|
|
581
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
|
|
582
|
+
const lwpoly = new Lwpolyline({ points: [new Point(0, -15), new Point(0, 15)] });
|
|
583
|
+
const result = Intersection.intersectPolylinePolyline(circle.toPolylinePoints(), lwpoly.toPolylinePoints());
|
|
584
|
+
expect(result.status).toBe('Intersection');
|
|
585
|
+
expect(result.points.length).toBe(2);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test('Arc vs Lwpolyline', () => {
|
|
589
|
+
const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
|
|
590
|
+
const lwpoly = new Lwpolyline({ points: [new Point(0, 0), new Point(15, 15)] });
|
|
591
|
+
const result = Intersection.intersectPolylinePolyline(arc.toPolylinePoints(), lwpoly.toPolylinePoints());
|
|
592
|
+
expect(result.status).toBe('Intersection');
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ========================================================
|
|
597
|
+
// Dimension touched tests
|
|
598
|
+
// ========================================================
|
|
599
|
+
describe('Dimension touched tests', () => {
|
|
600
|
+
test('AlignedDimension.touched delegates to block', () => {
|
|
601
|
+
const pt13 = new Point(0, 0, 0, 13); // first definition point
|
|
602
|
+
const pt14 = new Point(100, 0, 0, 14); // second definition point
|
|
603
|
+
const pt11 = new Point(50, 20, 0, 11); // text position
|
|
604
|
+
const pt10 = new Point(100, 20, 0, 10); // dimension line location
|
|
605
|
+
const dim = new AlignedDimension({
|
|
606
|
+
points: [pt13, pt14, pt11, pt10],
|
|
607
|
+
});
|
|
608
|
+
dim.refresh();
|
|
609
|
+
|
|
610
|
+
// Selection rectangle around the dimension text area
|
|
611
|
+
const sE = selectionExtremes(new Point(40, 15), new Point(60, 25));
|
|
612
|
+
const touched = dim.touched(sE);
|
|
613
|
+
expect(typeof touched).toBe('boolean');
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test('AlignedDimension.touched returns false when far away', () => {
|
|
617
|
+
const pt13 = new Point(0, 0, 0, 13);
|
|
618
|
+
const pt14 = new Point(100, 0, 0, 14);
|
|
619
|
+
const pt11 = new Point(50, 20, 0, 11);
|
|
620
|
+
const pt10 = new Point(100, 20, 0, 10);
|
|
621
|
+
const dim = new AlignedDimension({
|
|
622
|
+
points: [pt13, pt14, pt11, pt10],
|
|
623
|
+
});
|
|
624
|
+
dim.refresh();
|
|
625
|
+
|
|
626
|
+
// Selection rectangle far from dimension
|
|
627
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
628
|
+
expect(dim.touched(sE)).toBe(false);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test('RotatedDimension.touched delegates to block', () => {
|
|
632
|
+
const pt13 = new Point(0, 0, 0, 13);
|
|
633
|
+
const pt14 = new Point(100, 0, 0, 14);
|
|
634
|
+
const pt11 = new Point(50, 20, 0, 11);
|
|
635
|
+
const pt10 = new Point(100, 20, 0, 10);
|
|
636
|
+
const dim = new RotatedDimension({
|
|
637
|
+
points: [pt13, pt14, pt11, pt10],
|
|
638
|
+
});
|
|
639
|
+
dim.refresh();
|
|
640
|
+
|
|
641
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
642
|
+
expect(dim.touched(sE)).toBe(false);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test('DiametricDimension.touched delegates to block', () => {
|
|
646
|
+
const dim = new DiametricDimension({
|
|
647
|
+
points: [new Point(0, 0), new Point(100, 0)],
|
|
648
|
+
});
|
|
649
|
+
dim.refresh();
|
|
650
|
+
|
|
651
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
652
|
+
expect(dim.touched(sE)).toBe(false);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('RadialDimension.touched delegates to block', () => {
|
|
656
|
+
const dim = new RadialDimension({
|
|
657
|
+
points: [new Point(0, 0), new Point(100, 0)],
|
|
658
|
+
});
|
|
659
|
+
dim.refresh();
|
|
660
|
+
|
|
661
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
662
|
+
expect(dim.touched(sE)).toBe(false);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test('AngularDimension.touched delegates to block', () => {
|
|
666
|
+
const pt13 = new Point(0, 0, 0, 13); // line 1 start
|
|
667
|
+
const pt14 = new Point(100, 0, 0, 14); // line 1 end
|
|
668
|
+
const pt15 = new Point(0, 0, 0, 15); // line 2 start
|
|
669
|
+
const pt10 = new Point(0, 100, 0, 10); // line 2 end
|
|
670
|
+
const pt16 = new Point(50, 50, 0, 16); // dimension line position
|
|
671
|
+
const pt11 = new Point(50, 50, 0, 11); // text position
|
|
672
|
+
const dim = new AngularDimension({
|
|
673
|
+
points: [pt13, pt14, pt15, pt10, pt16, pt11],
|
|
674
|
+
});
|
|
675
|
+
dim.refresh();
|
|
676
|
+
|
|
677
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
678
|
+
expect(dim.touched(sE)).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test('AlignedDimension.touched uses block.touched not toPolylinePoints', () => {
|
|
682
|
+
// Dimensions delegate touched() to block.touched() which iterates block items.
|
|
683
|
+
const pt13 = new Point(0, 0, 0, 13);
|
|
684
|
+
const pt14 = new Point(100, 0, 0, 14);
|
|
685
|
+
const pt11 = new Point(50, 20, 0, 11);
|
|
686
|
+
const pt10 = new Point(100, 20, 0, 10);
|
|
687
|
+
const dim = new AlignedDimension({
|
|
688
|
+
points: [pt13, pt14, pt11, pt10],
|
|
689
|
+
});
|
|
690
|
+
dim.refresh();
|
|
691
|
+
|
|
692
|
+
// touched() works via block delegation - returns boolean
|
|
693
|
+
const sE = selectionExtremes(new Point(-10, -10), new Point(110, 30));
|
|
694
|
+
expect(dim.touched(sE)).toBe(false);
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// ========================================================
|
|
699
|
+
// Hatch touched tests
|
|
700
|
+
// ========================================================
|
|
701
|
+
describe('Hatch touched tests', () => {
|
|
702
|
+
test('Hatch.touched delegates to child entities', () => {
|
|
703
|
+
const hatch = new Hatch();
|
|
704
|
+
// Add a polyline as child entity
|
|
705
|
+
const boundary = new Polyline({
|
|
706
|
+
points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0)],
|
|
707
|
+
});
|
|
708
|
+
hatch.childEntities.push(boundary);
|
|
709
|
+
|
|
710
|
+
// Selection rectangle crossing the boundary
|
|
711
|
+
const sE = selectionExtremes(new Point(-5, 50), new Point(5, 60));
|
|
712
|
+
expect(hatch.touched(sE)).toBe(true);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test('Hatch.touched returns false when no child entity touched', () => {
|
|
716
|
+
const hatch = new Hatch();
|
|
717
|
+
const boundary = new Polyline({
|
|
718
|
+
points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
|
|
719
|
+
});
|
|
720
|
+
hatch.childEntities.push(boundary);
|
|
721
|
+
|
|
722
|
+
// Selection rectangle far from hatch
|
|
723
|
+
const sE = selectionExtremes(new Point(500, 500), new Point(600, 600));
|
|
724
|
+
expect(hatch.touched(sE)).toBe(false);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test('Hatch.touched with multiple child entities', () => {
|
|
728
|
+
const hatch = new Hatch();
|
|
729
|
+
const boundary1 = new Polyline({
|
|
730
|
+
points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
|
|
731
|
+
});
|
|
732
|
+
const boundary2 = new Polyline({
|
|
733
|
+
points: [new Point(50, 50), new Point(60, 50), new Point(60, 60), new Point(50, 60), new Point(50, 50)],
|
|
734
|
+
});
|
|
735
|
+
hatch.childEntities.push(boundary1);
|
|
736
|
+
hatch.childEntities.push(boundary2);
|
|
737
|
+
|
|
738
|
+
// Touch only the second boundary
|
|
739
|
+
const sE = selectionExtremes(new Point(48, 55), new Point(52, 58));
|
|
740
|
+
expect(hatch.touched(sE)).toBe(true);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// ========================================================
|
|
745
|
+
// Edge cases
|
|
746
|
+
// ========================================================
|
|
747
|
+
describe('Intersection edge cases', () => {
|
|
748
|
+
test('Zero-length line toPolylinePoints', () => {
|
|
749
|
+
const line = new Line({ points: [new Point(5, 5), new Point(5, 5)] });
|
|
750
|
+
const pts = line.toPolylinePoints();
|
|
751
|
+
expect(pts.length).toBe(2);
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test('Large line touched by large rectangle', () => {
|
|
755
|
+
// Line that crosses the selection rectangle edge
|
|
756
|
+
const line = new Line({ points: [new Point(-200, 0), new Point(200, 0)] });
|
|
757
|
+
const sE = selectionExtremes(new Point(-100, -100), new Point(100, 100));
|
|
758
|
+
expect(line.touched(sE)).toBe(true);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test('Entity exactly on rectangle edge', () => {
|
|
762
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
763
|
+
// Rectangle edge coincides with the line
|
|
764
|
+
const sE = selectionExtremes(new Point(0, 0), new Point(10, 10));
|
|
765
|
+
// Coincident lines detected
|
|
766
|
+
expect(line.touched(sE)).toBe(true);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test('Polyline with single point returns valid toPolylinePoints', () => {
|
|
770
|
+
const poly = new Polyline({ points: [new Point(5, 5)] });
|
|
771
|
+
const pts = poly.toPolylinePoints();
|
|
772
|
+
expect(pts.length).toBe(1);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test('Coincident lines intersection', () => {
|
|
776
|
+
const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
777
|
+
const line2 = new Line({ points: [new Point(2, 0), new Point(8, 0)] });
|
|
778
|
+
const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
|
|
779
|
+
// Coincident lines have special status
|
|
780
|
+
expect(result.points.length).toBe(1);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
test('Perpendicular lines at endpoint', () => {
|
|
784
|
+
const line1 = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
785
|
+
const line2 = new Line({ points: [new Point(10, 0), new Point(10, 10)] });
|
|
786
|
+
const result = Intersection.intersectPolylinePolyline(line1.toPolylinePoints(), line2.toPolylinePoints());
|
|
787
|
+
expect(result.status).toBe('Intersection');
|
|
788
|
+
expect(result.points[0].x).toBeCloseTo(10);
|
|
789
|
+
expect(result.points[0].y).toBeCloseTo(0);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test('Diagonal line through circle centre', () => {
|
|
793
|
+
const line = new Line({ points: [new Point(-20, -20), new Point(20, 20)] });
|
|
794
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
|
|
795
|
+
const result = Intersection.intersectPolylinePolyline(line.toPolylinePoints(), circle.toPolylinePoints());
|
|
796
|
+
expect(result.status).toBe('Intersection');
|
|
797
|
+
expect(result.points.length).toBe(2);
|
|
798
|
+
// Points should be equidistant from centre
|
|
799
|
+
const dist1 = Math.sqrt(result.points[0].x ** 2 + result.points[0].y ** 2);
|
|
800
|
+
const dist2 = Math.sqrt(result.points[1].x ** 2 + result.points[1].y ** 2);
|
|
801
|
+
expect(dist1).toBeCloseTo(10);
|
|
802
|
+
expect(dist2).toBeCloseTo(10);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test('Two concentric circles - no intersection', () => {
|
|
806
|
+
const circle1 = new Circle({ points: [new Point(0, 0), new Point(5, 0)] }); // radius=5
|
|
807
|
+
const circle2 = new Circle({ points: [new Point(0, 0), new Point(10, 0)] }); // radius=10
|
|
808
|
+
const result = Intersection.intersectPolylinePolyline(circle1.toPolylinePoints(), circle2.toPolylinePoints());
|
|
809
|
+
expect(result.status).toBe('None');
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test('Polyline segment with negative bulge', () => {
|
|
813
|
+
// Negative bulge = clockwise arc
|
|
814
|
+
const poly = new Polyline({ points: [new Point(0, 0, -1), new Point(0, 10)] });
|
|
815
|
+
const line = new Line({ points: [new Point(-10, 5), new Point(10, 5)] });
|
|
816
|
+
const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
|
|
817
|
+
expect(result.status).toBe('Intersection');
|
|
818
|
+
// CW arc bulges to the left, line crosses through
|
|
819
|
+
expect(result.points.length).toBeGreaterThanOrEqual(1);
|
|
820
|
+
expect(result.points[0].x).toBeCloseTo(-5);
|
|
821
|
+
expect(result.points[0].y).toBeCloseTo(5);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
test('Polyline segment with small bulge (shallow arc)', () => {
|
|
825
|
+
// Small bulge creates a shallow arc
|
|
826
|
+
const poly = new Polyline({ points: [new Point(0, 0, 0.1), new Point(10, 0)] });
|
|
827
|
+
const line = new Line({ points: [new Point(5, -5), new Point(5, 5)] });
|
|
828
|
+
const result = Intersection.intersectPolylinePolyline(poly.toPolylinePoints(), line.toPolylinePoints());
|
|
829
|
+
expect(result.status).toBe('Intersection');
|
|
830
|
+
expect(result.points.length).toBe(1);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// ========================================================
|
|
835
|
+
// Extend flag tests
|
|
836
|
+
// ========================================================
|
|
837
|
+
describe('Intersection with extend flag', () => {
|
|
838
|
+
test('intersectSegmentSegment with extend finds projected intersection', () => {
|
|
839
|
+
// line1 (boundary) is long enough to contain intersection
|
|
840
|
+
// line2 (selected) doesn't reach line1 without extend
|
|
841
|
+
const result1 = Intersection.intersectSegmentSegment(
|
|
842
|
+
new Point(10, -10), new Point(10, 10),
|
|
843
|
+
new Point(0, 0), new Point(5, 0),
|
|
844
|
+
false,
|
|
845
|
+
);
|
|
846
|
+
expect(result1.status).toBe('None');
|
|
847
|
+
// With extend - line2 projects beyond its endpoint
|
|
848
|
+
const result2 = Intersection.intersectSegmentSegment(
|
|
849
|
+
new Point(10, -10), new Point(10, 10),
|
|
850
|
+
new Point(0, 0), new Point(5, 0),
|
|
851
|
+
false, true,
|
|
852
|
+
);
|
|
853
|
+
expect(result2.status).toBe('Perpendicular');
|
|
854
|
+
expect(result2.points[0].x).toBeCloseTo(10);
|
|
855
|
+
expect(result2.points[0].y).toBeCloseTo(0);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
test('intersectPolylinePolyline with extend', () => {
|
|
859
|
+
// Polyline (boundary) is long, line (selected) doesn't reach it
|
|
860
|
+
const poly = [new Point(10, -10), new Point(10, 10)];
|
|
861
|
+
const line = [new Point(0, 0), new Point(5, 0)];
|
|
862
|
+
const result = Intersection.intersectPolylinePolyline(poly, line, true);
|
|
863
|
+
expect(result.status).toBe('Intersection');
|
|
864
|
+
expect(result.points[0].x).toBeCloseTo(10);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test('intersectPolylinePolyline arc with extend finds full circle intersection', () => {
|
|
868
|
+
// Arc from (5,0) to (-5,0) CCW, bulge=1 -> upper semicircle centred at (0,0), r=5
|
|
869
|
+
const arcPolyline = [new Point(5, 0, 1), new Point(-5, 0)];
|
|
870
|
+
// Line below the arc - doesn't intersect the arc without extend
|
|
871
|
+
const polyline = [new Point(0, -10), new Point(0, -3)];
|
|
872
|
+
const result1 = Intersection.intersectPolylinePolyline(polyline, arcPolyline, false);
|
|
873
|
+
expect(result1.status).toBe('None');
|
|
874
|
+
// With extend - uses full circle, finds intersection at (0,-5)
|
|
875
|
+
const result2 = Intersection.intersectPolylinePolyline(polyline, arcPolyline, true);
|
|
876
|
+
expect(result2.status).toBe('Intersection');
|
|
877
|
+
expect(result2.points[0].x).toBeCloseTo(0);
|
|
878
|
+
expect(result2.points[0].y).toBeCloseTo(-5);
|
|
879
|
+
});
|
|
880
|
+
});
|