@design-core/core 0.0.6 → 50.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +4 -0
- package/.github/workflows/DesignCoreQA.yml +3 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +12 -11
- package/core/dimensions/angularDimension.js +38 -37
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
- package/core/dimensions/diametricDimension.js +14 -13
- package/core/dimensions/dimension.js +62 -55
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
- package/core/dimensions/radialDimension.js +12 -11
- package/core/dimensions/rotatedDimension.js +20 -19
- package/core/entities/arc.js +123 -156
- package/core/entities/arctext.js +222 -184
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +346 -273
- package/core/entities/insert.js +68 -83
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +7 -7
- package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +190 -201
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/boundingBox.js +11 -11
- package/core/lib/canvas.js +232 -168
- package/core/lib/clipboard.js +9 -3
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +15 -14
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +33 -26
- package/core/lib/inputManager.js +36 -12
- package/core/lib/intersect.js +25 -0
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +9 -2
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +28 -6
- package/core/lib/selectionManager.js +20 -20
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +26 -7
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +60 -44
- package/core/tables/block.js +50 -81
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +117 -143
- package/core/tools/chamferFilletBase.js +138 -37
- package/core/tools/copy.js +11 -11
- package/core/tools/copybase.js +3 -3
- package/core/tools/copyclip.js +3 -3
- package/core/tools/cornerEntity.js +2 -2
- package/core/tools/cutclip.js +3 -3
- package/core/tools/explode.js +7 -7
- package/core/tools/extend.js +138 -25
- package/core/tools/fillet.js +92 -189
- package/core/tools/matchProp.js +3 -12
- package/core/tools/mirror.js +15 -14
- package/core/tools/move.js +8 -8
- package/core/tools/offset.js +31 -33
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +19 -16
- package/core/tools/scale.js +7 -7
- package/core/tools/trim.js +271 -29
- package/core/tools/zoom.js +1 -1
- package/cspell.json +321 -0
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +17 -10
- package/test/dimensions/angularDimension.test.js +13 -13
- package/test/dimensions/diametricDimension.test.js +15 -15
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
- package/test/dimensions/linearDimensionBase.test.js +62 -0
- package/test/dimensions/radialDimension.test.js +15 -15
- package/test/dimensions/rotatedDimension.test.js +18 -11
- package/test/entities/arc.test.js +83 -86
- package/test/entities/arctext.test.js +184 -57
- package/test/entities/circle.test.js +68 -96
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +569 -96
- package/test/entities/insert.test.js +99 -4
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/point.test.js +3 -3
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +302 -77
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +5 -5
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +744 -125
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +81 -20
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +72 -130
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +25 -25
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +92 -13
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +158 -6
- package/test/tools/chamfer.test.js +273 -51
- package/test/tools/chamferFilletBase.test.js +125 -36
- package/test/tools/copy.test.js +8 -8
- package/test/tools/copybase.test.js +2 -2
- package/test/tools/copyclip.test.js +2 -2
- package/test/tools/cornerEntity.test.js +8 -8
- package/test/tools/cutclip.test.js +2 -2
- package/test/tools/erase.test.js +6 -6
- package/test/tools/explode.test.js +5 -5
- package/test/tools/extend.test.js +373 -42
- package/test/tools/fillet.test.js +230 -49
- package/test/tools/matchProp.test.js +11 -11
- package/test/tools/mirror.test.js +39 -39
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +222 -55
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +39 -9
- package/test/tools/scale.test.js +33 -33
- package/test/tools/trim.test.js +579 -53
- package/test/tools/zoom.test.js +12 -12
- package/core/lib/selectionWindow.js +0 -83
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Core } from '../../core/core/core.js';
|
|
2
2
|
import { Point } from '../../core/entities/point.js';
|
|
3
3
|
import { Fillet } from '../../core/tools/fillet.js';
|
|
4
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
4
5
|
import { Strings } from '../../core/lib/strings.js';
|
|
5
6
|
import { SingleSelection } from '../../core/lib/selectionManager.js';
|
|
6
7
|
import { expect, jest } from '@jest/globals';
|
|
@@ -16,8 +17,8 @@ test('Fillet.register returns correct command object', () => {
|
|
|
16
17
|
|
|
17
18
|
test('Fillet.execute selects two entities and calls executeCommand', async () => {
|
|
18
19
|
core.scene.clear();
|
|
19
|
-
core.scene.
|
|
20
|
-
core.scene.
|
|
20
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
21
|
+
core.scene.addEntity('Line', { points: [new Point(5, -5), new Point(5, 5)] });
|
|
21
22
|
|
|
22
23
|
const executeCommandSpy = jest.fn();
|
|
23
24
|
|
|
@@ -44,7 +45,7 @@ test('Fillet.execute returns early when first input is undefined', async () => {
|
|
|
44
45
|
|
|
45
46
|
test('Fillet.action does nothing when entities are not set', () => {
|
|
46
47
|
core.scene.clear();
|
|
47
|
-
core.scene.
|
|
48
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
48
49
|
const entityCountBefore = core.scene.entities.count();
|
|
49
50
|
|
|
50
51
|
const fillet = new Fillet();
|
|
@@ -55,8 +56,8 @@ test('Fillet.action does nothing when entities are not set', () => {
|
|
|
55
56
|
|
|
56
57
|
test('Fillet.action notifies when entity type is not supported', () => {
|
|
57
58
|
core.scene.clear();
|
|
58
|
-
core.scene.
|
|
59
|
-
core.scene.
|
|
59
|
+
core.scene.addEntity('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
60
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
60
61
|
|
|
61
62
|
const notifySpy = jest.spyOn(core, 'notify');
|
|
62
63
|
|
|
@@ -74,8 +75,8 @@ test('Fillet.action notifies when entity type is not supported', () => {
|
|
|
74
75
|
test('Fillet.action notifies for parallel lines', () => {
|
|
75
76
|
core.scene.clear();
|
|
76
77
|
// Two horizontal parallel lines
|
|
77
|
-
core.scene.
|
|
78
|
-
core.scene.
|
|
78
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
79
|
+
core.scene.addEntity('Line', { points: [new Point(0, 5), new Point(10, 5)] });
|
|
79
80
|
|
|
80
81
|
const notifySpy = jest.spyOn(core, 'notify');
|
|
81
82
|
|
|
@@ -97,8 +98,8 @@ test('Fillet.action radius=0 trimMode=true trims both lines to intersection', ()
|
|
|
97
98
|
// Horizontal: (0,0)→(8,0), Vertical: (10,-10)→(10,-2)
|
|
98
99
|
// Their infinite extensions meet at (10, 0).
|
|
99
100
|
core.scene.clear();
|
|
100
|
-
core.scene.
|
|
101
|
-
core.scene.
|
|
101
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(8, 0)] });
|
|
102
|
+
core.scene.addEntity('Line', { points: [new Point(10, -10), new Point(10, -2)] });
|
|
102
103
|
|
|
103
104
|
core.scene.headers.filletRadius = 0;
|
|
104
105
|
core.scene.headers.trimMode = true;
|
|
@@ -128,8 +129,8 @@ test('Fillet.action radius=0 trimMode=true trims both lines to intersection', ()
|
|
|
128
129
|
|
|
129
130
|
test('Fillet.action radius=0 trimMode=false makes no changes', () => {
|
|
130
131
|
core.scene.clear();
|
|
131
|
-
core.scene.
|
|
132
|
-
core.scene.
|
|
132
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(8, 0)] });
|
|
133
|
+
core.scene.addEntity('Line', { points: [new Point(10, -10), new Point(10, -2)] });
|
|
133
134
|
|
|
134
135
|
core.scene.headers.filletRadius = 0;
|
|
135
136
|
core.scene.headers.trimMode = false;
|
|
@@ -152,8 +153,8 @@ test('Fillet.action radius>0 trimMode=true adds arc and trims lines', () => {
|
|
|
152
153
|
// Horizontal: (-10,0)→(0,0) Vertical: (0,0)→(0,10)
|
|
153
154
|
// With radius 2, tangent points are at (-2,0) and (0,2)
|
|
154
155
|
core.scene.clear();
|
|
155
|
-
core.scene.
|
|
156
|
-
core.scene.
|
|
156
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
157
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
157
158
|
|
|
158
159
|
core.scene.headers.filletRadius = 2;
|
|
159
160
|
core.scene.headers.trimMode = true;
|
|
@@ -189,8 +190,8 @@ test('Fillet.action radius>0 trimMode=true adds arc and trims lines', () => {
|
|
|
189
190
|
|
|
190
191
|
test('Fillet.action radius>0 trimMode=false adds arc only, lines unchanged', () => {
|
|
191
192
|
core.scene.clear();
|
|
192
|
-
core.scene.
|
|
193
|
-
core.scene.
|
|
193
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
194
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
194
195
|
|
|
195
196
|
core.scene.headers.filletRadius = 2;
|
|
196
197
|
core.scene.headers.trimMode = false;
|
|
@@ -216,8 +217,8 @@ test('Fillet.action radius>0 trimMode=false adds arc only, lines unchanged', ()
|
|
|
216
217
|
test('Fillet.action notifies when radius is too large for the segments', () => {
|
|
217
218
|
// Lines only 5 units long from intersection — radius 10 won't fit
|
|
218
219
|
core.scene.clear();
|
|
219
|
-
core.scene.
|
|
220
|
-
core.scene.
|
|
220
|
+
core.scene.addEntity('Line', { points: [new Point(-5, 0), new Point(0, 0)] });
|
|
221
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 5)] });
|
|
221
222
|
|
|
222
223
|
core.scene.headers.filletRadius = 10;
|
|
223
224
|
core.scene.headers.trimMode = true;
|
|
@@ -240,8 +241,8 @@ test('Fillet.action notifies when radius is too large for the segments', () => {
|
|
|
240
241
|
test('Fillet.action arc centre is at correct position for 90° corner', () => {
|
|
241
242
|
// 90° corner at origin, radius 3 → arc centre at (-3, 3)
|
|
242
243
|
core.scene.clear();
|
|
243
|
-
core.scene.
|
|
244
|
-
core.scene.
|
|
244
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
245
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
245
246
|
|
|
246
247
|
core.scene.headers.filletRadius = 3;
|
|
247
248
|
core.scene.headers.trimMode = false;
|
|
@@ -271,8 +272,8 @@ test('Fillet.action selects correct corner when lines form a cross', () => {
|
|
|
271
272
|
// Full cross: horizontal (-10,0)→(10,0), vertical (0,-10)→(0,10)
|
|
272
273
|
// Click on the top-right quadrant → fillet should be in that quadrant
|
|
273
274
|
core.scene.clear();
|
|
274
|
-
core.scene.
|
|
275
|
-
core.scene.
|
|
275
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
276
|
+
core.scene.addEntity('Line', { points: [new Point(0, -10), new Point(0, 10)] });
|
|
276
277
|
|
|
277
278
|
core.scene.headers.filletRadius = 2;
|
|
278
279
|
core.scene.headers.trimMode = false;
|
|
@@ -296,8 +297,8 @@ test('Fillet.action selects correct corner when lines form a cross', () => {
|
|
|
296
297
|
|
|
297
298
|
test('Fillet.action notifies when second entity type is not supported', () => {
|
|
298
299
|
core.scene.clear();
|
|
299
|
-
core.scene.
|
|
300
|
-
core.scene.
|
|
300
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
301
|
+
core.scene.addEntity('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
301
302
|
|
|
302
303
|
const notifySpy = jest.spyOn(core, 'notify');
|
|
303
304
|
|
|
@@ -318,8 +319,8 @@ test('Fillet.action notifies when click coincides with intersection', () => {
|
|
|
318
319
|
// L-corner at origin. Clicking firstClickPoint exactly at (0,0) makes
|
|
319
320
|
// firstClickDistance = 0, triggering the invalid-selection guard.
|
|
320
321
|
core.scene.clear();
|
|
321
|
-
core.scene.
|
|
322
|
-
core.scene.
|
|
322
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
323
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
323
324
|
|
|
324
325
|
core.scene.headers.filletRadius = 2;
|
|
325
326
|
core.scene.headers.trimMode = true;
|
|
@@ -342,8 +343,8 @@ test('Fillet.action radius=0 notifies and makes no changes when click coincides
|
|
|
342
343
|
// Click at exactly the intersection point — clickDistance = 0.
|
|
343
344
|
// The guard must fire before applySharpTrim() runs.
|
|
344
345
|
core.scene.clear();
|
|
345
|
-
core.scene.
|
|
346
|
-
core.scene.
|
|
346
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
347
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
347
348
|
|
|
348
349
|
core.scene.headers.filletRadius = 0;
|
|
349
350
|
core.scene.headers.trimMode = true;
|
|
@@ -414,7 +415,7 @@ test('Fillet.execute sets trimMode to false when No trim option is selected', as
|
|
|
414
415
|
|
|
415
416
|
test('Fillet.execute re-prompts and notifies when first selected entity is not a Line', async () => {
|
|
416
417
|
core.scene.clear();
|
|
417
|
-
core.scene.
|
|
418
|
+
core.scene.addEntity('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
418
419
|
|
|
419
420
|
const notifySpy = jest.spyOn(core, 'notify');
|
|
420
421
|
|
|
@@ -433,8 +434,8 @@ test('Fillet.execute re-prompts and notifies when first selected entity is not a
|
|
|
433
434
|
|
|
434
435
|
test('Fillet.execute re-prompts and notifies when second selected entity is not a Line', async () => {
|
|
435
436
|
core.scene.clear();
|
|
436
|
-
core.scene.
|
|
437
|
-
core.scene.
|
|
437
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
438
|
+
core.scene.addEntity('Circle', { points: [new Point(0, 0), new Point(5, 0)] });
|
|
438
439
|
|
|
439
440
|
const notifySpy = jest.spyOn(core, 'notify');
|
|
440
441
|
|
|
@@ -465,8 +466,8 @@ test('Fillet.execute re-prompts and notifies when second selected entity is not
|
|
|
465
466
|
test('Fillet.action radius>0 trimMode=true Line first + Lwpolyline second keepStart: arc embedded as bulge', () => {
|
|
466
467
|
// Click polyline near (-15,0) → segment 1. keepStart keeps the start portion.
|
|
467
468
|
core.scene.clear();
|
|
468
|
-
core.scene.
|
|
469
|
-
core.scene.
|
|
469
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
470
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
470
471
|
|
|
471
472
|
core.scene.headers.filletRadius = 2;
|
|
472
473
|
core.scene.headers.trimMode = true;
|
|
@@ -506,8 +507,8 @@ test('Fillet.action radius>0 trimMode=true Line first + Lwpolyline second keepSt
|
|
|
506
507
|
test('Fillet.action radius>0 trimMode=true Lwpolyline first + Line second keepStart: arc direction correct', () => {
|
|
507
508
|
// Same geometry but entity order reversed — tests the polyToLineDir arc direction fix.
|
|
508
509
|
core.scene.clear();
|
|
509
|
-
core.scene.
|
|
510
|
-
core.scene.
|
|
510
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
511
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
511
512
|
|
|
512
513
|
core.scene.headers.filletRadius = 2;
|
|
513
514
|
core.scene.headers.trimMode = true;
|
|
@@ -547,8 +548,8 @@ test('Fillet.action radius>0 trimMode=true Line + Lwpolyline keepEnd: keeps end
|
|
|
547
548
|
// Click polyline near (8,0) → segment 3 (right of intersection). keepEnd.
|
|
548
549
|
// arc centre = (2,2), polyTangent = (2,0), lineTangent = (0,2).
|
|
549
550
|
core.scene.clear();
|
|
550
|
-
core.scene.
|
|
551
|
-
core.scene.
|
|
551
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
552
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
552
553
|
|
|
553
554
|
core.scene.headers.filletRadius = 2;
|
|
554
555
|
core.scene.headers.trimMode = true;
|
|
@@ -588,8 +589,8 @@ test('Fillet.action radius>0 trimMode=true Line + Lwpolyline keepEnd: keeps end
|
|
|
588
589
|
test('Fillet.action radius=0 trimMode=true Line + Lwpolyline keepStart: line consumed to sharp corner', () => {
|
|
589
590
|
// radius=0 → sharp intersection at (0,0), no arc.
|
|
590
591
|
core.scene.clear();
|
|
591
|
-
core.scene.
|
|
592
|
-
core.scene.
|
|
592
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
593
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
593
594
|
|
|
594
595
|
core.scene.headers.filletRadius = 0;
|
|
595
596
|
core.scene.headers.trimMode = true;
|
|
@@ -628,7 +629,7 @@ test('Fillet.action radius>0 trimMode=true same Lwpolyline consecutive segments:
|
|
|
628
629
|
// Segment 1: (-10,0)→(0,0), Segment 2: (0,0)→(0,10). Corner at (0,0).
|
|
629
630
|
// radius=2 → arc centre (-2,2), tangents at (-2,0) and (0,2).
|
|
630
631
|
core.scene.clear();
|
|
631
|
-
core.scene.
|
|
632
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-10, 0), new Point(0, 0), new Point(0, 10)] });
|
|
632
633
|
|
|
633
634
|
core.scene.headers.filletRadius = 2;
|
|
634
635
|
core.scene.headers.trimMode = true;
|
|
@@ -670,7 +671,7 @@ test('Fillet.action radius>0 trimMode=true same Lwpolyline open ends: separate A
|
|
|
670
671
|
// radius=2, clicks at (-3,0) and (1,3):
|
|
671
672
|
// arc centre = (-1,2), firstTangent = (-1,0), secondTangent = (1,2).
|
|
672
673
|
core.scene.clear();
|
|
673
|
-
core.scene.
|
|
674
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-5, 0), new Point(0, 0), new Point(1, 0), new Point(1, 5)] });
|
|
674
675
|
|
|
675
676
|
core.scene.headers.filletRadius = 2;
|
|
676
677
|
core.scene.headers.trimMode = true;
|
|
@@ -712,7 +713,7 @@ test('Fillet.action radius>0 trimMode=true closed Lwpolyline seg3+closing-seg: c
|
|
|
712
713
|
// Closing segment (idx=4) goes (0,10)→(0,0); seg 3 goes (10,10)→(0,10).
|
|
713
714
|
// Corner at (0,10) — radius=2 replaces the shared vertex with arc bulge points.
|
|
714
715
|
core.scene.clear();
|
|
715
|
-
core.scene.
|
|
716
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
716
717
|
const polyEntity = core.scene.entities.get(0);
|
|
717
718
|
polyEntity.flags.setFlagValue(1);
|
|
718
719
|
|
|
@@ -744,7 +745,7 @@ test('Fillet.action radius>0 trimMode=true closed Lwpolyline closing-wrap (seg4+
|
|
|
744
745
|
// Corner at (0,0) shared by closing seg (idx=4) and seg 1.
|
|
745
746
|
// Bug: without the closed-poly guard, this was treated as open-ends → wrong path.
|
|
746
747
|
core.scene.clear();
|
|
747
|
-
core.scene.
|
|
748
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
748
749
|
const polyEntity = core.scene.entities.get(0);
|
|
749
750
|
polyEntity.flags.setFlagValue(1);
|
|
750
751
|
|
|
@@ -776,7 +777,7 @@ test('Fillet.action radius>0 trimMode=true closed Lwpolyline closing-wrap (seg4+
|
|
|
776
777
|
test('Fillet.action radius>0 trimMode=false closed Lwpolyline closing-wrap: standalone Arc only', () => {
|
|
777
778
|
// trimMode=false: no polyline mutation; only Arc entity added.
|
|
778
779
|
core.scene.clear();
|
|
779
|
-
core.scene.
|
|
780
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
780
781
|
const polyEntity = core.scene.entities.get(0);
|
|
781
782
|
polyEntity.flags.setFlagValue(1);
|
|
782
783
|
|
|
@@ -802,8 +803,8 @@ test('Fillet.action radius>0 trimMode=false closed Lwpolyline closing-wrap: stan
|
|
|
802
803
|
|
|
803
804
|
test('Fillet.action radius>0 trimMode=false Line + Lwpolyline: standalone arc added, entities unchanged', () => {
|
|
804
805
|
core.scene.clear();
|
|
805
|
-
core.scene.
|
|
806
|
-
core.scene.
|
|
806
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
807
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0)] });
|
|
807
808
|
|
|
808
809
|
core.scene.headers.filletRadius = 2;
|
|
809
810
|
core.scene.headers.trimMode = false;
|
|
@@ -842,14 +843,14 @@ test('Fillet.action radius>0 trimMode=false Line + Lwpolyline: standalone arc ad
|
|
|
842
843
|
// – lowerTangent = (0,2) on the closing segment (left edge, y=2)
|
|
843
844
|
// – upperTangent = (2,0) on segment 1 (bottom edge, x=2)
|
|
844
845
|
// – bulge on lowerTangent must be POSITIVE (CCW arc from (0,2) to (2,0) through ≈(0.59,0.59))
|
|
845
|
-
// (
|
|
846
|
+
// (PolylineBase convention: –ve bulge = CW, +ve bulge = CCW)
|
|
846
847
|
//
|
|
847
848
|
// Before the fix the lowerTangent/upperTangent assignment was inverted, producing
|
|
848
849
|
// an arc that traversed the exterior of the square.
|
|
849
850
|
|
|
850
851
|
test('Fillet.action closing-wrap: lowerTangent is on closing segment (left edge)', () => {
|
|
851
852
|
core.scene.clear();
|
|
852
|
-
core.scene.
|
|
853
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
853
854
|
const polyEntity = core.scene.entities.get(0);
|
|
854
855
|
polyEntity.flags.setFlagValue(1);
|
|
855
856
|
|
|
@@ -888,7 +889,7 @@ test('Fillet.action closing-wrap: arc bulge sign is positive (CCW, fillet inside
|
|
|
888
889
|
// Before the fix the lowerTangent/upperTangent were swapped, producing an arc
|
|
889
890
|
// that traversed the exterior of the square.
|
|
890
891
|
core.scene.clear();
|
|
891
|
-
core.scene.
|
|
892
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
892
893
|
const polyEntity = core.scene.entities.get(0);
|
|
893
894
|
polyEntity.flags.setFlagValue(1);
|
|
894
895
|
|
|
@@ -914,7 +915,7 @@ test('Fillet.action closing-wrap: arc bulge sign is positive (CCW, fillet inside
|
|
|
914
915
|
test('Fillet.action closing-wrap reversed pick order: arc bulge sign and tangent positions are correct', () => {
|
|
915
916
|
// Swap firstPick/secondPick — should produce the same geometry.
|
|
916
917
|
core.scene.clear();
|
|
917
|
-
core.scene.
|
|
918
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
918
919
|
const polyEntity = core.scene.entities.get(0);
|
|
919
920
|
polyEntity.flags.setFlagValue(1);
|
|
920
921
|
|
|
@@ -944,3 +945,183 @@ test('Fillet.action closing-wrap reversed pick order: arc bulge sign and tangent
|
|
|
944
945
|
expect(poly.points[1].y).toBeCloseTo(0);
|
|
945
946
|
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
946
947
|
});
|
|
948
|
+
|
|
949
|
+
// ─── action: entity property inheritance ─────────────────────────────────────
|
|
950
|
+
|
|
951
|
+
test('Fillet.action arc inherits layer, lineWidth, lineType from first entity', () => {
|
|
952
|
+
// Perpendicular L-corner at origin; first entity on a non-default layer.
|
|
953
|
+
core.scene.clear();
|
|
954
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)], layer: 'walls', lineWidth: 5, lineType: 'DASHED' });
|
|
955
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
956
|
+
|
|
957
|
+
core.scene.headers.filletRadius = 2;
|
|
958
|
+
core.scene.headers.trimMode = false;
|
|
959
|
+
|
|
960
|
+
const fillet = new Fillet();
|
|
961
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
962
|
+
fillet.secondPick.entity = core.scene.entities.get(1);
|
|
963
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
964
|
+
fillet.secondPick.clickPoint = new Point(0, 5);
|
|
965
|
+
fillet.action();
|
|
966
|
+
|
|
967
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
968
|
+
const arc = core.scene.entities.get(2);
|
|
969
|
+
expect(arc.type).toBe('Arc');
|
|
970
|
+
expect(arc.getProperty('layer')).toBe('walls');
|
|
971
|
+
expect(arc.getProperty('lineWidth')).toBe(5);
|
|
972
|
+
expect(arc.getProperty('lineType')).toBe('DASHED');
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test('Fillet.preview does not throw', () => {
|
|
976
|
+
const fillet = new Fillet();
|
|
977
|
+
expect(() => fillet.preview()).not.toThrow();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test('Fillet.preview returns early when findClosestItem returns undefined', () => {
|
|
981
|
+
core.scene.clear();
|
|
982
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
983
|
+
DesignCore.Scene.previewEntities.clear();
|
|
984
|
+
|
|
985
|
+
const fillet = new Fillet();
|
|
986
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
987
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
988
|
+
|
|
989
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
990
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
991
|
+
fillet.preview();
|
|
992
|
+
findSpy.mockRestore();
|
|
993
|
+
|
|
994
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test('Fillet.preview adds fillet arc to previewEntities in no-trim mode', () => {
|
|
998
|
+
core.scene.clear();
|
|
999
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1000
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1001
|
+
DesignCore.Scene.previewEntities.clear();
|
|
1002
|
+
|
|
1003
|
+
core.scene.headers.filletRadius = 2;
|
|
1004
|
+
core.scene.headers.trimMode = false;
|
|
1005
|
+
|
|
1006
|
+
const fillet = new Fillet();
|
|
1007
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
1008
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
1009
|
+
|
|
1010
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
1011
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
1012
|
+
fillet.preview();
|
|
1013
|
+
findSpy.mockRestore();
|
|
1014
|
+
|
|
1015
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThanOrEqual(1);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test('Fillet.preview adds dulled segments and arc to previewEntities in trim mode', () => {
|
|
1019
|
+
core.scene.clear();
|
|
1020
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1021
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1022
|
+
DesignCore.Scene.previewEntities.clear();
|
|
1023
|
+
|
|
1024
|
+
core.scene.headers.filletRadius = 2;
|
|
1025
|
+
core.scene.headers.trimMode = true;
|
|
1026
|
+
|
|
1027
|
+
const fillet = new Fillet();
|
|
1028
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
1029
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
1030
|
+
|
|
1031
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
1032
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
1033
|
+
fillet.preview();
|
|
1034
|
+
findSpy.mockRestore();
|
|
1035
|
+
|
|
1036
|
+
// dulled firstSeg + dulled secondSeg + firstTrimLine + secondTrimLine + arc = 5
|
|
1037
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThanOrEqual(5);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test('Fillet.execute returns early when second-entity input is undefined (inner loop)', async () => {
|
|
1041
|
+
core.scene.clear();
|
|
1042
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1043
|
+
core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1044
|
+
|
|
1045
|
+
const executeCommandSpy = jest.fn();
|
|
1046
|
+
// Only one input: the first selection. Second requestInput returns undefined → inner loop returns.
|
|
1047
|
+
await withMockInput(
|
|
1048
|
+
core.scene,
|
|
1049
|
+
[new SingleSelection(0, new Point(-5, 0))],
|
|
1050
|
+
async () => {
|
|
1051
|
+
const fillet = new Fillet();
|
|
1052
|
+
await fillet.execute();
|
|
1053
|
+
expect(executeCommandSpy).not.toHaveBeenCalled();
|
|
1054
|
+
},
|
|
1055
|
+
{ extraMethods: { executeCommand: executeCommandSpy } },
|
|
1056
|
+
);
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test('Fillet.execute notifies NONCONSECUTIVESEGMENTS for non-adjacent polyline segments', async () => {
|
|
1060
|
+
// 5-point polyline → 4 segments. Pick segment 1 (near -15,0) then segment 3 (near 5,0).
|
|
1061
|
+
// Neither consecutive nor open-ends → NONCONSECUTIVESEGMENTS, then undefined to exit.
|
|
1062
|
+
core.scene.clear();
|
|
1063
|
+
core.scene.addEntity('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0), new Point(20, 0)] });
|
|
1064
|
+
|
|
1065
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
1066
|
+
await withMockInput(
|
|
1067
|
+
core.scene,
|
|
1068
|
+
[new SingleSelection(0, new Point(-15, 0)), new SingleSelection(0, new Point(5, 0)), undefined],
|
|
1069
|
+
async () => {
|
|
1070
|
+
const fillet = new Fillet();
|
|
1071
|
+
await fillet.execute();
|
|
1072
|
+
},
|
|
1073
|
+
);
|
|
1074
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NONCONSECUTIVESEGMENTS));
|
|
1075
|
+
notifySpy.mockRestore();
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test('Fillet.execute Trim→Trim keeps trimMode=true', async () => {
|
|
1079
|
+
core.scene.headers.trimMode = true;
|
|
1080
|
+
|
|
1081
|
+
await withMockInput(
|
|
1082
|
+
core.scene,
|
|
1083
|
+
['Trim', 'Trim', undefined],
|
|
1084
|
+
async () => {
|
|
1085
|
+
const fillet = new Fillet();
|
|
1086
|
+
await fillet.execute();
|
|
1087
|
+
},
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
expect(core.scene.headers.trimMode).toBe(true);
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// ─── regression: bulge must not leak onto Line endpoints ─────────────────────
|
|
1094
|
+
|
|
1095
|
+
test('Fillet.action two fillets on the same line: trimmed endpoints have bulge = 0', () => {
|
|
1096
|
+
// Line A: (-10, 0) → (10, 0) horizontal
|
|
1097
|
+
// Line B: (-10, 2) → (-10, -2) vertical at left end
|
|
1098
|
+
// Line C: ( 10, 2) → ( 10, -2) vertical at right end
|
|
1099
|
+
// Fillet A–B at the left corner, then A–C at the right corner.
|
|
1100
|
+
// arc.toPolylinePoints() sets bulge on the start point;
|
|
1101
|
+
// that value must not bleed onto the Line endpoints.
|
|
1102
|
+
core.scene.clear();
|
|
1103
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
1104
|
+
core.scene.addEntity('Line', { points: [new Point(-10, 2), new Point(-10, -2)] });
|
|
1105
|
+
core.scene.addEntity('Line', { points: [new Point(10, 2), new Point(10, -2)] });
|
|
1106
|
+
|
|
1107
|
+
core.scene.headers.filletRadius = 1;
|
|
1108
|
+
core.scene.headers.trimMode = true;
|
|
1109
|
+
|
|
1110
|
+
const fillet1 = new Fillet();
|
|
1111
|
+
fillet1.firstPick.entity = core.scene.entities.get(0);
|
|
1112
|
+
fillet1.secondPick.entity = core.scene.entities.get(1);
|
|
1113
|
+
fillet1.firstPick.clickPoint = new Point(-5, 0);
|
|
1114
|
+
fillet1.secondPick.clickPoint = new Point(-10, 1);
|
|
1115
|
+
fillet1.action();
|
|
1116
|
+
|
|
1117
|
+
const fillet2 = new Fillet();
|
|
1118
|
+
fillet2.firstPick.entity = core.scene.entities.get(0);
|
|
1119
|
+
fillet2.secondPick.entity = core.scene.entities.get(2);
|
|
1120
|
+
fillet2.firstPick.clickPoint = new Point(5, 0);
|
|
1121
|
+
fillet2.secondPick.clickPoint = new Point(10, 1);
|
|
1122
|
+
fillet2.action();
|
|
1123
|
+
|
|
1124
|
+
const line = core.scene.entities.get(0);
|
|
1125
|
+
expect(line.points[0].bulge).toBe(0);
|
|
1126
|
+
expect(line.points[1].bulge).toBe(0);
|
|
1127
|
+
});
|
|
@@ -8,12 +8,12 @@ import { jest } from '@jest/globals';
|
|
|
8
8
|
const core = new Core();
|
|
9
9
|
|
|
10
10
|
// Add items to scene
|
|
11
|
-
DesignCore.Scene.
|
|
12
|
-
DesignCore.Scene.
|
|
13
|
-
DesignCore.Scene.
|
|
14
|
-
DesignCore.Scene.
|
|
15
|
-
DesignCore.Scene.
|
|
16
|
-
DesignCore.Scene.
|
|
11
|
+
DesignCore.Scene.addEntity('Line', { points: [new Point(10, 0), new Point(20, 0)], layer: 'test' });
|
|
12
|
+
DesignCore.Scene.addEntity('Circle', { points: [new Point(10, 0), new Point(20, 0)] });
|
|
13
|
+
DesignCore.Scene.addEntity('Polyline', { points: [new Point(10, 0), new Point(20, 0)] });
|
|
14
|
+
DesignCore.Scene.addEntity('Arc', { points: [new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
15
|
+
DesignCore.Scene.addEntity('Rectangle', { points: [new Point(10, 0), new Point(0, 10)] });
|
|
16
|
+
DesignCore.Scene.addEntity('Text', { points: [new Point(10, 0), new Point(0, 10)], height: 10, rotation: 0, string: 'text test' });
|
|
17
17
|
|
|
18
18
|
test('Test MatchProp.execute requests source and targets and completes', async () => {
|
|
19
19
|
const match = new MatchProp();
|
|
@@ -69,16 +69,16 @@ test('Test MatchProp.action matches properties from source to targets', () => {
|
|
|
69
69
|
// Validate that layer property was matched to targets
|
|
70
70
|
for (let i = 0; i < match.destinationSetIndices.length; i++) {
|
|
71
71
|
const tgt = core.scene.entities.get(match.destinationSetIndices[i]);
|
|
72
|
-
expect(tgt.layer).toBe('test');
|
|
72
|
+
expect(tgt.getProperty('layer')).toBe('test');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const circle = core.scene.entities.get(1);
|
|
76
|
-
expect(circle.radius).toBe(10); // original radius
|
|
76
|
+
expect(circle.getProperty('radius')).toBe(10); // original radius
|
|
77
77
|
|
|
78
78
|
const arc = core.scene.entities.get(3);
|
|
79
|
-
expect(arc.radius).toBe(10); // original radius
|
|
79
|
+
expect(arc.getProperty('radius')).toBe(10); // original radius
|
|
80
80
|
|
|
81
81
|
const text = core.scene.entities.get(5);
|
|
82
|
-
expect(text.height).toBe(10); // original height
|
|
83
|
-
expect(text.string).toBe('text test'); // original string
|
|
82
|
+
expect(text.getProperty('height')).toBe(10); // original height
|
|
83
|
+
expect(text.getProperty('string')).toBe('text test'); // original string
|
|
84
84
|
});
|