@design-core/core 0.0.5 → 0.0.7
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/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +33 -284
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- 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/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- 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 +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +36 -13
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- 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 +27 -5
- package/core/lib/selectionManager.js +2 -2
- 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 +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +129 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- 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/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- 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/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- 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 +3 -3
- 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 +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +296 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
|
@@ -10,10 +10,11 @@ import { Line } from './line.js';
|
|
|
10
10
|
import { Arc } from './arc.js';
|
|
11
11
|
import { Flags } from '../properties/flags.js';
|
|
12
12
|
import { Property } from '../properties/property.js';
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
|
|
15
15
|
import { DesignCore } from '../designCore.js';
|
|
16
|
-
import { SnapPoint } from '../lib/
|
|
16
|
+
import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
|
|
17
|
+
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Base Polyline Entity Class
|
|
@@ -75,6 +76,14 @@ export class BasePolyline extends Entity {
|
|
|
75
76
|
|
|
76
77
|
this.flags.setFlagValue(Property.loadValue([data?.flags, data?.[70]], 0));
|
|
77
78
|
|
|
79
|
+
// DXF Groupcode 70 bit 1 - closed flag exposed via EntityProperties
|
|
80
|
+
this.properties.add(Property.Names.CLOSED, {
|
|
81
|
+
type: Property.Type.BOOLEAN,
|
|
82
|
+
get: (entity) => entity.flags.hasFlag(1),
|
|
83
|
+
set: (entity, value) => value ? entity.flags.addValue(1) : entity.flags.removeValue(1),
|
|
84
|
+
dxfCode: 70,
|
|
85
|
+
});
|
|
86
|
+
|
|
78
87
|
// If the first and last points are the same the polyline should be considered closed.
|
|
79
88
|
// Require at least 4 points so that after removing the duplicate endpoint at least 3 remain.
|
|
80
89
|
if (this.points.length >= 4 && this.points[0].isSame(this.points.at(-1))) {
|
|
@@ -161,54 +170,25 @@ export class BasePolyline extends Entity {
|
|
|
161
170
|
|
|
162
171
|
if (this.points.length >= 1) {
|
|
163
172
|
const points = [...this.points, mousePoint];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
if (this.inputMode === this.modes.ARC) {
|
|
174
|
+
// Straight segment is not the final form — show as rubber-band indicator
|
|
175
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([this.points.at(-1), mousePoint]));
|
|
176
|
+
// Arc segment is the final form — draw on the current layer
|
|
177
|
+
const arcpoints = Utils.cloneObject(this.points);
|
|
178
|
+
arcpoints.at(-1).bulge = this.getBulgeFromSegment(mousePoint);
|
|
179
|
+
DesignCore.Scene.previewEntities.create(this.type, { points: [...arcpoints, mousePoint] });
|
|
180
|
+
} else {
|
|
181
|
+
DesignCore.Scene.previewEntities.create(this.type, { points: points });
|
|
182
|
+
}
|
|
172
183
|
}
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
/**
|
|
176
187
|
* Draw the polyline
|
|
177
|
-
* @param {Object}
|
|
178
|
-
* @param {number} scale
|
|
179
|
-
* @param {boolean} stroke - don't stroke hatch boundary shapes
|
|
188
|
+
* @param {Object} renderer
|
|
180
189
|
*/
|
|
181
|
-
draw(
|
|
182
|
-
|
|
183
|
-
if (this.points[i].bulge === 0) {
|
|
184
|
-
ctx.lineTo(this.points[i].x, this.points[i].y);
|
|
185
|
-
} else {
|
|
186
|
-
// define the next point or the first point for closed shapes
|
|
187
|
-
const nextPoint = this.points[i + 1] || this.points[0];
|
|
188
|
-
const centerPoint = this.points[i].bulgeCentrePoint(nextPoint);
|
|
189
|
-
const radius = this.points[i].bulgeRadius(nextPoint);
|
|
190
|
-
|
|
191
|
-
if (this.points[i].bulge > 0) {
|
|
192
|
-
// TODO: make this work with canvas
|
|
193
|
-
ctx.arc(centerPoint.x, centerPoint.y, radius, centerPoint.angle(this.points[i]), centerPoint.angle(nextPoint));
|
|
194
|
-
} else {
|
|
195
|
-
try { // HTML
|
|
196
|
-
ctx.arc(centerPoint.x, centerPoint.y, radius, centerPoint.angle(this.points[i]), centerPoint.angle(nextPoint), true);
|
|
197
|
-
} catch { // Cairo
|
|
198
|
-
ctx.arcNegative(centerPoint.x, centerPoint.y, radius, centerPoint.angle(this.points[i]), centerPoint.angle(nextPoint));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// handle a closed shape
|
|
205
|
-
if (this.flags.hasFlag(1)) {
|
|
206
|
-
ctx.lineTo(this.points[0].x, this.points[0].y);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (stroke) {
|
|
210
|
-
ctx.stroke();
|
|
211
|
-
}
|
|
190
|
+
draw(renderer) {
|
|
191
|
+
renderer.drawShape(this.toPolylinePoints());
|
|
212
192
|
}
|
|
213
193
|
|
|
214
194
|
/**
|
|
@@ -217,12 +197,12 @@ export class BasePolyline extends Entity {
|
|
|
217
197
|
*/
|
|
218
198
|
dxf(file) {
|
|
219
199
|
file.writeGroupCode('0', 'LWPOLYLINE');
|
|
220
|
-
file.writeGroupCode('5', this.
|
|
200
|
+
file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
|
|
221
201
|
file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
|
|
222
202
|
file.writeGroupCode('100', 'AcDbPolyline', DXFFile.Version.R2000);
|
|
223
|
-
file.writeGroupCode('8', this.
|
|
224
|
-
file.writeGroupCode('6', this.
|
|
225
|
-
file.writeGroupCode('39', this.
|
|
203
|
+
file.writeGroupCode('8', this.getProperty(Property.Names.LAYER)); // LAYERNAME
|
|
204
|
+
file.writeGroupCode('6', this.getProperty(Property.Names.LINETYPE));
|
|
205
|
+
file.writeGroupCode('39', this.getProperty(Property.Names.LINEWIDTH));
|
|
226
206
|
file.writeGroupCode('90', this.points.length);
|
|
227
207
|
file.writeGroupCode('70', this.flags.getFlagValue());
|
|
228
208
|
for (let i = 0; i < this.points.length; i++) {
|
|
@@ -253,6 +233,7 @@ export class BasePolyline extends Entity {
|
|
|
253
233
|
/**
|
|
254
234
|
* Set entity points from a polyline point representation
|
|
255
235
|
* @param {Array} points
|
|
236
|
+
* @return {BasePolyline}
|
|
256
237
|
*/
|
|
257
238
|
fromPolylinePoints(points) {
|
|
258
239
|
const pts = points.map((p) => new Point(p.x, p.y, p.bulge));
|
|
@@ -260,6 +241,7 @@ export class BasePolyline extends Entity {
|
|
|
260
241
|
pts.pop();
|
|
261
242
|
}
|
|
262
243
|
this.points = pts;
|
|
244
|
+
return this;
|
|
263
245
|
}
|
|
264
246
|
|
|
265
247
|
/**
|
|
@@ -398,9 +380,10 @@ export class BasePolyline extends Entity {
|
|
|
398
380
|
let distance = Infinity;
|
|
399
381
|
let minPnt = P;
|
|
400
382
|
|
|
401
|
-
|
|
383
|
+
const segmentCount = this.flags.hasFlag(1) ? this.points.length : this.points.length - 1;
|
|
384
|
+
for (let i = 1; i <= segmentCount; i++) {
|
|
402
385
|
const A = this.points[i - 1];
|
|
403
|
-
const B = this.points[i];
|
|
386
|
+
const B = this.points[i % this.points.length];
|
|
404
387
|
|
|
405
388
|
let pnt;
|
|
406
389
|
if (A.bulge !== 0) {
|
|
@@ -491,240 +474,6 @@ export class BasePolyline extends Entity {
|
|
|
491
474
|
return A.distance(point);
|
|
492
475
|
}
|
|
493
476
|
|
|
494
|
-
/**
|
|
495
|
-
* Extend the entity
|
|
496
|
-
* Only extends when the end segment closest to the mouse is a line segment (bulge === 0)
|
|
497
|
-
* @param {Array} intersections - array of intersection points
|
|
498
|
-
* @return {Array} - array of state changes
|
|
499
|
-
*/
|
|
500
|
-
extend(intersections) {
|
|
501
|
-
const stateChanges = [];
|
|
502
|
-
|
|
503
|
-
if (!intersections?.length) {
|
|
504
|
-
return stateChanges;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const mousePosition = DesignCore.Mouse.pointOnScene();
|
|
508
|
-
const lastIndex = this.points.length - 1;
|
|
509
|
-
|
|
510
|
-
// Determine which end segment is closer to the mouse
|
|
511
|
-
const closestOnFirst = this.closestPointOnSegment(mousePosition, 1);
|
|
512
|
-
const closestOnLast = this.closestPointOnSegment(mousePosition, lastIndex);
|
|
513
|
-
const distToFirst = mousePosition.distance(closestOnFirst);
|
|
514
|
-
const distToLast = mousePosition.distance(closestOnLast);
|
|
515
|
-
|
|
516
|
-
// Identify the selected end: the endpoint index and the bulge of its segment
|
|
517
|
-
let endPointIndex;
|
|
518
|
-
let endSegmentBulge;
|
|
519
|
-
|
|
520
|
-
if (distToFirst < distToLast) {
|
|
521
|
-
endPointIndex = 0;
|
|
522
|
-
endSegmentBulge = this.points[0].bulge;
|
|
523
|
-
} else if (distToLast < distToFirst) {
|
|
524
|
-
endPointIndex = lastIndex;
|
|
525
|
-
endSegmentBulge = this.points[lastIndex - 1].bulge;
|
|
526
|
-
} else {
|
|
527
|
-
// Tie (includes 2-point polyline where both segments are the same)
|
|
528
|
-
// Break tie by distance to the actual endpoints
|
|
529
|
-
if (mousePosition.distance(this.points[0]) <= mousePosition.distance(this.points[lastIndex])) {
|
|
530
|
-
endPointIndex = 0;
|
|
531
|
-
endSegmentBulge = this.points[0].bulge;
|
|
532
|
-
} else {
|
|
533
|
-
endPointIndex = lastIndex;
|
|
534
|
-
endSegmentBulge = this.points[lastIndex - 1].bulge;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Only allow extending line segments (not arcs)
|
|
539
|
-
if (endSegmentBulge !== 0) {
|
|
540
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Message.NOEXTEND}`);
|
|
541
|
-
return stateChanges;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const endPoint = this.points[endPointIndex];
|
|
545
|
-
const adjacentPoint = endPointIndex === 0 ? this.points[1] : this.points[lastIndex - 1];
|
|
546
|
-
|
|
547
|
-
// Sort intersections by distance from the end point
|
|
548
|
-
Utils.sortPointsByDistance(intersections, endPoint);
|
|
549
|
-
|
|
550
|
-
// Find the closest intersection that lies beyond the endpoint
|
|
551
|
-
const direction = endPoint.subtract(adjacentPoint);
|
|
552
|
-
const newEndPoint = intersections.find((p) => {
|
|
553
|
-
if (p.isSame(endPoint)) return false;
|
|
554
|
-
if (p.subtract(endPoint).dot(direction) <= 0) return false;
|
|
555
|
-
if (adjacentPoint.distance(p) <= adjacentPoint.distance(endPoint)) return false;
|
|
556
|
-
return true;
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
if (!newEndPoint) {
|
|
560
|
-
return stateChanges;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Build the new points array
|
|
564
|
-
const newPoints = this.points.map((p) => p.clone());
|
|
565
|
-
newPoints[endPointIndex] = newEndPoint;
|
|
566
|
-
|
|
567
|
-
if (newPoints[endPointIndex].isSame(this.points[endPointIndex])) {
|
|
568
|
-
return stateChanges;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
stateChanges.push(new UpdateState(this, { points: newPoints }));
|
|
572
|
-
return stateChanges;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Trim the entity
|
|
577
|
-
* @param {Array} intersections - array of intersection points
|
|
578
|
-
* @return {Array} - array of state changes
|
|
579
|
-
*/
|
|
580
|
-
trim(intersections) {
|
|
581
|
-
const stateChanges = [];
|
|
582
|
-
|
|
583
|
-
if (!intersections?.length) {
|
|
584
|
-
return stateChanges;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const mousePosition = DesignCore.Mouse.pointOnScene();
|
|
588
|
-
|
|
589
|
-
// Find the segment index closest to the mouse
|
|
590
|
-
let mouseSegmentIndex = 0;
|
|
591
|
-
let minDistance = Infinity;
|
|
592
|
-
|
|
593
|
-
for (let i = 1; i < this.points.length; i++) {
|
|
594
|
-
const closestPoint = this.closestPointOnSegment(mousePosition, i);
|
|
595
|
-
if (closestPoint) {
|
|
596
|
-
const dist = mousePosition.distance(closestPoint);
|
|
597
|
-
if (dist < minDistance) {
|
|
598
|
-
minDistance = dist;
|
|
599
|
-
mouseSegmentIndex = i;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Filter out intersections that coincide with existing polyline vertices
|
|
605
|
-
const filteredIntersections = intersections.filter((point) => !this.points.some((vertex) => vertex.isSame(point)));
|
|
606
|
-
|
|
607
|
-
if (!filteredIntersections.length) {
|
|
608
|
-
return stateChanges;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Locate each intersection on its segment with an ordering value
|
|
612
|
-
const locatedIntersections = [];
|
|
613
|
-
|
|
614
|
-
for (const point of filteredIntersections) {
|
|
615
|
-
for (let i = 1; i < this.points.length; i++) {
|
|
616
|
-
if (this.isPointOnSegment(point, this.points[i - 1], this.points[i])) {
|
|
617
|
-
locatedIntersections.push({
|
|
618
|
-
segmentIndex: i,
|
|
619
|
-
point: point,
|
|
620
|
-
positionAlongSegment: this.positionOnSegment(point, i),
|
|
621
|
-
});
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
if (!locatedIntersections.length) {
|
|
628
|
-
return stateChanges;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Sort by segment index, then by position along segment
|
|
632
|
-
locatedIntersections.sort((a, b) => {
|
|
633
|
-
if (a.segmentIndex !== b.segmentIndex) return a.segmentIndex - b.segmentIndex;
|
|
634
|
-
return a.positionAlongSegment - b.positionAlongSegment;
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
// Precompute the mouse position along its segment
|
|
638
|
-
const mouseClosest = this.closestPointOnSegment(mousePosition, mouseSegmentIndex);
|
|
639
|
-
const mousePos = mouseClosest ? this.positionOnSegment(mouseClosest, mouseSegmentIndex) : 0;
|
|
640
|
-
|
|
641
|
-
// Find the nearest intersections that bracket the mouse segment
|
|
642
|
-
// - trimBefore: the last intersection at or before the mouse
|
|
643
|
-
// - trimAfter: the first intersection at or after the mouse
|
|
644
|
-
let trimBefore = null;
|
|
645
|
-
let trimAfter = null;
|
|
646
|
-
|
|
647
|
-
for (const loc of locatedIntersections) {
|
|
648
|
-
if (loc.segmentIndex < mouseSegmentIndex) {
|
|
649
|
-
trimBefore = loc;
|
|
650
|
-
} else if (loc.segmentIndex === mouseSegmentIndex) {
|
|
651
|
-
if (loc.positionAlongSegment <= mousePos) {
|
|
652
|
-
trimBefore = loc;
|
|
653
|
-
} else if (!trimAfter) {
|
|
654
|
-
trimAfter = loc;
|
|
655
|
-
}
|
|
656
|
-
} else if (!trimAfter) {
|
|
657
|
-
trimAfter = loc;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Build new polyline(s) from the remaining portions
|
|
662
|
-
// Portion 1: start of polyline to trimBefore point
|
|
663
|
-
if (trimBefore) {
|
|
664
|
-
const points = [];
|
|
665
|
-
|
|
666
|
-
for (let i = 0; i < trimBefore.segmentIndex - 1; i++) {
|
|
667
|
-
points.push(this.points[i].clone());
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Add the segment start point (with adjusted bulge for arcs)
|
|
671
|
-
const segStart = this.points[trimBefore.segmentIndex - 1];
|
|
672
|
-
const segStartClone = segStart.clone();
|
|
673
|
-
if (segStart.bulge !== 0) {
|
|
674
|
-
segStartClone.bulge = segStart.partialBulge(this.points[trimBefore.segmentIndex], trimBefore.point);
|
|
675
|
-
}
|
|
676
|
-
points.push(segStartClone);
|
|
677
|
-
|
|
678
|
-
// Add the trim point (skip if it coincides with the last added point)
|
|
679
|
-
const trimPoint = trimBefore.point.clone();
|
|
680
|
-
if (!points.at(-1).isSame(trimPoint)) {
|
|
681
|
-
points.push(trimPoint);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (points.length >= 2) {
|
|
685
|
-
const polyline = Utils.cloneObject(this);
|
|
686
|
-
polyline.points = points;
|
|
687
|
-
polyline.flags.removeValue(1); // clear closed bit
|
|
688
|
-
stateChanges.push(new AddState(polyline));
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Portion 2: trimAfter point to end of polyline
|
|
693
|
-
if (trimAfter) {
|
|
694
|
-
const points = [];
|
|
695
|
-
const trimPoint = trimAfter.point.clone();
|
|
696
|
-
|
|
697
|
-
// If the segment is an arc, set remaining arc bulge
|
|
698
|
-
const segStart = this.points[trimAfter.segmentIndex - 1];
|
|
699
|
-
if (segStart.bulge !== 0) {
|
|
700
|
-
trimPoint.bulge = segStart.partialBulge(this.points[trimAfter.segmentIndex], trimPoint, true);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
points.push(trimPoint);
|
|
704
|
-
|
|
705
|
-
// Add remaining points (skip if coincides with the trim point)
|
|
706
|
-
for (let i = trimAfter.segmentIndex; i < this.points.length; i++) {
|
|
707
|
-
const nextPoint = this.points[i].clone();
|
|
708
|
-
if (!points.at(-1).isSame(nextPoint)) {
|
|
709
|
-
points.push(nextPoint);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
if (points.length >= 2) {
|
|
714
|
-
const polyline = Utils.cloneObject(this);
|
|
715
|
-
polyline.points = points;
|
|
716
|
-
polyline.flags.removeValue(1); // clear closed bit
|
|
717
|
-
stateChanges.push(new AddState(polyline));
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
if (trimBefore || trimAfter) {
|
|
722
|
-
stateChanges.push(new RemoveState(this));
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
return stateChanges;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
477
|
/**
|
|
729
478
|
* Return boundingbox for entity
|
|
730
479
|
* @return {BoundingBox}
|
package/core/entities/circle.js
CHANGED
|
@@ -5,12 +5,11 @@ import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
|
5
5
|
import { Logging } from '../lib/logging.js';
|
|
6
6
|
import { DXFFile } from '../lib/dxf/dxfFile.js';
|
|
7
7
|
import { BoundingBox } from '../lib/boundingBox.js';
|
|
8
|
-
import { Property } from '../properties/property.js';
|
|
9
|
-
import { Utils } from '../lib/utils.js';
|
|
10
|
-
|
|
11
8
|
import { DesignCore } from '../designCore.js';
|
|
12
|
-
|
|
13
|
-
import {
|
|
9
|
+
|
|
10
|
+
import { Property } from '../properties/property.js';
|
|
11
|
+
import { SnapPoint } from '../lib/auxiliary/snapPoint.js';
|
|
12
|
+
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Circle Entity Class
|
|
@@ -26,19 +25,20 @@ export class Circle extends Entity {
|
|
|
26
25
|
constructor(data) {
|
|
27
26
|
super(data);
|
|
28
27
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
get:
|
|
33
|
-
set:
|
|
34
|
-
|
|
28
|
+
// radius: computed from points — stored in EntityProperties
|
|
29
|
+
this.properties.add(Property.Names.RADIUS, {
|
|
30
|
+
type: Property.Type.NUMBER,
|
|
31
|
+
get: (entity) => entity.points[1] ? entity.points[0].distance(entity.points[1]) : 0,
|
|
32
|
+
set: (entity, rad) => {
|
|
33
|
+
entity.points[1] = entity.points[0].project(0, rad);
|
|
34
|
+
},
|
|
35
|
+
dxfCode: 40,
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
38
|
+
// Named scalar radius (internal API / named prop) — projects points[1] from center.
|
|
39
|
+
// Full-points data (post-execute or post-fromDxf) skips this.
|
|
40
|
+
if (data?.radius !== undefined && this.points.length < 2) {
|
|
41
|
+
this.setRadius(data.radius);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -54,6 +54,21 @@ export class Circle extends Entity {
|
|
|
54
54
|
return command;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Normalise DXF group codes into the canonical points-based representation.
|
|
59
|
+
* Called by the DXF loader before construction.
|
|
60
|
+
* @param {Object} data
|
|
61
|
+
* @return {Object}
|
|
62
|
+
*/
|
|
63
|
+
static fromDxf(data) {
|
|
64
|
+
const center = data.points?.[0];
|
|
65
|
+
if (!center || data[40] === undefined) return data;
|
|
66
|
+
return {
|
|
67
|
+
...data,
|
|
68
|
+
points: [center, center.project(0, data[40])],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
/**
|
|
58
73
|
* Execute method
|
|
59
74
|
* executes the workflow, requesting input required to create an entity
|
|
@@ -113,8 +128,8 @@ export class Circle extends Entity {
|
|
|
113
128
|
preview() {
|
|
114
129
|
if (this.points.length >= 1) {
|
|
115
130
|
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
116
|
-
|
|
117
|
-
DesignCore.Scene.
|
|
131
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([this.points.at(0), mousePoint]));
|
|
132
|
+
DesignCore.Scene.previewEntities.create(this.type, { points: [this.points.at(0), mousePoint] });
|
|
118
133
|
}
|
|
119
134
|
}
|
|
120
135
|
|
|
@@ -139,9 +154,12 @@ export class Circle extends Entity {
|
|
|
139
154
|
* @param {Object} ctx - context
|
|
140
155
|
* @param {number} scale
|
|
141
156
|
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Draw the circle
|
|
159
|
+
* @param {Object} renderer
|
|
160
|
+
*/
|
|
161
|
+
draw(renderer) {
|
|
162
|
+
renderer.drawShape(this.toPolylinePoints());
|
|
145
163
|
}
|
|
146
164
|
|
|
147
165
|
/**
|
|
@@ -150,17 +168,17 @@ export class Circle extends Entity {
|
|
|
150
168
|
*/
|
|
151
169
|
dxf(file) {
|
|
152
170
|
file.writeGroupCode('0', 'CIRCLE');
|
|
153
|
-
file.writeGroupCode('5', this.
|
|
171
|
+
file.writeGroupCode('5', this.getProperty(Property.Names.HANDLE), DXFFile.Version.R2000); // Handle
|
|
154
172
|
file.writeGroupCode('100', 'AcDbEntity', DXFFile.Version.R2000);
|
|
155
173
|
file.writeGroupCode('100', 'AcDbCircle', DXFFile.Version.R2000);
|
|
156
|
-
file.writeGroupCode('8', this.
|
|
157
|
-
file.writeGroupCode('6', this.
|
|
174
|
+
file.writeGroupCode('8', this.getProperty(Property.Names.LAYER));
|
|
175
|
+
file.writeGroupCode('6', this.getProperty(Property.Names.LINETYPE));
|
|
158
176
|
this.writeDxfColour(file);
|
|
159
177
|
file.writeGroupCode('10', this.points[0].x);
|
|
160
178
|
file.writeGroupCode('20', this.points[0].y);
|
|
161
179
|
file.writeGroupCode('30', '0.0');
|
|
162
|
-
file.writeGroupCode('39', this.
|
|
163
|
-
file.writeGroupCode('40', this.
|
|
180
|
+
file.writeGroupCode('39', this.getProperty(Property.Names.LINEWIDTH));
|
|
181
|
+
file.writeGroupCode('40', this.getProperty(Property.Names.RADIUS));
|
|
164
182
|
}
|
|
165
183
|
|
|
166
184
|
/**
|
|
@@ -168,76 +186,34 @@ export class Circle extends Entity {
|
|
|
168
186
|
* @return {Array}
|
|
169
187
|
*/
|
|
170
188
|
toPolylinePoints() {
|
|
171
|
-
const
|
|
189
|
+
const radius = this.getProperty(Property.Names.RADIUS);
|
|
190
|
+
const startPoint = this.points[0].project(0, radius);
|
|
172
191
|
startPoint.bulge = 1;
|
|
173
|
-
const endPoint = this.points[0].project(0, -
|
|
192
|
+
const endPoint = this.points[0].project(0, -radius);
|
|
174
193
|
endPoint.bulge = 1;
|
|
175
|
-
const closurePoint = this.points[0].project(0,
|
|
194
|
+
const closurePoint = this.points[0].project(0, radius);
|
|
176
195
|
return [startPoint, endPoint, closurePoint];
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
/**
|
|
180
|
-
* Set entity points from a polyline point representation
|
|
199
|
+
* Set entity points from a polyline point representation.
|
|
200
|
+
* If the points describe a full circle (start === end), mutates this Circle
|
|
201
|
+
* in place and returns it. Otherwise creates and returns an Arc, since a
|
|
202
|
+
* Circle cannot represent an open arc.
|
|
181
203
|
* @param {Array} points
|
|
204
|
+
* @return {Circle|Arc}
|
|
182
205
|
*/
|
|
183
206
|
fromPolylinePoints(points) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Trim the entity
|
|
191
|
-
* @param {Array} intersections
|
|
192
|
-
* @return {Array} - array of state changes
|
|
193
|
-
*/
|
|
194
|
-
trim(intersections) {
|
|
195
|
-
// array to hold state changes
|
|
196
|
-
const stateChanges = [];
|
|
197
|
-
|
|
198
|
-
if (intersections?.length === 0 || !intersections) {
|
|
199
|
-
return stateChanges;
|
|
207
|
+
if (points.length >= 3 && points[0].isSame(points.at(-1))) {
|
|
208
|
+
const center = points[0].bulgeCentrePoint(points[1]);
|
|
209
|
+
const radius = center.distance(points[0]);
|
|
210
|
+
this.points = [center, center.project(0, radius)];
|
|
211
|
+
return this;
|
|
200
212
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const mousePosition = DesignCore.Mouse.pointOnScene();
|
|
206
|
-
// get the point on the arc closest to the mouse
|
|
207
|
-
const pointOnCircle = mousePosition.closestPointOnArc(this.points[1], this.points[1], this.points[0]);
|
|
208
|
-
// sort intersection points
|
|
209
|
-
Utils.sortPointsOnArc(intersections, this.points[0], this.points[0], this.points[0], direction);
|
|
210
|
-
// Repeat the first point to close the circle
|
|
211
|
-
const testPoints = [...intersections, intersections.at(0)]; // Closing the circle
|
|
212
|
-
|
|
213
|
-
// Test if mouse position is between two intersection points
|
|
214
|
-
if (testPoints.length > 1) {
|
|
215
|
-
for (let i = 0; i < testPoints.length - 1; i++) {
|
|
216
|
-
const startPoint = testPoints[i];
|
|
217
|
-
const endPoint = testPoints[i + 1];
|
|
218
|
-
|
|
219
|
-
// check if the mouse is between startPoint and endPoint
|
|
220
|
-
if (pointOnCircle.isOnArc(startPoint, endPoint, this.points[0], direction)) {
|
|
221
|
-
// create a new arc entity
|
|
222
|
-
const arc = DesignCore.CommandManager.createNew('Arc', this);
|
|
223
|
-
arc.handle = undefined;
|
|
224
|
-
// flip direction
|
|
225
|
-
arc.direction = direction * -1;
|
|
226
|
-
arc.points = [this.points[0], startPoint, endPoint];
|
|
227
|
-
const addState = new AddState(arc);
|
|
228
|
-
stateChanges.push(addState);
|
|
229
|
-
|
|
230
|
-
// Remove the existing arc
|
|
231
|
-
if (stateChanges.length > 0) {
|
|
232
|
-
const removeState = new RemoveState(this);
|
|
233
|
-
stateChanges.push(removeState);
|
|
234
|
-
return stateChanges;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return stateChanges;
|
|
213
|
+
const arc = DesignCore.CommandManager.createNew('Arc', this);
|
|
214
|
+
arc.setProperty('handle', undefined);
|
|
215
|
+
arc.fromPolylinePoints(points);
|
|
216
|
+
return arc;
|
|
241
217
|
}
|
|
242
218
|
|
|
243
219
|
/**
|
|
@@ -251,13 +227,14 @@ export class Circle extends Entity {
|
|
|
251
227
|
|
|
252
228
|
const centre = this.points[0]; // circle centre point
|
|
253
229
|
|
|
230
|
+
const radius = this.getProperty(Property.Names.RADIUS);
|
|
254
231
|
snaps.push(new SnapPoint(centre, SnapPoint.Type.CENTRE));
|
|
255
232
|
|
|
256
233
|
snaps.push(
|
|
257
|
-
new SnapPoint(new Point(centre.x +
|
|
258
|
-
new SnapPoint(new Point(centre.x, centre.y +
|
|
259
|
-
new SnapPoint(new Point(centre.x -
|
|
260
|
-
new SnapPoint(new Point(centre.x, centre.y -
|
|
234
|
+
new SnapPoint(new Point(centre.x + radius, centre.y), SnapPoint.Type.QUADRANT), // 0°
|
|
235
|
+
new SnapPoint(new Point(centre.x, centre.y + radius), SnapPoint.Type.QUADRANT), // 90°
|
|
236
|
+
new SnapPoint(new Point(centre.x - radius, centre.y), SnapPoint.Type.QUADRANT), // 180°
|
|
237
|
+
new SnapPoint(new Point(centre.x, centre.y - radius), SnapPoint.Type.QUADRANT), // 270°
|
|
261
238
|
);
|
|
262
239
|
|
|
263
240
|
if (mousePoint) {
|
|
@@ -274,16 +251,16 @@ export class Circle extends Entity {
|
|
|
274
251
|
const angleFromCentreToInput = centre.angle(fromPoint); // angle from centre toward fromPoint
|
|
275
252
|
|
|
276
253
|
// Tangent: only possible when fromPoint is outside the circle radius
|
|
277
|
-
if (distanceToCenter >
|
|
278
|
-
const tangentHalfAngle = Math.acos(
|
|
254
|
+
if (distanceToCenter > radius) {
|
|
255
|
+
const tangentHalfAngle = Math.acos(radius / distanceToCenter); // half-angle between the two tangent directions
|
|
279
256
|
|
|
280
|
-
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput + tangentHalfAngle,
|
|
281
|
-
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput - tangentHalfAngle,
|
|
257
|
+
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput + tangentHalfAngle, radius), SnapPoint.Type.TANGENT));
|
|
258
|
+
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput - tangentHalfAngle, radius), SnapPoint.Type.TANGENT));
|
|
282
259
|
}
|
|
283
260
|
|
|
284
261
|
// Perpendicular: point on the circle that lies on the line from fromPoint through the centre
|
|
285
262
|
if (distanceToCenter > 0) {
|
|
286
|
-
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput,
|
|
263
|
+
snaps.push(new SnapPoint(centre.project(angleFromCentreToInput, radius), SnapPoint.Type.PERPENDICULAR));
|
|
287
264
|
}
|
|
288
265
|
}
|
|
289
266
|
|
|
@@ -308,10 +285,11 @@ export class Circle extends Entity {
|
|
|
308
285
|
* @return {BoundingBox}
|
|
309
286
|
*/
|
|
310
287
|
boundingBox() {
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
288
|
+
const radius = this.getProperty(Property.Names.RADIUS);
|
|
289
|
+
const xmin = this.points[0].x - radius;
|
|
290
|
+
const xmax = this.points[0].x + radius;
|
|
291
|
+
const ymin = this.points[0].y - radius;
|
|
292
|
+
const ymax = this.points[0].y + radius;
|
|
315
293
|
|
|
316
294
|
const topLeft = new Point(xmin, ymax);
|
|
317
295
|
const bottomRight = new Point(xmax, ymin);
|