@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,970 @@
|
|
|
1
|
+
import { Core } from '../../core/core/core.js';
|
|
2
|
+
import { Point } from '../../core/entities/point.js';
|
|
3
|
+
import { Chamfer } from '../../core/tools/chamfer.js';
|
|
4
|
+
import { Strings } from '../../core/lib/strings.js';
|
|
5
|
+
import { SingleSelection } from '../../core/lib/selectionManager.js';
|
|
6
|
+
import { expect, jest } from '@jest/globals';
|
|
7
|
+
import { withMockInput } from '../test-helpers/test-helpers.js';
|
|
8
|
+
|
|
9
|
+
const core = new Core();
|
|
10
|
+
|
|
11
|
+
// ─── register ────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
test('Chamfer.register returns correct command object', () => {
|
|
14
|
+
const reg = Chamfer.register();
|
|
15
|
+
expect(reg.command).toBe('Chamfer');
|
|
16
|
+
expect(reg.shortcut).toBe('CHA');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// ─── action: guard conditions ─────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
test('Chamfer.action does nothing when entities are not set', () => {
|
|
22
|
+
core.scene.clear();
|
|
23
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
24
|
+
const countBefore = core.scene.entities.count();
|
|
25
|
+
|
|
26
|
+
const chamfer = new Chamfer();
|
|
27
|
+
chamfer.action();
|
|
28
|
+
|
|
29
|
+
expect(core.scene.entities.count()).toBe(countBefore);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Chamfer.action notifies when first entity type is not supported', () => {
|
|
33
|
+
core.scene.clear();
|
|
34
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
35
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
36
|
+
|
|
37
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
38
|
+
|
|
39
|
+
const chamfer = new Chamfer();
|
|
40
|
+
chamfer.firstPick.entity = core.scene.entities.get(0); // Circle
|
|
41
|
+
chamfer.secondPick.entity = core.scene.entities.get(1); // Line
|
|
42
|
+
chamfer.firstPick.clickPoint = new Point(0, 0);
|
|
43
|
+
chamfer.secondPick.clickPoint = new Point(5, 0);
|
|
44
|
+
chamfer.action();
|
|
45
|
+
|
|
46
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOCHAMFER));
|
|
47
|
+
notifySpy.mockRestore();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('Chamfer.action notifies when second entity type is not supported', () => {
|
|
51
|
+
core.scene.clear();
|
|
52
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
53
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
54
|
+
|
|
55
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
56
|
+
|
|
57
|
+
const chamfer = new Chamfer();
|
|
58
|
+
chamfer.firstPick.entity = core.scene.entities.get(0); // Line
|
|
59
|
+
chamfer.secondPick.entity = core.scene.entities.get(1); // Circle
|
|
60
|
+
chamfer.firstPick.clickPoint = new Point(5, 0);
|
|
61
|
+
chamfer.secondPick.clickPoint = new Point(0, 0);
|
|
62
|
+
chamfer.action();
|
|
63
|
+
|
|
64
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOCHAMFER));
|
|
65
|
+
notifySpy.mockRestore();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('Chamfer.action notifies for parallel lines', () => {
|
|
69
|
+
core.scene.clear();
|
|
70
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
71
|
+
core.scene.addItem('Line', { points: [new Point(0, 5), new Point(10, 5)] });
|
|
72
|
+
|
|
73
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
74
|
+
|
|
75
|
+
const chamfer = new Chamfer();
|
|
76
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
77
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
78
|
+
chamfer.firstPick.clickPoint = new Point(5, 0);
|
|
79
|
+
chamfer.secondPick.clickPoint = new Point(5, 5);
|
|
80
|
+
chamfer.action();
|
|
81
|
+
|
|
82
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.PARALLELLINES));
|
|
83
|
+
notifySpy.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ─── action: distance method ──────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
test('Chamfer.action dist=0 trimMode=true trims both lines to intersection', () => {
|
|
89
|
+
// Lines only touching when extended: horizontal (0,0)→(8,0), vertical (10,-10)→(10,-2)
|
|
90
|
+
// Infinite extensions meet at (10, 0).
|
|
91
|
+
core.scene.clear();
|
|
92
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(8, 0)] });
|
|
93
|
+
core.scene.addItem('Line', { points: [new Point(10, -10), new Point(10, -2)] });
|
|
94
|
+
|
|
95
|
+
core.scene.headers.chamferDistanceA = 0;
|
|
96
|
+
core.scene.headers.chamferDistanceB = 0;
|
|
97
|
+
core.scene.headers.chamferMode = false;
|
|
98
|
+
core.scene.headers.trimMode = true;
|
|
99
|
+
|
|
100
|
+
const chamfer = new Chamfer();
|
|
101
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
102
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
103
|
+
chamfer.firstPick.clickPoint = new Point(4, 0);
|
|
104
|
+
chamfer.secondPick.clickPoint = new Point(10, -6);
|
|
105
|
+
chamfer.action();
|
|
106
|
+
|
|
107
|
+
// No chamfer line added
|
|
108
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
109
|
+
|
|
110
|
+
const line1 = core.scene.entities.get(0);
|
|
111
|
+
const line2 = core.scene.entities.get(1);
|
|
112
|
+
|
|
113
|
+
// Line 1 extended to intersection (10, 0)
|
|
114
|
+
expect(line1.points[1].x).toBeCloseTo(10);
|
|
115
|
+
expect(line1.points[1].y).toBeCloseTo(0);
|
|
116
|
+
|
|
117
|
+
// Line 2 extended to intersection (10, 0)
|
|
118
|
+
expect(line2.points[1].x).toBeCloseTo(10);
|
|
119
|
+
expect(line2.points[1].y).toBeCloseTo(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('Chamfer.action dist=0 trimMode=false makes no changes', () => {
|
|
123
|
+
core.scene.clear();
|
|
124
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(8, 0)] });
|
|
125
|
+
core.scene.addItem('Line', { points: [new Point(10, -10), new Point(10, -2)] });
|
|
126
|
+
|
|
127
|
+
core.scene.headers.chamferDistanceA = 0;
|
|
128
|
+
core.scene.headers.chamferDistanceB = 0;
|
|
129
|
+
core.scene.headers.chamferMode = false;
|
|
130
|
+
core.scene.headers.trimMode = false;
|
|
131
|
+
|
|
132
|
+
const chamfer = new Chamfer();
|
|
133
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
134
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
135
|
+
chamfer.firstPick.clickPoint = new Point(4, 0);
|
|
136
|
+
chamfer.secondPick.clickPoint = new Point(10, -6);
|
|
137
|
+
chamfer.action();
|
|
138
|
+
|
|
139
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
140
|
+
expect(core.scene.entities.get(0).points[1].x).toBeCloseTo(8);
|
|
141
|
+
expect(core.scene.entities.get(1).points[1].y).toBeCloseTo(-2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('Chamfer.action distance method trimMode=true adds chamfer line and trims', () => {
|
|
145
|
+
// Perpendicular L-corner at origin: horizontal (-10,0)→(0,0), vertical (0,0)→(0,10)
|
|
146
|
+
// Equal distances of 2 → chamfer endpoints at (-2,0) and (0,2)
|
|
147
|
+
core.scene.clear();
|
|
148
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
149
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
150
|
+
|
|
151
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
152
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
153
|
+
core.scene.headers.chamferMode = false;
|
|
154
|
+
core.scene.headers.trimMode = true;
|
|
155
|
+
|
|
156
|
+
const chamfer = new Chamfer();
|
|
157
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
158
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
159
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
160
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
161
|
+
chamfer.action();
|
|
162
|
+
|
|
163
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
164
|
+
|
|
165
|
+
const line1 = core.scene.entities.get(0);
|
|
166
|
+
const line2 = core.scene.entities.get(1);
|
|
167
|
+
const chamferLine = core.scene.entities.get(2);
|
|
168
|
+
|
|
169
|
+
expect(chamferLine.type).toBe('Line');
|
|
170
|
+
|
|
171
|
+
// Chamfer line connects (-2,0) and (0,2)
|
|
172
|
+
expect(chamferLine.points[0].x).toBeCloseTo(-2);
|
|
173
|
+
expect(chamferLine.points[0].y).toBeCloseTo(0);
|
|
174
|
+
expect(chamferLine.points[1].x).toBeCloseTo(0);
|
|
175
|
+
expect(chamferLine.points[1].y).toBeCloseTo(2);
|
|
176
|
+
|
|
177
|
+
// Line 1 trimmed: kept end (-10,0), trimmed to (-2,0)
|
|
178
|
+
expect(line1.points[0].x).toBeCloseTo(-10);
|
|
179
|
+
expect(line1.points[0].y).toBeCloseTo(0);
|
|
180
|
+
expect(line1.points[1].x).toBeCloseTo(-2);
|
|
181
|
+
expect(line1.points[1].y).toBeCloseTo(0);
|
|
182
|
+
|
|
183
|
+
// Line 2 trimmed: kept end (0,10), trimmed to (0,2)
|
|
184
|
+
expect(line2.points[0].x).toBeCloseTo(0);
|
|
185
|
+
expect(line2.points[0].y).toBeCloseTo(10);
|
|
186
|
+
expect(line2.points[1].x).toBeCloseTo(0);
|
|
187
|
+
expect(line2.points[1].y).toBeCloseTo(2);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('Chamfer.action distance method asymmetric distances', () => {
|
|
191
|
+
// L-corner at origin, distA=3 along horizontal, distB=1 along vertical
|
|
192
|
+
core.scene.clear();
|
|
193
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
194
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
195
|
+
|
|
196
|
+
core.scene.headers.chamferDistanceA = 3;
|
|
197
|
+
core.scene.headers.chamferDistanceB = 1;
|
|
198
|
+
core.scene.headers.chamferMode = false;
|
|
199
|
+
core.scene.headers.trimMode = true;
|
|
200
|
+
|
|
201
|
+
const chamfer = new Chamfer();
|
|
202
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
203
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
204
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
205
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
206
|
+
chamfer.action();
|
|
207
|
+
|
|
208
|
+
const chamferLine = core.scene.entities.get(2);
|
|
209
|
+
expect(chamferLine.type).toBe('Line');
|
|
210
|
+
expect(chamferLine.points[0].x).toBeCloseTo(-3);
|
|
211
|
+
expect(chamferLine.points[0].y).toBeCloseTo(0);
|
|
212
|
+
expect(chamferLine.points[1].x).toBeCloseTo(0);
|
|
213
|
+
expect(chamferLine.points[1].y).toBeCloseTo(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('Chamfer.action distance method trimMode=false adds chamfer line only', () => {
|
|
217
|
+
core.scene.clear();
|
|
218
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
219
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
220
|
+
|
|
221
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
222
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
223
|
+
core.scene.headers.chamferMode = false;
|
|
224
|
+
core.scene.headers.trimMode = false;
|
|
225
|
+
|
|
226
|
+
const chamfer = new Chamfer();
|
|
227
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
228
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
229
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
230
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
231
|
+
chamfer.action();
|
|
232
|
+
|
|
233
|
+
// Chamfer line added, original lines unchanged
|
|
234
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
235
|
+
expect(core.scene.entities.get(2).type).toBe('Line');
|
|
236
|
+
|
|
237
|
+
// Original lines still have their original endpoints
|
|
238
|
+
expect(core.scene.entities.get(0).points[1].x).toBeCloseTo(0);
|
|
239
|
+
expect(core.scene.entities.get(0).points[1].y).toBeCloseTo(0);
|
|
240
|
+
expect(core.scene.entities.get(1).points[0].x).toBeCloseTo(0);
|
|
241
|
+
expect(core.scene.entities.get(1).points[0].y).toBeCloseTo(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('Chamfer.action notifies when distance is too large for the segments', () => {
|
|
245
|
+
// Lines only 5 units long from intersection — distance 10 won't fit
|
|
246
|
+
core.scene.clear();
|
|
247
|
+
core.scene.addItem('Line', { points: [new Point(-5, 0), new Point(0, 0)] });
|
|
248
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 5)] });
|
|
249
|
+
|
|
250
|
+
core.scene.headers.chamferDistanceA = 10;
|
|
251
|
+
core.scene.headers.chamferDistanceB = 10;
|
|
252
|
+
core.scene.headers.chamferMode = false;
|
|
253
|
+
core.scene.headers.trimMode = true;
|
|
254
|
+
|
|
255
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
256
|
+
|
|
257
|
+
const chamfer = new Chamfer();
|
|
258
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
259
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
260
|
+
chamfer.firstPick.clickPoint = new Point(-2, 0);
|
|
261
|
+
chamfer.secondPick.clickPoint = new Point(0, 2);
|
|
262
|
+
chamfer.action();
|
|
263
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.MAXVALUE));
|
|
264
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
265
|
+
notifySpy.mockRestore();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ─── action: angle method ─────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
test('Chamfer.action angle method 45° on perpendicular lines matches distance method', () => {
|
|
271
|
+
// L-corner at origin, distA=2, angle=45° on perpendicular lines.
|
|
272
|
+
// With perpendicular lines and 45°, the chamfer endpoint on line2 should also be 2 units
|
|
273
|
+
// from the intersection — matching a symmetric distance chamfer.
|
|
274
|
+
core.scene.clear();
|
|
275
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
276
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
277
|
+
|
|
278
|
+
core.scene.headers.chamferLength = 2;
|
|
279
|
+
core.scene.headers.chamferAngle = 45; // degrees
|
|
280
|
+
core.scene.headers.chamferMode = true;
|
|
281
|
+
core.scene.headers.trimMode = true;
|
|
282
|
+
|
|
283
|
+
const chamfer = new Chamfer();
|
|
284
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
285
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
286
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
287
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
288
|
+
chamfer.action();
|
|
289
|
+
|
|
290
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
291
|
+
const chamferLine = core.scene.entities.get(2);
|
|
292
|
+
expect(chamferLine.type).toBe('Line');
|
|
293
|
+
|
|
294
|
+
// First chamfer point: 2 units along line1 from intersection → (-2, 0)
|
|
295
|
+
expect(chamferLine.points[0].x).toBeCloseTo(-2);
|
|
296
|
+
expect(chamferLine.points[0].y).toBeCloseTo(0);
|
|
297
|
+
|
|
298
|
+
// Second chamfer point: where chamfer ray meets line2 → (0, 2) for 45° on perpendicular lines
|
|
299
|
+
expect(chamferLine.points[1].x).toBeCloseTo(0);
|
|
300
|
+
expect(chamferLine.points[1].y).toBeCloseTo(2);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ─── action: click at intersection ───────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
test('Chamfer.action notifies when click coincides with intersection', () => {
|
|
306
|
+
// L-corner at origin. Clicking firstClickPoint exactly at (0,0) makes
|
|
307
|
+
// firstClickDistance = 0, triggering the "Selected corner cannot be chamfered" guard.
|
|
308
|
+
core.scene.clear();
|
|
309
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
310
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
311
|
+
|
|
312
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
313
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
314
|
+
core.scene.headers.chamferMode = false;
|
|
315
|
+
core.scene.headers.trimMode = true;
|
|
316
|
+
|
|
317
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
318
|
+
|
|
319
|
+
const chamfer = new Chamfer();
|
|
320
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
321
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
322
|
+
chamfer.firstPick.clickPoint = new Point(0, 0); // exactly at the intersection
|
|
323
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
324
|
+
chamfer.action();
|
|
325
|
+
|
|
326
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.SELECTION));
|
|
327
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
328
|
+
notifySpy.mockRestore();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// ─── action: angle method edge cases ─────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
test('Chamfer.action angle method notifies INVALIDNUMBER when angle is zero', () => {
|
|
334
|
+
core.scene.clear();
|
|
335
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
336
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
337
|
+
|
|
338
|
+
core.scene.headers.chamferLength = 2;
|
|
339
|
+
core.scene.headers.chamferAngle = 0; // alpha = 0 * π/180 = 0, fails alpha <= 0 guard
|
|
340
|
+
core.scene.headers.chamferMode = true;
|
|
341
|
+
core.scene.headers.trimMode = true;
|
|
342
|
+
|
|
343
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
344
|
+
|
|
345
|
+
const chamfer = new Chamfer();
|
|
346
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
347
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
348
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
349
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
350
|
+
chamfer.action();
|
|
351
|
+
|
|
352
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
|
|
353
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
354
|
+
notifySpy.mockRestore();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('Chamfer.action angle method notifies PARALLELLINES when chamfer direction is parallel to line2', () => {
|
|
358
|
+
// L-corner at origin, angle = 90°.
|
|
359
|
+
// firstClickUnit along line1 clicked from left: (-1, 0).
|
|
360
|
+
// rotAngle = π - π/2 = π/2.
|
|
361
|
+
// candidate1 = rotate(-1,0) by +π/2 = (0,-1); dot with secondClickUnit (0,1) = -1 < 0.
|
|
362
|
+
// candidate2 = rotate(-1,0) by -π/2 = (0, 1) → parallel to secondLineDirection (0,1) → cross = 0.
|
|
363
|
+
core.scene.clear();
|
|
364
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
365
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
366
|
+
|
|
367
|
+
core.scene.headers.chamferLength = 2;
|
|
368
|
+
core.scene.headers.chamferAngle = 90; // degrees
|
|
369
|
+
core.scene.headers.chamferMode = true;
|
|
370
|
+
core.scene.headers.trimMode = true;
|
|
371
|
+
|
|
372
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
373
|
+
|
|
374
|
+
const chamfer = new Chamfer();
|
|
375
|
+
chamfer.firstPick.entity = core.scene.entities.get(0);
|
|
376
|
+
chamfer.secondPick.entity = core.scene.entities.get(1);
|
|
377
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
378
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
379
|
+
chamfer.action();
|
|
380
|
+
|
|
381
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.PARALLELLINES));
|
|
382
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
383
|
+
notifySpy.mockRestore();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ─── execute ──────────────────────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
test('Chamfer.execute returns early when first input is undefined', async () => {
|
|
389
|
+
core.scene.clear();
|
|
390
|
+
const chamfer = new Chamfer();
|
|
391
|
+
await withMockInput(core.scene, [], async () => {
|
|
392
|
+
await chamfer.execute();
|
|
393
|
+
});
|
|
394
|
+
// no error thrown — confirms early-return path
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test('Chamfer.execute selects two lines and calls executeCommand', async () => {
|
|
398
|
+
core.scene.clear();
|
|
399
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
400
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
401
|
+
|
|
402
|
+
const executeCommandSpy = jest.fn();
|
|
403
|
+
|
|
404
|
+
await withMockInput(
|
|
405
|
+
core.scene,
|
|
406
|
+
[new SingleSelection(0, new Point(-5, 0)), new SingleSelection(1, new Point(0, 5))],
|
|
407
|
+
async () => {
|
|
408
|
+
const chamfer = new Chamfer();
|
|
409
|
+
await chamfer.execute();
|
|
410
|
+
expect(executeCommandSpy).toHaveBeenCalled();
|
|
411
|
+
},
|
|
412
|
+
{ extraMethods: { executeCommand: executeCommandSpy } },
|
|
413
|
+
);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('Chamfer.execute sets chamferDistanceA and chamferDistanceB via Distance option', async () => {
|
|
417
|
+
core.scene.headers.chamferDistanceA = 0;
|
|
418
|
+
core.scene.headers.chamferDistanceB = 0;
|
|
419
|
+
|
|
420
|
+
await withMockInput(
|
|
421
|
+
core.scene,
|
|
422
|
+
['Distance', 4, 6, undefined],
|
|
423
|
+
async () => {
|
|
424
|
+
const chamfer = new Chamfer();
|
|
425
|
+
await chamfer.execute();
|
|
426
|
+
},
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
expect(core.scene.headers.chamferDistanceA).toBe(4);
|
|
430
|
+
expect(core.scene.headers.chamferDistanceB).toBe(6);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('Chamfer.execute notifies INVALIDNUMBER for negative first distance', async () => {
|
|
434
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
435
|
+
|
|
436
|
+
await withMockInput(
|
|
437
|
+
core.scene,
|
|
438
|
+
['Distance', -2, undefined],
|
|
439
|
+
async () => {
|
|
440
|
+
const chamfer = new Chamfer();
|
|
441
|
+
await chamfer.execute();
|
|
442
|
+
},
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
|
|
446
|
+
notifySpy.mockRestore();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('Chamfer.execute notifies INVALIDNUMBER for negative second distance', async () => {
|
|
450
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
451
|
+
|
|
452
|
+
await withMockInput(
|
|
453
|
+
core.scene,
|
|
454
|
+
['Distance', 3, -1, undefined],
|
|
455
|
+
async () => {
|
|
456
|
+
const chamfer = new Chamfer();
|
|
457
|
+
await chamfer.execute();
|
|
458
|
+
},
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
|
|
462
|
+
notifySpy.mockRestore();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test('Chamfer.execute sets chamferLength and chamferAngle via Angle option', async () => {
|
|
466
|
+
core.scene.headers.chamferLength = 0;
|
|
467
|
+
core.scene.headers.chamferAngle = 0;
|
|
468
|
+
|
|
469
|
+
await withMockInput(
|
|
470
|
+
core.scene,
|
|
471
|
+
['Angle', 3, 60, undefined],
|
|
472
|
+
async () => {
|
|
473
|
+
const chamfer = new Chamfer();
|
|
474
|
+
await chamfer.execute();
|
|
475
|
+
},
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
expect(core.scene.headers.chamferLength).toBe(3);
|
|
479
|
+
expect(core.scene.headers.chamferAngle).toBe(60);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('Chamfer.execute notifies INVALIDNUMBER for negative length in Angle option', async () => {
|
|
483
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
484
|
+
|
|
485
|
+
await withMockInput(
|
|
486
|
+
core.scene,
|
|
487
|
+
['Angle', -1, undefined],
|
|
488
|
+
async () => {
|
|
489
|
+
const chamfer = new Chamfer();
|
|
490
|
+
await chamfer.execute();
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
|
|
495
|
+
notifySpy.mockRestore();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('Chamfer.execute notifies INVALIDNUMBER for out-of-range angle in Angle option', async () => {
|
|
499
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
500
|
+
|
|
501
|
+
await withMockInput(
|
|
502
|
+
core.scene,
|
|
503
|
+
['Angle', 3, 200, undefined], // 200° is >= 180, invalid
|
|
504
|
+
async () => {
|
|
505
|
+
const chamfer = new Chamfer();
|
|
506
|
+
await chamfer.execute();
|
|
507
|
+
},
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
|
|
511
|
+
notifySpy.mockRestore();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test('Chamfer.execute sets trimMode to false via Trim option', async () => {
|
|
515
|
+
core.scene.headers.trimMode = true;
|
|
516
|
+
|
|
517
|
+
await withMockInput(
|
|
518
|
+
core.scene,
|
|
519
|
+
['Trim', 'No trim', undefined],
|
|
520
|
+
async () => {
|
|
521
|
+
const chamfer = new Chamfer();
|
|
522
|
+
await chamfer.execute();
|
|
523
|
+
},
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
expect(core.scene.headers.trimMode).toBe(false);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test('Chamfer.execute sets chamferMode to true via Method=Angle option', async () => {
|
|
530
|
+
core.scene.headers.chamferMode = false;
|
|
531
|
+
|
|
532
|
+
await withMockInput(
|
|
533
|
+
core.scene,
|
|
534
|
+
['Method', 'Angle', undefined],
|
|
535
|
+
async () => {
|
|
536
|
+
const chamfer = new Chamfer();
|
|
537
|
+
await chamfer.execute();
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
expect(core.scene.headers.chamferMode).toBe(true);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('Chamfer.execute re-prompts and notifies when first selected entity is not a Line', async () => {
|
|
545
|
+
core.scene.clear();
|
|
546
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
547
|
+
|
|
548
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
549
|
+
|
|
550
|
+
await withMockInput(
|
|
551
|
+
core.scene,
|
|
552
|
+
[new SingleSelection(0, new Point(0, 0)), undefined],
|
|
553
|
+
async () => {
|
|
554
|
+
const chamfer = new Chamfer();
|
|
555
|
+
await chamfer.execute();
|
|
556
|
+
},
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOCHAMFER));
|
|
560
|
+
notifySpy.mockRestore();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('Chamfer.execute re-prompts and notifies when second selected entity is not a Line', async () => {
|
|
564
|
+
core.scene.clear();
|
|
565
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
566
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
567
|
+
|
|
568
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
569
|
+
|
|
570
|
+
await withMockInput(
|
|
571
|
+
core.scene,
|
|
572
|
+
[new SingleSelection(0, new Point(5, 0)), new SingleSelection(1, new Point(0, 0)), undefined],
|
|
573
|
+
async () => {
|
|
574
|
+
const chamfer = new Chamfer();
|
|
575
|
+
await chamfer.execute();
|
|
576
|
+
},
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOCHAMFER));
|
|
580
|
+
notifySpy.mockRestore();
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// ─── action: polyline scenarios ───────────────────────────────────────────────
|
|
584
|
+
|
|
585
|
+
// Geometry shared across the Line + Lwpolyline tests:
|
|
586
|
+
// Line: (0,0)→(0,10) (vertical)
|
|
587
|
+
// Lwpolyline: [(-20,0), (-10,0), (0,0), (10,0)] (horizontal, 3 segments)
|
|
588
|
+
// Infinite extensions of the y=0 and x=0 lines meet at (0,0).
|
|
589
|
+
// With distance 2:
|
|
590
|
+
// polyChamfer = (-2, 0) (on the polyline segment y=0)
|
|
591
|
+
// lineChamfer = ( 0, 2) (on the external line x=0)
|
|
592
|
+
|
|
593
|
+
test('Chamfer.action dist>0 trimMode=true Line first + Lwpolyline second keepStart: chamfer embedded in polyline', () => {
|
|
594
|
+
// Click polyline near (-15,0) → segment 1. keepStart keeps the start portion.
|
|
595
|
+
core.scene.clear();
|
|
596
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
597
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
598
|
+
|
|
599
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
600
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
601
|
+
core.scene.headers.chamferMode = false;
|
|
602
|
+
core.scene.headers.trimMode = true;
|
|
603
|
+
|
|
604
|
+
const lineEntity = core.scene.entities.get(0);
|
|
605
|
+
const polyEntity = core.scene.entities.get(1);
|
|
606
|
+
const polyClickPoint = new Point(-15, 0);
|
|
607
|
+
|
|
608
|
+
const chamfer = new Chamfer();
|
|
609
|
+
chamfer.firstPick.entity = lineEntity;
|
|
610
|
+
chamfer.firstPick.segment = lineEntity;
|
|
611
|
+
chamfer.firstPick.segmentIndex = null;
|
|
612
|
+
chamfer.firstPick.clickPoint = new Point(0, 5);
|
|
613
|
+
chamfer.secondPick.entity = polyEntity;
|
|
614
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(polyClickPoint);
|
|
615
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(polyClickPoint);
|
|
616
|
+
chamfer.secondPick.clickPoint = polyClickPoint;
|
|
617
|
+
chamfer.action();
|
|
618
|
+
|
|
619
|
+
// Line is consumed into the polyline; no separate chamfer Line entity
|
|
620
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
621
|
+
|
|
622
|
+
const poly = core.scene.entities.get(0);
|
|
623
|
+
// [(-20,0), polyChamfer(-2,0), lineChamfer(0,2), lineKeptEnd(0,10)]
|
|
624
|
+
expect(poly.points.length).toBe(4);
|
|
625
|
+
expect(poly.points[0].x).toBeCloseTo(-20);
|
|
626
|
+
expect(poly.points[0].y).toBeCloseTo(0);
|
|
627
|
+
expect(poly.points[1].x).toBeCloseTo(-2);
|
|
628
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
629
|
+
expect(poly.points[2].x).toBeCloseTo(0);
|
|
630
|
+
expect(poly.points[2].y).toBeCloseTo(2);
|
|
631
|
+
expect(poly.points[3].x).toBeCloseTo(0);
|
|
632
|
+
expect(poly.points[3].y).toBeCloseTo(10);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test('Chamfer.action dist>0 trimMode=true Lwpolyline first + Line second keepStart: same result with entities reversed', () => {
|
|
636
|
+
core.scene.clear();
|
|
637
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
638
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
639
|
+
|
|
640
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
641
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
642
|
+
core.scene.headers.chamferMode = false;
|
|
643
|
+
core.scene.headers.trimMode = true;
|
|
644
|
+
|
|
645
|
+
const polyEntity = core.scene.entities.get(0);
|
|
646
|
+
const lineEntity = core.scene.entities.get(1);
|
|
647
|
+
const polyClickPoint = new Point(-15, 0);
|
|
648
|
+
|
|
649
|
+
const chamfer = new Chamfer();
|
|
650
|
+
chamfer.firstPick.entity = polyEntity;
|
|
651
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(polyClickPoint);
|
|
652
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(polyClickPoint);
|
|
653
|
+
chamfer.firstPick.clickPoint = polyClickPoint;
|
|
654
|
+
chamfer.secondPick.entity = lineEntity;
|
|
655
|
+
chamfer.secondPick.segment = lineEntity;
|
|
656
|
+
chamfer.secondPick.segmentIndex = null;
|
|
657
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
658
|
+
chamfer.action();
|
|
659
|
+
|
|
660
|
+
// Line is consumed; no separate chamfer Line entity
|
|
661
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
662
|
+
|
|
663
|
+
const poly = core.scene.entities.get(0);
|
|
664
|
+
expect(poly.points.length).toBe(4);
|
|
665
|
+
expect(poly.points[0].x).toBeCloseTo(-20);
|
|
666
|
+
expect(poly.points[1].x).toBeCloseTo(-2);
|
|
667
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
668
|
+
expect(poly.points[2].x).toBeCloseTo(0);
|
|
669
|
+
expect(poly.points[2].y).toBeCloseTo(2);
|
|
670
|
+
expect(poly.points[3].x).toBeCloseTo(0);
|
|
671
|
+
expect(poly.points[3].y).toBeCloseTo(10);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test('Chamfer.action dist>0 trimMode=true Line + Lwpolyline keepEnd: keeps end portion of polyline', () => {
|
|
675
|
+
// Click polyline near (8,0) → segment 3 (right of intersection). keepEnd.
|
|
676
|
+
// polyChamfer = (2,0), lineChamfer = (0,2).
|
|
677
|
+
core.scene.clear();
|
|
678
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
679
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
680
|
+
|
|
681
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
682
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
683
|
+
core.scene.headers.chamferMode = false;
|
|
684
|
+
core.scene.headers.trimMode = true;
|
|
685
|
+
|
|
686
|
+
const lineEntity = core.scene.entities.get(0);
|
|
687
|
+
const polyEntity = core.scene.entities.get(1);
|
|
688
|
+
const polyClickPoint = new Point(8, 0);
|
|
689
|
+
|
|
690
|
+
const chamfer = new Chamfer();
|
|
691
|
+
chamfer.firstPick.entity = lineEntity;
|
|
692
|
+
chamfer.firstPick.segment = lineEntity;
|
|
693
|
+
chamfer.firstPick.segmentIndex = null;
|
|
694
|
+
chamfer.firstPick.clickPoint = new Point(0, 5);
|
|
695
|
+
chamfer.secondPick.entity = polyEntity;
|
|
696
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(polyClickPoint);
|
|
697
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(polyClickPoint);
|
|
698
|
+
chamfer.secondPick.clickPoint = polyClickPoint;
|
|
699
|
+
chamfer.action();
|
|
700
|
+
|
|
701
|
+
// Line consumed; end portion of polyline kept; no separate chamfer Line entity
|
|
702
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
703
|
+
|
|
704
|
+
const poly = core.scene.entities.get(0);
|
|
705
|
+
// [lineKeptEnd(0,10), lineChamfer(0,2), polyChamfer(2,0), (10,0)]
|
|
706
|
+
expect(poly.points.length).toBe(4);
|
|
707
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
708
|
+
expect(poly.points[0].y).toBeCloseTo(10);
|
|
709
|
+
expect(poly.points[1].x).toBeCloseTo(0);
|
|
710
|
+
expect(poly.points[1].y).toBeCloseTo(2);
|
|
711
|
+
expect(poly.points[2].x).toBeCloseTo(2);
|
|
712
|
+
expect(poly.points[2].y).toBeCloseTo(0);
|
|
713
|
+
expect(poly.points[3].x).toBeCloseTo(10);
|
|
714
|
+
expect(poly.points[3].y).toBeCloseTo(0);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test('Chamfer.action dist>0 trimMode=true same Lwpolyline consecutive segments: chamfer points inserted', () => {
|
|
718
|
+
// L-shaped polyline; chamfer the inner corner. No separate chamfer Line entity.
|
|
719
|
+
// Segment 1: (-10,0)→(0,0), Segment 2: (0,0)→(0,10). Corner at (0,0).
|
|
720
|
+
// dist=2 → chamfer at (-2,0) and (0,2).
|
|
721
|
+
core.scene.clear();
|
|
722
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-10, 0), new Point(0, 0), new Point(0, 10)] });
|
|
723
|
+
|
|
724
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
725
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
726
|
+
core.scene.headers.chamferMode = false;
|
|
727
|
+
core.scene.headers.trimMode = true;
|
|
728
|
+
|
|
729
|
+
const polyEntity = core.scene.entities.get(0);
|
|
730
|
+
|
|
731
|
+
const chamfer = new Chamfer();
|
|
732
|
+
chamfer.firstPick.entity = polyEntity;
|
|
733
|
+
chamfer.secondPick.entity = polyEntity;
|
|
734
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(-5, 0));
|
|
735
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(-5, 0));
|
|
736
|
+
chamfer.firstPick.clickPoint = new Point(-5, 0);
|
|
737
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
738
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
739
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
740
|
+
chamfer.action();
|
|
741
|
+
|
|
742
|
+
// Chamfer segment is a straight polyline segment — no separate Line entity
|
|
743
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
744
|
+
|
|
745
|
+
const poly = core.scene.entities.get(0);
|
|
746
|
+
// Corner vertex replaced: [(-10,0), (-2,0), (0,2), (0,10)]
|
|
747
|
+
expect(poly.points.length).toBe(4);
|
|
748
|
+
expect(poly.points[0].x).toBeCloseTo(-10);
|
|
749
|
+
expect(poly.points[0].y).toBeCloseTo(0);
|
|
750
|
+
expect(poly.points[1].x).toBeCloseTo(-2);
|
|
751
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
752
|
+
expect(poly.points[2].x).toBeCloseTo(0);
|
|
753
|
+
expect(poly.points[2].y).toBeCloseTo(2);
|
|
754
|
+
expect(poly.points[3].x).toBeCloseTo(0);
|
|
755
|
+
expect(poly.points[3].y).toBeCloseTo(10);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test('Chamfer.action dist>0 trimMode=true same Lwpolyline open ends: separate chamfer Line fills gap', () => {
|
|
759
|
+
// 4-point polyline: [(-5,0),(0,0),(1,0),(1,5)]. Segments 1 and 3 are the open ends.
|
|
760
|
+
// They are non-adjacent (diff=2) so the open-ends path is taken.
|
|
761
|
+
// Infinite lines of seg1 (y=0) and seg3 (x=1) meet at (1,0).
|
|
762
|
+
// dist=2, clicks at (-3,0) and (1,3):
|
|
763
|
+
// firstChamfer = (-1,0), secondChamfer = (1,2).
|
|
764
|
+
core.scene.clear();
|
|
765
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-5, 0), new Point(0, 0), new Point(1, 0), new Point(1, 5)] });
|
|
766
|
+
|
|
767
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
768
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
769
|
+
core.scene.headers.chamferMode = false;
|
|
770
|
+
core.scene.headers.trimMode = true;
|
|
771
|
+
|
|
772
|
+
const polyEntity = core.scene.entities.get(0);
|
|
773
|
+
|
|
774
|
+
const chamfer = new Chamfer();
|
|
775
|
+
chamfer.firstPick.entity = polyEntity;
|
|
776
|
+
chamfer.secondPick.entity = polyEntity;
|
|
777
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(-3, 0)); // segment 1 (index 1)
|
|
778
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(-3, 0));
|
|
779
|
+
chamfer.firstPick.clickPoint = new Point(-3, 0);
|
|
780
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(1, 3)); // segment 3 (last, index 3)
|
|
781
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(1, 3));
|
|
782
|
+
chamfer.secondPick.clickPoint = new Point(1, 3);
|
|
783
|
+
chamfer.action();
|
|
784
|
+
|
|
785
|
+
// Separate chamfer Line entity fills the gap between the trimmed open ends
|
|
786
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
787
|
+
|
|
788
|
+
const chamferLine = core.scene.entities.get(1);
|
|
789
|
+
expect(chamferLine.type).toBe('Line');
|
|
790
|
+
// Chamfer line from firstChamfer(-1,0) to secondChamfer(1,2)
|
|
791
|
+
expect(chamferLine.points[0].x).toBeCloseTo(-1);
|
|
792
|
+
expect(chamferLine.points[0].y).toBeCloseTo(0);
|
|
793
|
+
expect(chamferLine.points[1].x).toBeCloseTo(1);
|
|
794
|
+
expect(chamferLine.points[1].y).toBeCloseTo(2);
|
|
795
|
+
|
|
796
|
+
const poly = core.scene.entities.get(0);
|
|
797
|
+
expect(poly.points.length).toBe(4);
|
|
798
|
+
// Start of polyline trimmed to first chamfer point: (-1,0)
|
|
799
|
+
expect(poly.points[0].x).toBeCloseTo(-1);
|
|
800
|
+
expect(poly.points[0].y).toBeCloseTo(0);
|
|
801
|
+
// End of polyline trimmed to second chamfer point: (1,2)
|
|
802
|
+
expect(poly.points[3].x).toBeCloseTo(1);
|
|
803
|
+
expect(poly.points[3].y).toBeCloseTo(2);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test('Chamfer.action dist>0 trimMode=true closed Lwpolyline seg3+closing-seg: corner-splice at shared vertex', () => {
|
|
807
|
+
// Square: [(0,0),(10,0),(10,10),(0,10)], closed (flag 1).
|
|
808
|
+
// Closing segment (idx=4) goes (0,10)→(0,0); seg 3 goes (10,10)→(0,10).
|
|
809
|
+
// Corner at (0,10) — chamfer dist=2 trims to (2,10) on seg3 and (0,8) on closing seg.
|
|
810
|
+
core.scene.clear();
|
|
811
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
812
|
+
const polyEntity = core.scene.entities.get(0);
|
|
813
|
+
polyEntity.flags.setFlagValue(1);
|
|
814
|
+
|
|
815
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
816
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
817
|
+
core.scene.headers.chamferMode = false;
|
|
818
|
+
core.scene.headers.trimMode = true;
|
|
819
|
+
|
|
820
|
+
const chamfer = new Chamfer();
|
|
821
|
+
chamfer.firstPick.entity = polyEntity;
|
|
822
|
+
chamfer.secondPick.entity = polyEntity;
|
|
823
|
+
// Click on segment 3 near (5,10) and on closing segment near (0,5)
|
|
824
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(5, 10));
|
|
825
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 10));
|
|
826
|
+
chamfer.firstPick.clickPoint = new Point(5, 10);
|
|
827
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
828
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
829
|
+
chamfer.secondPick.clickPoint = new Point(0, 5);
|
|
830
|
+
chamfer.action();
|
|
831
|
+
|
|
832
|
+
// Corner-splice: no extra entity, just the modified polyline
|
|
833
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
834
|
+
const poly = core.scene.entities.get(0);
|
|
835
|
+
// Shared vertex (0,10) at index 3 replaced by two chamfer points → total 5 points
|
|
836
|
+
expect(poly.points.length).toBe(5);
|
|
837
|
+
// Other vertices unchanged
|
|
838
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
839
|
+
expect(poly.points[0].y).toBeCloseTo(0);
|
|
840
|
+
expect(poly.points[1].x).toBeCloseTo(10);
|
|
841
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test('Chamfer.action dist>0 trimMode=true closed Lwpolyline closing-wrap (seg4+seg1): NOT treated as open-ends', () => {
|
|
845
|
+
// Same square, closed. Corner at (0,0) shared by closing seg (idx=4) and seg 1.
|
|
846
|
+
// Bug: old code had no closed-poly guard, so closing-wrap was treated as open-ends.
|
|
847
|
+
// Fix: closed poly must NOT enter the open-ends path → corner-splice at index 0.
|
|
848
|
+
core.scene.clear();
|
|
849
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
850
|
+
const polyEntity = core.scene.entities.get(0);
|
|
851
|
+
polyEntity.flags.setFlagValue(1);
|
|
852
|
+
|
|
853
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
854
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
855
|
+
core.scene.headers.chamferMode = false;
|
|
856
|
+
core.scene.headers.trimMode = true;
|
|
857
|
+
|
|
858
|
+
const chamfer = new Chamfer();
|
|
859
|
+
chamfer.firstPick.entity = polyEntity;
|
|
860
|
+
chamfer.secondPick.entity = polyEntity;
|
|
861
|
+
// closing segment near (0,5), seg 1 near (5,0)
|
|
862
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
863
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
864
|
+
chamfer.firstPick.clickPoint = new Point(0, 5);
|
|
865
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
866
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
867
|
+
chamfer.secondPick.clickPoint = new Point(5, 0);
|
|
868
|
+
chamfer.action();
|
|
869
|
+
|
|
870
|
+
// Must take corner-splice path (not open-ends): no extra chamfer Line entity
|
|
871
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
872
|
+
const poly = core.scene.entities.get(0);
|
|
873
|
+
// Shared vertex (0,0) at index 0 replaced by two chamfer points → total 5 points
|
|
874
|
+
expect(poly.points.length).toBe(5);
|
|
875
|
+
// Endpoints must NOT have been moved to the intersection (open-ends symptom)
|
|
876
|
+
// The first two new points should be the chamfer points, not (0,0)/(0,0)
|
|
877
|
+
const allAtOrigin = poly.points[0].x === 0 && poly.points[0].y === 0 &&
|
|
878
|
+
poly.points[1].x === 0 && poly.points[1].y === 0;
|
|
879
|
+
expect(allAtOrigin).toBe(false);
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
test('Chamfer.action dist>0 trimMode=false closed Lwpolyline closing-wrap: standalone chamfer Line only', () => {
|
|
883
|
+
// Same square, closed, trimMode=false. No polyline mutation; only chamfer Line added.
|
|
884
|
+
core.scene.clear();
|
|
885
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
886
|
+
const polyEntity = core.scene.entities.get(0);
|
|
887
|
+
polyEntity.flags.setFlagValue(1);
|
|
888
|
+
|
|
889
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
890
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
891
|
+
core.scene.headers.chamferMode = false;
|
|
892
|
+
core.scene.headers.trimMode = false;
|
|
893
|
+
|
|
894
|
+
const chamfer = new Chamfer();
|
|
895
|
+
chamfer.firstPick.entity = polyEntity;
|
|
896
|
+
chamfer.secondPick.entity = polyEntity;
|
|
897
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
898
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
899
|
+
chamfer.firstPick.clickPoint = new Point(0, 5);
|
|
900
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
901
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
902
|
+
chamfer.secondPick.clickPoint = new Point(5, 0);
|
|
903
|
+
chamfer.action();
|
|
904
|
+
|
|
905
|
+
// trimMode=false: polyline unchanged, chamfer Line added
|
|
906
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
907
|
+
expect(core.scene.entities.get(0).points.length).toBe(4); // polyline unchanged
|
|
908
|
+
expect(core.scene.entities.get(1).type).toBe('Line');
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
test('Chamfer.action dist>0 trimMode=true open Lwpolyline seg1+lastIdx still takes open-ends path', () => {
|
|
912
|
+
// Open 4-point polyline (no flags). Seg 1 and seg 3 (lastIdx=3) → open-ends path still fires.
|
|
913
|
+
// Regression guard: the closed-poly fix must not break the open-poly open-ends case.
|
|
914
|
+
core.scene.clear();
|
|
915
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-5, 0), new Point(0, 0), new Point(1, 0), new Point(1, 5)] });
|
|
916
|
+
const polyEntity = core.scene.entities.get(0);
|
|
917
|
+
// No flags set — open polyline
|
|
918
|
+
|
|
919
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
920
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
921
|
+
core.scene.headers.chamferMode = false;
|
|
922
|
+
core.scene.headers.trimMode = true;
|
|
923
|
+
|
|
924
|
+
const chamfer = new Chamfer();
|
|
925
|
+
chamfer.firstPick.entity = polyEntity;
|
|
926
|
+
chamfer.secondPick.entity = polyEntity;
|
|
927
|
+
chamfer.firstPick.segment = polyEntity.getClosestSegment(new Point(-3, 0));
|
|
928
|
+
chamfer.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(-3, 0));
|
|
929
|
+
chamfer.firstPick.clickPoint = new Point(-3, 0);
|
|
930
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(new Point(1, 3));
|
|
931
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(1, 3));
|
|
932
|
+
chamfer.secondPick.clickPoint = new Point(1, 3);
|
|
933
|
+
chamfer.action();
|
|
934
|
+
|
|
935
|
+
// Open-ends path: separate chamfer Line entity added
|
|
936
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
937
|
+
expect(core.scene.entities.get(1).type).toBe('Line');
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
test('Chamfer.action dist>0 trimMode=false Line + Lwpolyline: standalone chamfer Line added, entities unchanged', () => {
|
|
941
|
+
core.scene.clear();
|
|
942
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
943
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
944
|
+
|
|
945
|
+
core.scene.headers.chamferDistanceA = 2;
|
|
946
|
+
core.scene.headers.chamferDistanceB = 2;
|
|
947
|
+
core.scene.headers.chamferMode = false;
|
|
948
|
+
core.scene.headers.trimMode = false;
|
|
949
|
+
|
|
950
|
+
const lineEntity = core.scene.entities.get(0);
|
|
951
|
+
const polyEntity = core.scene.entities.get(1);
|
|
952
|
+
const polyClickPoint = new Point(-15, 0);
|
|
953
|
+
|
|
954
|
+
const chamfer = new Chamfer();
|
|
955
|
+
chamfer.firstPick.entity = lineEntity;
|
|
956
|
+
chamfer.firstPick.segment = lineEntity;
|
|
957
|
+
chamfer.firstPick.segmentIndex = null;
|
|
958
|
+
chamfer.firstPick.clickPoint = new Point(0, 5);
|
|
959
|
+
chamfer.secondPick.entity = polyEntity;
|
|
960
|
+
chamfer.secondPick.segment = polyEntity.getClosestSegment(polyClickPoint);
|
|
961
|
+
chamfer.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(polyClickPoint);
|
|
962
|
+
chamfer.secondPick.clickPoint = polyClickPoint;
|
|
963
|
+
chamfer.action();
|
|
964
|
+
|
|
965
|
+
// Chamfer Line added as standalone entity; original line and polyline untouched
|
|
966
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
967
|
+
expect(core.scene.entities.get(2).type).toBe('Line');
|
|
968
|
+
expect(core.scene.entities.get(0).points.length).toBe(2);
|
|
969
|
+
expect(core.scene.entities.get(1).points.length).toBe(4);
|
|
970
|
+
});
|