@design-core/core 0.0.4 → 0.0.6
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/core/entities/arc.js +39 -22
- package/core/entities/arctext.js +6 -4
- package/core/entities/basePolyline.js +29 -20
- package/core/entities/circle.js +33 -15
- package/core/entities/entity.js +1 -1
- package/core/entities/hatch.js +2 -0
- package/core/entities/insert.js +9 -3
- package/core/entities/line.js +16 -11
- package/core/entities/lwpolyline.js +2 -0
- package/core/entities/point.js +3 -1
- package/core/entities/polyline.js +2 -0
- package/core/entities/rectangle.js +2 -0
- package/core/entities/solid.js +2 -0
- package/core/entities/text.js +5 -8
- package/core/lib/input.js +8 -4
- package/core/lib/inputManager.js +3 -5
- package/core/lib/selectionManager.js +4 -0
- package/core/lib/settings.js +9 -5
- package/core/lib/snapping.js +112 -17
- package/core/tables/block.js +2 -0
- package/core/tools/chamfer.js +2 -0
- package/core/tools/chamferFilletBase.js +2 -0
- package/core/tools/copy.js +2 -0
- package/core/tools/copybase.js +2 -0
- package/core/tools/copyclip.js +2 -0
- package/core/tools/cutclip.js +2 -0
- package/core/tools/distance.js +2 -0
- package/core/tools/erase.js +2 -0
- package/core/tools/explode.js +2 -0
- package/core/tools/extend.js +2 -0
- package/core/tools/fillet.js +5 -3
- package/core/tools/identify.js +2 -0
- package/core/tools/matchProp.js +2 -0
- package/core/tools/mirror.js +2 -0
- package/core/tools/move.js +2 -0
- package/core/tools/offset.js +2 -0
- package/core/tools/pan.js +2 -0
- package/core/tools/pasteclip.js +2 -0
- package/core/tools/purge.js +2 -0
- package/core/tools/rotate.js +2 -0
- package/core/tools/scale.js +2 -0
- package/core/tools/tool.js +1 -1
- package/core/tools/trim.js +2 -0
- package/core/tools/zoom.js +2 -0
- package/package.json +1 -1
- package/test/entities/arc.test.js +60 -11
- package/test/entities/arctext.test.js +18 -2
- package/test/entities/basePolyline.test.js +61 -0
- package/test/entities/circle.test.js +48 -22
- package/test/entities/insert.test.js +14 -4
- package/test/entities/line.test.js +53 -0
- package/test/entities/text.test.js +10 -1
- package/test/lib/input.test.js +117 -0
- package/test/lib/settings.test.js +5 -7
- package/test/lib/snapping.test.js +218 -12
- package/test/tools/fillet.test.js +115 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import { Core } from '../../core/core/core.js';
|
|
3
|
-
import { Snapping } from '../../core/lib/snapping.js';
|
|
3
|
+
import { Snapping, SnapPoint } from '../../core/lib/snapping.js';
|
|
4
4
|
import { Point } from '../../core/entities/point.js';
|
|
5
5
|
import { DesignCore } from '../../core/designCore.js';
|
|
6
6
|
|
|
@@ -24,16 +24,16 @@ test('Test Snapping.getSnapPoint', () => {
|
|
|
24
24
|
core.mouse.mouseMoved(8, 8);
|
|
25
25
|
// Get the snap point
|
|
26
26
|
const snapPoint1 = snapping.getSnapPoint();
|
|
27
|
-
expect(snapPoint1.x).toBe(10);
|
|
28
|
-
expect(snapPoint1.y).toBe(10);
|
|
27
|
+
expect(snapPoint1.snapPoint.x).toBe(10);
|
|
28
|
+
expect(snapPoint1.snapPoint.y).toBe(10);
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
// set the mouse position
|
|
32
32
|
core.mouse.mouseMoved(101, 11);
|
|
33
33
|
// Get the snap point
|
|
34
34
|
const snapPoint2 = snapping.getSnapPoint();
|
|
35
|
-
expect(snapPoint2.x).toBe(100);
|
|
36
|
-
expect(snapPoint2.y).toBe(10);
|
|
35
|
+
expect(snapPoint2.snapPoint.x).toBe(100);
|
|
36
|
+
expect(snapPoint2.snapPoint.y).toBe(10);
|
|
37
37
|
|
|
38
38
|
// set the mouse position
|
|
39
39
|
core.mouse.mouseMoved(100, 100);
|
|
@@ -48,8 +48,8 @@ test('Test Snapping.snap active', () => {
|
|
|
48
48
|
core.mouse.mouseMoved(8, 8);
|
|
49
49
|
const result = snapping.snap();
|
|
50
50
|
expect(result).toBeDefined();
|
|
51
|
-
expect(result.x).toBe(10);
|
|
52
|
-
expect(result.y).toBe(10);
|
|
51
|
+
expect(result.snapPoint.x).toBe(10);
|
|
52
|
+
expect(result.snapPoint.y).toBe(10);
|
|
53
53
|
expect(DesignCore.Scene.auxiliaryEntities.count()).toBeGreaterThan(0);
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -61,24 +61,29 @@ test('Test Snapping.snap inactive', () => {
|
|
|
61
61
|
|
|
62
62
|
test('Test Snapping.addSnapPoint', () => {
|
|
63
63
|
DesignCore.Scene.auxiliaryEntities.clear();
|
|
64
|
-
snapping.addSnapPoint(new Point(10, 10));
|
|
64
|
+
snapping.addSnapPoint(new SnapPoint(new Point(10, 10)));
|
|
65
65
|
expect(DesignCore.Scene.auxiliaryEntities.count()).toBe(1);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
test('Test SnapPoint.draw', () => {
|
|
69
69
|
DesignCore.Scene.auxiliaryEntities.clear();
|
|
70
|
-
snapping.addSnapPoint(new Point(0.5, 0.5));
|
|
70
|
+
snapping.addSnapPoint(new SnapPoint(new Point(0.5, 0.5), SnapPoint.Type.CENTRE));
|
|
71
71
|
const snapPointEntity = DesignCore.Scene.auxiliaryEntities.get(0);
|
|
72
72
|
const ctx = {
|
|
73
|
-
|
|
73
|
+
strokeStyle: '',
|
|
74
|
+
lineWidth: 0,
|
|
75
|
+
setLineDash: jest.fn(),
|
|
74
76
|
beginPath: jest.fn(),
|
|
77
|
+
moveTo: jest.fn(),
|
|
78
|
+
lineTo: jest.fn(),
|
|
79
|
+
closePath: jest.fn(),
|
|
75
80
|
arc: jest.fn(),
|
|
76
|
-
|
|
81
|
+
stroke: jest.fn(),
|
|
77
82
|
};
|
|
78
83
|
snapPointEntity.draw(ctx, 1);
|
|
79
84
|
expect(ctx.beginPath).toHaveBeenCalled();
|
|
80
85
|
expect(ctx.arc).toHaveBeenCalledWith(0.5, 0.5, expect.any(Number), 0, 6.283);
|
|
81
|
-
expect(ctx.
|
|
86
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
82
87
|
});
|
|
83
88
|
|
|
84
89
|
test('Test Snapping.addTrackingLine', () => {
|
|
@@ -193,3 +198,204 @@ test('Test Snapping.orthoSnap', () => {
|
|
|
193
198
|
core.mouse.mouseMoved(100, -20);
|
|
194
199
|
expect(snapping.orthoSnap(new Point(0, 0))).toBeUndefined();
|
|
195
200
|
});
|
|
201
|
+
|
|
202
|
+
// ─── Context mock helpers ────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
/** @return {object} mock HTML canvas context for SnapPoint.draw tests */
|
|
205
|
+
function makeSnapCtx() {
|
|
206
|
+
return {
|
|
207
|
+
strokeStyle: '',
|
|
208
|
+
lineWidth: 0,
|
|
209
|
+
setLineDash: jest.fn(),
|
|
210
|
+
beginPath: jest.fn(),
|
|
211
|
+
moveTo: jest.fn(),
|
|
212
|
+
lineTo: jest.fn(),
|
|
213
|
+
closePath: jest.fn(),
|
|
214
|
+
arc: jest.fn(),
|
|
215
|
+
stroke: jest.fn(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** @return {object} mock HTML canvas context for TrackingLine.draw tests */
|
|
220
|
+
function makeTrackingCtx() {
|
|
221
|
+
return {
|
|
222
|
+
save: jest.fn(),
|
|
223
|
+
restore: jest.fn(),
|
|
224
|
+
beginPath: jest.fn(),
|
|
225
|
+
strokeStyle: '',
|
|
226
|
+
lineWidth: 0,
|
|
227
|
+
setLineDash: jest.fn(),
|
|
228
|
+
moveTo: jest.fn(),
|
|
229
|
+
lineTo: jest.fn(),
|
|
230
|
+
stroke: jest.fn(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── Snapping.reset ──────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
test('Test Snapping.reset', () => {
|
|
237
|
+
snapping.active = true;
|
|
238
|
+
snapping.snapOverride = SnapPoint.Type.END;
|
|
239
|
+
snapping.reset();
|
|
240
|
+
expect(snapping.active).toBe(false);
|
|
241
|
+
expect(snapping.snapOverride).toBeNull();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ─── Snapping.setSnapOverride ────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
test('Test Snapping.setSnapOverride valid type', () => {
|
|
247
|
+
snapping.setSnapOverride(SnapPoint.Type.END);
|
|
248
|
+
expect(snapping.snapOverride).toBe(SnapPoint.Type.END);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('Test Snapping.setSnapOverride null clears override', () => {
|
|
252
|
+
snapping.setSnapOverride(SnapPoint.Type.END);
|
|
253
|
+
snapping.setSnapOverride(null);
|
|
254
|
+
expect(snapping.snapOverride).toBeNull();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('Test Snapping.setSnapOverride NONE type', () => {
|
|
258
|
+
snapping.setSnapOverride(SnapPoint.Type.NONE);
|
|
259
|
+
expect(snapping.snapOverride).toBe(SnapPoint.Type.NONE);
|
|
260
|
+
snapping.setSnapOverride(null);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('Test Snapping.setSnapOverride invalid type throws', () => {
|
|
264
|
+
expect(() => snapping.setSnapOverride('invalid')).toThrow('Invalid snap override type: invalid');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// ─── Snapping.getSnapPoint with snap override ────────────────────────────────
|
|
268
|
+
|
|
269
|
+
test('Test Snapping.getSnapPoint with END override returns end snap', () => {
|
|
270
|
+
snapping.setSnapOverride(SnapPoint.Type.END);
|
|
271
|
+
core.mouse.mouseMoved(8, 8);
|
|
272
|
+
const result = snapping.getSnapPoint();
|
|
273
|
+
expect(result).toBeDefined();
|
|
274
|
+
expect(result.type).toBe(SnapPoint.Type.END);
|
|
275
|
+
snapping.setSnapOverride(null);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('Test Snapping.getSnapPoint with NONE override suppresses all snaps', () => {
|
|
279
|
+
snapping.setSnapOverride(SnapPoint.Type.NONE);
|
|
280
|
+
core.mouse.mouseMoved(8, 8);
|
|
281
|
+
const result = snapping.getSnapPoint();
|
|
282
|
+
expect(result).toBeUndefined();
|
|
283
|
+
snapping.setSnapOverride(null);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('Test Snapping.getSnapPoint with non-matching override returns undefined', () => {
|
|
287
|
+
// Mouse near end of line at (10,10); mid-point is at (55,10), well outside aperture
|
|
288
|
+
snapping.setSnapOverride(SnapPoint.Type.MID);
|
|
289
|
+
core.mouse.mouseMoved(8, 8);
|
|
290
|
+
const result = snapping.getSnapPoint();
|
|
291
|
+
expect(result).toBeUndefined();
|
|
292
|
+
snapping.setSnapOverride(null);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ─── SnapPoint.draw – all snap types ────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
test('Test SnapPoint.draw END type draws closed polygon without arc', () => {
|
|
298
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.END);
|
|
299
|
+
const ctx = makeSnapCtx();
|
|
300
|
+
sp.draw(ctx, 1);
|
|
301
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
302
|
+
expect(ctx.closePath).toHaveBeenCalled();
|
|
303
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
304
|
+
expect(ctx.arc).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('Test SnapPoint.draw MID type draws closed polygon without arc', () => {
|
|
308
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.MID);
|
|
309
|
+
const ctx = makeSnapCtx();
|
|
310
|
+
sp.draw(ctx, 1);
|
|
311
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
312
|
+
expect(ctx.closePath).toHaveBeenCalled();
|
|
313
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
314
|
+
expect(ctx.arc).not.toHaveBeenCalled();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('Test SnapPoint.draw QUADRANT type draws closed polygon without arc', () => {
|
|
318
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.QUADRANT);
|
|
319
|
+
const ctx = makeSnapCtx();
|
|
320
|
+
sp.draw(ctx, 1);
|
|
321
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
322
|
+
expect(ctx.closePath).toHaveBeenCalled();
|
|
323
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
324
|
+
expect(ctx.arc).not.toHaveBeenCalled();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('Test SnapPoint.draw NEAREST type draws closed polygon without arc', () => {
|
|
328
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.NEAREST);
|
|
329
|
+
const ctx = makeSnapCtx();
|
|
330
|
+
sp.draw(ctx, 1);
|
|
331
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
332
|
+
expect(ctx.closePath).toHaveBeenCalled();
|
|
333
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
334
|
+
expect(ctx.arc).not.toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('Test SnapPoint.draw TANGENT type draws arc with tangent line', () => {
|
|
338
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.TANGENT);
|
|
339
|
+
const ctx = makeSnapCtx();
|
|
340
|
+
sp.draw(ctx, 1);
|
|
341
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
342
|
+
expect(ctx.arc).toHaveBeenCalledWith(5, 5, expect.any(Number), 0, 6.283);
|
|
343
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
344
|
+
expect(ctx.closePath).not.toHaveBeenCalled();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('Test SnapPoint.draw NODE type draws arc with X inside', () => {
|
|
348
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.NODE);
|
|
349
|
+
const ctx = makeSnapCtx();
|
|
350
|
+
sp.draw(ctx, 1);
|
|
351
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
352
|
+
expect(ctx.arc).toHaveBeenCalledWith(5, 5, expect.any(Number), 0, 6.283);
|
|
353
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
354
|
+
expect(ctx.closePath).not.toHaveBeenCalled();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('Test SnapPoint.draw PERPENDICULAR type draws L-shape without arc', () => {
|
|
358
|
+
const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.PERPENDICULAR);
|
|
359
|
+
const ctx = makeSnapCtx();
|
|
360
|
+
sp.draw(ctx, 1);
|
|
361
|
+
expect(ctx.beginPath).toHaveBeenCalled();
|
|
362
|
+
expect(ctx.stroke).toHaveBeenCalled();
|
|
363
|
+
expect(ctx.closePath).not.toHaveBeenCalled();
|
|
364
|
+
expect(ctx.arc).not.toHaveBeenCalled();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// ─── TrackingLine.draw – out-of-bounds cases ─────────────────────────────────
|
|
368
|
+
|
|
369
|
+
test('Test TrackingLine.draw vertical line outside x bounds', () => {
|
|
370
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
371
|
+
// dir.x === 0 and from.x = 2 > boundsMax.x = 1 → draw returns early
|
|
372
|
+
snapping.addTrackingLine(new Point(2, -0.5), new Point(2, 0.5));
|
|
373
|
+
const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
|
|
374
|
+
const ctx = makeTrackingCtx();
|
|
375
|
+
trackingLine.draw(ctx, 1);
|
|
376
|
+
expect(ctx.moveTo).not.toHaveBeenCalled();
|
|
377
|
+
expect(ctx.stroke).not.toHaveBeenCalled();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('Test TrackingLine.draw horizontal line outside y bounds', () => {
|
|
381
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
382
|
+
// dir.y === 0 and from.y = -5 < boundsMin.y = -1 → draw returns early
|
|
383
|
+
snapping.addTrackingLine(new Point(0.5, -5), new Point(0.8, -5));
|
|
384
|
+
const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
|
|
385
|
+
const ctx = makeTrackingCtx();
|
|
386
|
+
trackingLine.draw(ctx, 1);
|
|
387
|
+
expect(ctx.moveTo).not.toHaveBeenCalled();
|
|
388
|
+
expect(ctx.stroke).not.toHaveBeenCalled();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('Test TrackingLine.draw line does not intersect canvas bounds', () => {
|
|
392
|
+
DesignCore.Scene.auxiliaryEntities.clear();
|
|
393
|
+
// from = (2, -0.5), dir = (1, 1): x-clamp gives t ∈ [-2, -1], y-clamp gives t ∈ [-0.5, 0.5]
|
|
394
|
+
// tMax (-1) < tMin (-0.5) → draw returns early
|
|
395
|
+
snapping.addTrackingLine(new Point(2, -0.5), new Point(3, 0.5));
|
|
396
|
+
const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
|
|
397
|
+
const ctx = makeTrackingCtx();
|
|
398
|
+
trackingLine.draw(ctx, 1);
|
|
399
|
+
expect(ctx.moveTo).not.toHaveBeenCalled();
|
|
400
|
+
expect(ctx.stroke).not.toHaveBeenCalled();
|
|
401
|
+
});
|
|
@@ -829,3 +829,118 @@ test('Fillet.action radius>0 trimMode=false Line + Lwpolyline: standalone arc ad
|
|
|
829
829
|
expect(core.scene.entities.get(0).points.length).toBe(2);
|
|
830
830
|
expect(core.scene.entities.get(1).points.length).toBe(4);
|
|
831
831
|
});
|
|
832
|
+
|
|
833
|
+
// ─── action: closed polyline closing-wrap arc direction (regression) ──────────
|
|
834
|
+
//
|
|
835
|
+
// Square: P0=(0,0), P1=(10,0), P2=(10,10), P3=(0,10), closed flag=1.
|
|
836
|
+
// Closing segment (idx=4): P3=(0,10)→P0=(0,0) — i.e. the left edge, travelling downward.
|
|
837
|
+
// Segment 1: P0=(0,0)→P1=(10,0) — i.e. the bottom edge, travelling rightward.
|
|
838
|
+
// The shared corner is P0=(0,0).
|
|
839
|
+
//
|
|
840
|
+
// With radius 2 the fillet must sit inside the square (bottom-left interior corner).
|
|
841
|
+
// The arc centre is at (2,2).
|
|
842
|
+
// – lowerTangent = (0,2) on the closing segment (left edge, y=2)
|
|
843
|
+
// – upperTangent = (2,0) on segment 1 (bottom edge, x=2)
|
|
844
|
+
// – bulge on lowerTangent must be POSITIVE (CCW arc from (0,2) to (2,0) through ≈(0.59,0.59))
|
|
845
|
+
// (BasePolyline convention: –ve bulge = CW, +ve bulge = CCW)
|
|
846
|
+
//
|
|
847
|
+
// Before the fix the lowerTangent/upperTangent assignment was inverted, producing
|
|
848
|
+
// an arc that traversed the exterior of the square.
|
|
849
|
+
|
|
850
|
+
test('Fillet.action closing-wrap: lowerTangent is on closing segment (left edge)', () => {
|
|
851
|
+
core.scene.clear();
|
|
852
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
853
|
+
const polyEntity = core.scene.entities.get(0);
|
|
854
|
+
polyEntity.flags.setFlagValue(1);
|
|
855
|
+
|
|
856
|
+
core.scene.headers.filletRadius = 2;
|
|
857
|
+
core.scene.headers.trimMode = true;
|
|
858
|
+
|
|
859
|
+
// firstPick = closing segment (idx=4, left edge), secondPick = segment 1 (bottom edge)
|
|
860
|
+
const fillet = new Fillet();
|
|
861
|
+
fillet.firstPick.entity = polyEntity;
|
|
862
|
+
fillet.secondPick.entity = polyEntity;
|
|
863
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
864
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
865
|
+
fillet.firstPick.clickPoint = new Point(0, 5);
|
|
866
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
867
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
868
|
+
fillet.secondPick.clickPoint = new Point(5, 0);
|
|
869
|
+
fillet.action();
|
|
870
|
+
|
|
871
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
872
|
+
const poly = core.scene.entities.get(0);
|
|
873
|
+
expect(poly.points.length).toBe(5);
|
|
874
|
+
|
|
875
|
+
// After the splice at cornerIdx=0:
|
|
876
|
+
// points[0] = lowerTangent (arcStartPoint), points[1] = upperTangent
|
|
877
|
+
// lowerTangent must be on the closing segment (left edge, x≈0)
|
|
878
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
879
|
+
expect(poly.points[0].y).toBeCloseTo(2);
|
|
880
|
+
// upperTangent must be on segment 1 (bottom edge, y≈0)
|
|
881
|
+
expect(poly.points[1].x).toBeCloseTo(2);
|
|
882
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test('Fillet.action closing-wrap: arc bulge sign is positive (CCW, fillet inside square)', () => {
|
|
886
|
+
// Same setup. The CCW arc from (0,2) to (2,0) sweeps through (≈0.59, ≈0.59)
|
|
887
|
+
// staying inside the square — a positive bulge value.
|
|
888
|
+
// Before the fix the lowerTangent/upperTangent were swapped, producing an arc
|
|
889
|
+
// that traversed the exterior of the square.
|
|
890
|
+
core.scene.clear();
|
|
891
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
892
|
+
const polyEntity = core.scene.entities.get(0);
|
|
893
|
+
polyEntity.flags.setFlagValue(1);
|
|
894
|
+
|
|
895
|
+
core.scene.headers.filletRadius = 2;
|
|
896
|
+
core.scene.headers.trimMode = true;
|
|
897
|
+
|
|
898
|
+
const fillet = new Fillet();
|
|
899
|
+
fillet.firstPick.entity = polyEntity;
|
|
900
|
+
fillet.secondPick.entity = polyEntity;
|
|
901
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
902
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
903
|
+
fillet.firstPick.clickPoint = new Point(0, 5);
|
|
904
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
905
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
906
|
+
fillet.secondPick.clickPoint = new Point(5, 0);
|
|
907
|
+
fillet.action();
|
|
908
|
+
|
|
909
|
+
const poly = core.scene.entities.get(0);
|
|
910
|
+
// points[0] carries the bulge for the arc from lowerTangent to upperTangent
|
|
911
|
+
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test('Fillet.action closing-wrap reversed pick order: arc bulge sign and tangent positions are correct', () => {
|
|
915
|
+
// Swap firstPick/secondPick — should produce the same geometry.
|
|
916
|
+
core.scene.clear();
|
|
917
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
918
|
+
const polyEntity = core.scene.entities.get(0);
|
|
919
|
+
polyEntity.flags.setFlagValue(1);
|
|
920
|
+
|
|
921
|
+
core.scene.headers.filletRadius = 2;
|
|
922
|
+
core.scene.headers.trimMode = true;
|
|
923
|
+
|
|
924
|
+
// firstPick = segment 1 (bottom edge), secondPick = closing segment (left edge, idx=4)
|
|
925
|
+
const fillet = new Fillet();
|
|
926
|
+
fillet.firstPick.entity = polyEntity;
|
|
927
|
+
fillet.secondPick.entity = polyEntity;
|
|
928
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
929
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
930
|
+
fillet.firstPick.clickPoint = new Point(5, 0);
|
|
931
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
932
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
933
|
+
fillet.secondPick.clickPoint = new Point(0, 5);
|
|
934
|
+
fillet.action();
|
|
935
|
+
|
|
936
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
937
|
+
const poly = core.scene.entities.get(0);
|
|
938
|
+
expect(poly.points.length).toBe(5);
|
|
939
|
+
|
|
940
|
+
// Regardless of pick order: lowerTangent at (0,2), upperTangent at (2,0), bulge > 0 (CCW)
|
|
941
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
942
|
+
expect(poly.points[0].y).toBeCloseTo(2);
|
|
943
|
+
expect(poly.points[1].x).toBeCloseTo(2);
|
|
944
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
945
|
+
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
946
|
+
});
|