@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,5 +1,5 @@
|
|
|
1
1
|
import { Line } from '../entities/line.js';
|
|
2
|
-
import {
|
|
2
|
+
import { PolylineBase } from '../entities/polylineBase.js';
|
|
3
3
|
|
|
4
4
|
import { DesignCore } from '../designCore.js';
|
|
5
5
|
|
|
@@ -61,7 +61,7 @@ export class CornerEntity {
|
|
|
61
61
|
setPick(entity, clickPoint, arcSegmentErrorMsg) {
|
|
62
62
|
this.entity = entity;
|
|
63
63
|
this.clickPoint = clickPoint;
|
|
64
|
-
if (!(this.entity instanceof
|
|
64
|
+
if (!(this.entity instanceof PolylineBase)) {
|
|
65
65
|
this.segment = null;
|
|
66
66
|
this.segmentIndex = null;
|
|
67
67
|
return true;
|
package/core/tools/cutclip.js
CHANGED
|
@@ -53,11 +53,11 @@ export class Cutclip extends Tool {
|
|
|
53
53
|
* Perform the command
|
|
54
54
|
*/
|
|
55
55
|
action() {
|
|
56
|
-
// generate clipboard data from selected
|
|
57
|
-
const
|
|
56
|
+
// generate clipboard data from selected entities
|
|
57
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
58
58
|
|
|
59
59
|
// create clipboard data
|
|
60
|
-
DesignCore.Clipboard.Entities =
|
|
60
|
+
DesignCore.Clipboard.Entities = selectedEntities;
|
|
61
61
|
// get a copy of the selection set
|
|
62
62
|
const selections = DesignCore.Scene.selectionManager.selectionSet.selectionSet.slice();
|
|
63
63
|
// create state changes for each of the selections
|
package/core/tools/explode.js
CHANGED
|
@@ -83,23 +83,23 @@ export class Explode extends Tool {
|
|
|
83
83
|
|
|
84
84
|
// check the insert has a block and
|
|
85
85
|
// check the block has items
|
|
86
|
-
if (insert.block === undefined || insert.block.
|
|
86
|
+
if (insert.block === undefined || insert.block.entities.length === 0) {
|
|
87
87
|
counter++;
|
|
88
88
|
continue;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const insertPoint = insert.points[0];
|
|
92
92
|
const block = insert.block;
|
|
93
|
-
const blockItems = block.
|
|
93
|
+
const blockItems = block.entities;
|
|
94
94
|
|
|
95
|
-
const rotation = Utils.degrees2radians(insert.rotation);
|
|
95
|
+
const rotation = Utils.degrees2radians(insert.getProperty('rotation'));
|
|
96
96
|
const origin = new Point(0, 0);
|
|
97
97
|
|
|
98
98
|
blockItems.forEach((blockItem) => {
|
|
99
|
-
const
|
|
100
|
-
const points =
|
|
101
|
-
|
|
102
|
-
const stateChange = new AddState(
|
|
99
|
+
const copyOfItem = Utils.cloneObject(blockItem);
|
|
100
|
+
const points = copyOfItem.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).rotate(origin, rotation).add(insertPoint));
|
|
101
|
+
copyOfItem.points = points;
|
|
102
|
+
const stateChange = new AddState(copyOfItem);
|
|
103
103
|
stateChanges.push(stateChange);
|
|
104
104
|
});
|
|
105
105
|
|
package/core/tools/extend.js
CHANGED
|
@@ -3,6 +3,9 @@ import { Strings } from '../lib/strings.js';
|
|
|
3
3
|
import { Tool } from './tool.js';
|
|
4
4
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
5
5
|
import { Logging } from '../lib/logging.js';
|
|
6
|
+
import { UpdateState } from '../lib/stateManager.js';
|
|
7
|
+
import { Utils } from '../lib/utils.js';
|
|
8
|
+
import { PolylineUtils } from '../lib/polylineUtils.js';
|
|
6
9
|
|
|
7
10
|
import { DesignCore } from '../designCore.js';
|
|
8
11
|
|
|
@@ -16,7 +19,7 @@ export class Extend extends Tool {
|
|
|
16
19
|
/** Create an Extend command */
|
|
17
20
|
constructor() {
|
|
18
21
|
super();
|
|
19
|
-
this.
|
|
22
|
+
this.selectedEntity = null;
|
|
20
23
|
this.selectedBoundaryItems = [];
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -45,7 +48,7 @@ export class Extend extends Tool {
|
|
|
45
48
|
if (boundary === undefined) return;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
// add all selected
|
|
51
|
+
// add all selected entities to boundary items
|
|
49
52
|
for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
|
|
50
53
|
const boundaryItem = DesignCore.Scene.entities.get(DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]);
|
|
51
54
|
this.selectedBoundaryItems.push(boundaryItem);
|
|
@@ -55,7 +58,7 @@ export class Extend extends Tool {
|
|
|
55
58
|
while (true) {
|
|
56
59
|
const selection = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
57
60
|
if (selection === undefined) break;
|
|
58
|
-
this.
|
|
61
|
+
this.selectedEntity = DesignCore.Scene.entities.get(selection.selectedEntityIndex);
|
|
59
62
|
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
60
63
|
DesignCore.Scene.inputManager.actionCommand();
|
|
61
64
|
}
|
|
@@ -70,44 +73,154 @@ export class Extend extends Tool {
|
|
|
70
73
|
* Preview the command during execution
|
|
71
74
|
*/
|
|
72
75
|
preview() {
|
|
73
|
-
|
|
76
|
+
if (!this.selectedBoundaryItems.length) return;
|
|
77
|
+
|
|
78
|
+
const index = DesignCore.Scene.selectionManager.findClosestItem(DesignCore.Mouse.pointOnScene());
|
|
79
|
+
if (index === undefined) return;
|
|
80
|
+
|
|
81
|
+
const entity = DesignCore.Scene.entities.get(index);
|
|
82
|
+
if (typeof entity.toPolylinePoints !== 'function' || typeof entity.fromPolylinePoints !== 'function') return;
|
|
83
|
+
const intersectPoints = this.#collectIntersectPoints(entity);
|
|
84
|
+
if (!intersectPoints.length) return;
|
|
85
|
+
|
|
86
|
+
const stateChanges = this.#extendEntity(entity, intersectPoints);
|
|
87
|
+
if (!stateChanges?.length) return;
|
|
88
|
+
|
|
89
|
+
for (const change of stateChanges) {
|
|
90
|
+
if (change instanceof UpdateState) {
|
|
91
|
+
const extended = Utils.cloneObject(entity);
|
|
92
|
+
extended.setProperty('points', change.properties.points);
|
|
93
|
+
DesignCore.Scene.previewEntities.add(extended);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
/**
|
|
77
99
|
* Perform the command
|
|
78
100
|
*/
|
|
79
101
|
action() {
|
|
80
|
-
if (this.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const intersect = Intersection.intersectPolylinePolyline(boundaryItem.toPolylinePoints(), this.selectedItem.toPolylinePoints(), true);
|
|
87
|
-
if (intersect.points.length) {
|
|
88
|
-
for (let point = 0; point < intersect.points.length; point++) {
|
|
89
|
-
intersectPoints.push(intersect.points[point]);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
Logging.instance.warn(`${this.constructor.name}: Error intersecting between ${boundaryItem.type} and ${this.selectedItem.type}`);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
102
|
+
if (this.selectedEntity && this.selectedBoundaryItems.length) {
|
|
103
|
+
if (typeof this.selectedEntity.fromPolylinePoints !== 'function') {
|
|
104
|
+
DesignCore.Core.notify(`${this.type} - ${this.selectedEntity.type} ${Strings.Message.NOEXTEND}`);
|
|
105
|
+
this.selectedEntity = null;
|
|
106
|
+
return;
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
const intersectPoints = this.#collectIntersectPoints(this.selectedEntity);
|
|
110
|
+
|
|
99
111
|
if (intersectPoints.length) {
|
|
100
|
-
const stateChanges = this.
|
|
112
|
+
const stateChanges = this.#extendEntity(this.selectedEntity, intersectPoints);
|
|
101
113
|
if (stateChanges?.length) {
|
|
102
114
|
DesignCore.Scene.commit(stateChanges);
|
|
115
|
+
} else {
|
|
116
|
+
DesignCore.Core.notify(`${this.type} - ${this.selectedEntity.type} ${Strings.Message.NOEXTEND}`);
|
|
103
117
|
}
|
|
104
118
|
} else {
|
|
105
|
-
DesignCore.Core.notify(`${this.type} - ${this.
|
|
119
|
+
DesignCore.Core.notify(`${this.type} - ${this.selectedEntity.type} ${Strings.Message.NOEXTEND}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.selectedEntity = null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Collect all intersection points between the boundary items and the given entity.
|
|
128
|
+
* Unsupported entity combinations are silently skipped.
|
|
129
|
+
* @param {Object} entity
|
|
130
|
+
* @return {Array}
|
|
131
|
+
*/
|
|
132
|
+
#collectIntersectPoints(entity) {
|
|
133
|
+
const intersectPoints = [];
|
|
134
|
+
for (const boundaryItem of this.selectedBoundaryItems) {
|
|
135
|
+
if (boundaryItem === entity) continue;
|
|
136
|
+
try {
|
|
137
|
+
const intersect = Intersection.intersectPolylinePolyline(boundaryItem.toPolylinePoints(), entity.toPolylinePoints(), true);
|
|
138
|
+
intersectPoints.push(...intersect.points);
|
|
139
|
+
} catch {
|
|
140
|
+
// skip unsupported entity combinations
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return intersectPoints;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Compute a state change to extend entity to the nearest valid boundary
|
|
148
|
+
* intersection. Operates entirely in polyline-point space via
|
|
149
|
+
* entity.toPolylinePoints() / entity.fromPolylinePoints(), so it works for
|
|
150
|
+
* any entity that implements that protocol (Line, PolylineBase, and future
|
|
151
|
+
* entities such as Spline or Ellipse).
|
|
152
|
+
* Only straight end-segments (bulge === 0) can be extended.
|
|
153
|
+
* @param {Object} entity
|
|
154
|
+
* @param {Array} intersectPoints
|
|
155
|
+
* @return {Array}
|
|
156
|
+
*/
|
|
157
|
+
#extendEntity(entity, intersectPoints) {
|
|
158
|
+
if (!intersectPoints?.length) return [];
|
|
159
|
+
|
|
160
|
+
const polyPoints = entity.toPolylinePoints();
|
|
161
|
+
|
|
162
|
+
// Closed entities have no open endpoints to extend
|
|
163
|
+
if (polyPoints[0].isSame(polyPoints.at(-1))) return [];
|
|
164
|
+
|
|
165
|
+
const mousePosition = DesignCore.Mouse.pointOnScene();
|
|
166
|
+
const lastIndex = polyPoints.length - 1;
|
|
167
|
+
|
|
168
|
+
// Determine which end segment is closest to the mouse
|
|
169
|
+
const closestOnFirst = PolylineUtils.closestPointOnSegment(polyPoints, mousePosition, 1);
|
|
170
|
+
const closestOnLast = PolylineUtils.closestPointOnSegment(polyPoints, mousePosition, lastIndex);
|
|
171
|
+
const distToFirst = mousePosition.distance(closestOnFirst);
|
|
172
|
+
const distToLast = mousePosition.distance(closestOnLast);
|
|
173
|
+
|
|
174
|
+
let endPointIndex;
|
|
175
|
+
let endSegmentBulge;
|
|
176
|
+
if (distToFirst < distToLast) {
|
|
177
|
+
endPointIndex = 0;
|
|
178
|
+
endSegmentBulge = polyPoints[0].bulge ?? 0;
|
|
179
|
+
} else if (distToLast < distToFirst) {
|
|
180
|
+
endPointIndex = lastIndex;
|
|
181
|
+
endSegmentBulge = polyPoints[lastIndex - 1].bulge ?? 0;
|
|
182
|
+
} else {
|
|
183
|
+
if (mousePosition.distance(polyPoints[0]) <= mousePosition.distance(polyPoints[lastIndex])) {
|
|
184
|
+
endPointIndex = 0;
|
|
185
|
+
endSegmentBulge = polyPoints[0].bulge ?? 0;
|
|
186
|
+
} else {
|
|
187
|
+
endPointIndex = lastIndex;
|
|
188
|
+
endSegmentBulge = polyPoints[lastIndex - 1].bulge ?? 0;
|
|
106
189
|
}
|
|
107
190
|
}
|
|
108
191
|
|
|
109
|
-
//
|
|
110
|
-
|
|
192
|
+
// Only straight end-segments can be extended
|
|
193
|
+
if (endSegmentBulge !== 0) return [];
|
|
194
|
+
|
|
195
|
+
const endPoint = polyPoints[endPointIndex];
|
|
196
|
+
const adjacentPoint = endPointIndex === 0 ? polyPoints[1] : polyPoints[lastIndex - 1];
|
|
197
|
+
|
|
198
|
+
Utils.sortPointsByDistance(intersectPoints, endPoint);
|
|
199
|
+
|
|
200
|
+
const direction = endPoint.subtract(adjacentPoint);
|
|
201
|
+
const newEndPoint = intersectPoints.find((p) => {
|
|
202
|
+
// Ignore the current end point itself
|
|
203
|
+
if (p.isSame(endPoint)) return false;
|
|
204
|
+
// Point must be ahead of the end point in the extension direction
|
|
205
|
+
if (p.subtract(endPoint).dot(direction) <= 0) return false;
|
|
206
|
+
// Point must be further from the adjacent point than the end point is (i.e. actually extends the segment)
|
|
207
|
+
if (adjacentPoint.distance(p) <= adjacentPoint.distance(endPoint)) return false;
|
|
208
|
+
// Point must lie on the extension line of the end segment; intersections from internal
|
|
209
|
+
// segments extended as infinite lines will have a non-zero cross product with direction
|
|
210
|
+
if (Utils.round(direction.cross(p.subtract(adjacentPoint))) !== 0) return false;
|
|
211
|
+
return true;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (!newEndPoint) return [];
|
|
215
|
+
|
|
216
|
+
const newPolyPoints = polyPoints.map((p) => p.clone());
|
|
217
|
+
newPolyPoints[endPointIndex] = newEndPoint.clone();
|
|
218
|
+
|
|
219
|
+
if (newPolyPoints[endPointIndex].isSame(polyPoints[endPointIndex])) return [];
|
|
220
|
+
|
|
221
|
+
const clone = Utils.cloneObject(entity);
|
|
222
|
+
const output = clone.fromPolylinePoints(newPolyPoints);
|
|
223
|
+
return [new UpdateState(entity, { points: output.points })];
|
|
111
224
|
}
|
|
112
225
|
}
|
|
113
226
|
|
package/core/tools/fillet.js
CHANGED
|
@@ -3,9 +3,11 @@ import { ChamferFilletBase } from './chamferFilletBase.js';
|
|
|
3
3
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
4
4
|
import { Logging } from '../lib/logging.js';
|
|
5
5
|
import { Constants } from '../lib/constants.js';
|
|
6
|
+
import { Utils } from '../lib/utils.js';
|
|
6
7
|
import { Line } from '../entities/line.js';
|
|
7
|
-
import {
|
|
8
|
-
import { AddState
|
|
8
|
+
import { PolylineBase } from '../entities/polylineBase.js';
|
|
9
|
+
import { AddState } from '../lib/stateManager.js';
|
|
10
|
+
import { Colour } from '../lib/colour.js';
|
|
9
11
|
|
|
10
12
|
import { DesignCore } from '../designCore.js';
|
|
11
13
|
|
|
@@ -63,9 +65,9 @@ export class Fillet extends ChamferFilletBase {
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// input1 is a selection — validate and store first entity
|
|
66
|
-
const firstEntity = DesignCore.Scene.entities.get(input1.
|
|
68
|
+
const firstEntity = DesignCore.Scene.entities.get(input1.selectedEntityIndex);
|
|
67
69
|
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
68
|
-
if (!(firstEntity instanceof Line) && !(firstEntity instanceof
|
|
70
|
+
if (!(firstEntity instanceof Line) && !(firstEntity instanceof PolylineBase)) {
|
|
69
71
|
DesignCore.Core.notify(`${this.type} - ${firstEntity.type} ${Strings.Message.NOFILLET}`);
|
|
70
72
|
continue;
|
|
71
73
|
}
|
|
@@ -77,16 +79,16 @@ export class Fillet extends ChamferFilletBase {
|
|
|
77
79
|
const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
78
80
|
if (input2 === undefined) return;
|
|
79
81
|
|
|
80
|
-
const candidate = DesignCore.Scene.entities.get(input2.
|
|
82
|
+
const candidate = DesignCore.Scene.entities.get(input2.selectedEntityIndex);
|
|
81
83
|
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
82
|
-
if (!(candidate instanceof Line) && !(candidate instanceof
|
|
84
|
+
if (!(candidate instanceof Line) && !(candidate instanceof PolylineBase)) {
|
|
83
85
|
DesignCore.Core.notify(`${this.type} - ${candidate.type} ${Strings.Message.NOFILLET}`);
|
|
84
86
|
continue;
|
|
85
87
|
}
|
|
86
88
|
if (!this.secondPick.setPick(candidate, input2.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOFILLET}`)) continue;
|
|
87
89
|
// If both selections are the same polyline, segments must be consecutive
|
|
88
90
|
// or they must be the open-end segments of an open polyline.
|
|
89
|
-
if (candidate === firstEntity && firstEntity instanceof
|
|
91
|
+
if (candidate === firstEntity && firstEntity instanceof PolylineBase) {
|
|
90
92
|
const isConsecutive = firstEntity.areConsecutiveSegments(this.firstPick.segmentIndex, this.secondPick.segmentIndex);
|
|
91
93
|
const lastIdx = firstEntity.points.length - 1;
|
|
92
94
|
const isOpenEnds = !firstEntity.flags.hasFlag(1) &&
|
|
@@ -113,7 +115,43 @@ export class Fillet extends ChamferFilletBase {
|
|
|
113
115
|
* Preview the command during execution
|
|
114
116
|
*/
|
|
115
117
|
preview() {
|
|
116
|
-
|
|
118
|
+
const selection = this.validateSelection();
|
|
119
|
+
if (!selection) return;
|
|
120
|
+
|
|
121
|
+
const filletRadius = DesignCore.Scene.headers.filletRadius;
|
|
122
|
+
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
123
|
+
|
|
124
|
+
const geo = this.#computeFillet(this.firstPick, selection.tempSecond, selection.intersectionPoint, filletRadius);
|
|
125
|
+
if (!geo) return;
|
|
126
|
+
if (trimMode && !geo.tangentsInBounds) return;
|
|
127
|
+
|
|
128
|
+
if (!trimMode) {
|
|
129
|
+
if (geo.arc) DesignCore.Scene.previewEntities.add(geo.arc);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// trimMode: dull the original segments then show the shortened versions + arc
|
|
134
|
+
const firstSeg = Utils.cloneObject(this.firstPick.activeSeg);
|
|
135
|
+
firstSeg.setProperty('colour', Colour.blend(this.firstPick.entity.getDrawColour(), DesignCore.Settings.canvasbackgroundcolour, DesignCore.Settings.previewBlendFactor));
|
|
136
|
+
DesignCore.Scene.previewEntities.add(firstSeg);
|
|
137
|
+
|
|
138
|
+
const secondSeg = Utils.cloneObject(selection.tempSecond.activeSeg);
|
|
139
|
+
secondSeg.setProperty('colour', Colour.blend(selection.tempSecond.entity.getDrawColour(), DesignCore.Settings.canvasbackgroundcolour, DesignCore.Settings.previewBlendFactor));
|
|
140
|
+
DesignCore.Scene.previewEntities.add(secondSeg);
|
|
141
|
+
|
|
142
|
+
const firstTrimLine = DesignCore.CommandManager.createNew('Line', {
|
|
143
|
+
points: [this.firstPick.lineKeptEnd(selection.intersectionPoint), geo.firstTangentPoint],
|
|
144
|
+
...Utils.cloneProperties(this.firstPick.entity),
|
|
145
|
+
});
|
|
146
|
+
DesignCore.Scene.previewEntities.add(firstTrimLine);
|
|
147
|
+
|
|
148
|
+
const secondTrimLine = DesignCore.CommandManager.createNew('Line', {
|
|
149
|
+
points: [selection.tempSecond.lineKeptEnd(selection.intersectionPoint), geo.secondTangentPoint],
|
|
150
|
+
...Utils.cloneProperties(selection.candidate),
|
|
151
|
+
});
|
|
152
|
+
DesignCore.Scene.previewEntities.add(secondTrimLine);
|
|
153
|
+
|
|
154
|
+
if (geo.arc) DesignCore.Scene.previewEntities.add(geo.arc);
|
|
117
155
|
}
|
|
118
156
|
|
|
119
157
|
/**
|
|
@@ -126,205 +164,70 @@ export class Fillet extends ChamferFilletBase {
|
|
|
126
164
|
// radius = 0: trim/extend both lines to the sharp intersection with no arc
|
|
127
165
|
const filletRadius = DesignCore.Scene.headers.filletRadius;
|
|
128
166
|
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const stateChanges = this.applySharpTrim();
|
|
132
|
-
DesignCore.Scene.commit(stateChanges);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Angle between the two clicked-side direction vectors (i.e. the opening angle of the chosen corner)
|
|
138
|
-
const firstUnit = this.firstPick.clickUnit(this.intersectionPoint);
|
|
139
|
-
const secondUnit = this.secondPick.clickUnit(this.intersectionPoint);
|
|
140
|
-
const cosAngle = Math.min(1, Math.max(-1, firstUnit.dot(secondUnit)));
|
|
141
|
-
const cornerAngle = Math.acos(cosAngle);
|
|
142
|
-
|
|
143
|
-
// Collinear or antiparallel lines cannot be filleted
|
|
144
|
-
if (cornerAngle < Constants.Tolerance.EPSILON || cornerAngle > Math.PI - Constants.Tolerance.EPSILON) {
|
|
167
|
+
const geo = this.#computeFillet(this.firstPick, this.secondPick, this.intersectionPoint, filletRadius);
|
|
168
|
+
if (!geo) {
|
|
145
169
|
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
|
|
146
170
|
return;
|
|
147
171
|
}
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Distance from the intersection point to the fillet centre along the bisector
|
|
153
|
-
const intersectToCentreDistance = filletRadius / Math.sin(cornerAngle / 2);
|
|
154
|
-
|
|
155
|
-
// Fillet arc centre point
|
|
156
|
-
const arcCentre = this.intersectionPoint.add(bisectorUnit.scale(intersectToCentreDistance));
|
|
157
|
-
|
|
158
|
-
// Tangent points where the fillet arc meets each line (foot of perpendicular from centre to line)
|
|
159
|
-
const firstTangentPoint = arcCentre.perpendicular(this.firstPick.lineStart, this.firstPick.lineEnd);
|
|
160
|
-
const secondTangentPoint = arcCentre.perpendicular(this.secondPick.lineStart, this.secondPick.lineEnd);
|
|
161
|
-
|
|
162
|
-
// Verify the tangent points are reachable — same direction as the kept end from the
|
|
163
|
-
// intersection and not farther than the kept end (which would flip the line direction).
|
|
164
|
-
if (trimMode) {
|
|
165
|
-
const firstKeptDir = this.firstPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
|
|
166
|
-
const secondKeptDir = this.secondPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
|
|
167
|
-
const firstTangentDot = firstTangentPoint.subtract(this.intersectionPoint).dot(firstKeptDir);
|
|
168
|
-
const secondTangentDot = secondTangentPoint.subtract(this.intersectionPoint).dot(secondKeptDir);
|
|
169
|
-
if (firstTangentDot < 0 || firstTangentDot > firstKeptDir.dot(firstKeptDir) ||
|
|
170
|
-
secondTangentDot < 0 || secondTangentDot > secondKeptDir.dot(secondKeptDir)) {
|
|
171
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.RADIUSTOOLARGE}`);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
173
|
+
if (trimMode && !geo.tangentsInBounds) {
|
|
174
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.RADIUSTOOLARGE}`);
|
|
175
|
+
return;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
// Determine arc winding direction: if the cross product of the two centre-to-tangent
|
|
177
|
-
// vectors is positive, the short arc from firstTangentPoint to secondTangentPoint
|
|
178
|
-
// travels CCW (direction = 1), otherwise CW (direction = -1).
|
|
179
|
-
const centreToFirst = firstTangentPoint.subtract(arcCentre);
|
|
180
|
-
const centreToSecond = secondTangentPoint.subtract(arcCentre);
|
|
181
|
-
const windingCross = centreToFirst.cross(centreToSecond);
|
|
182
|
-
const arcDirection = windingCross > 0 ? 1 : -1;
|
|
183
|
-
|
|
184
|
-
const arc = DesignCore.CommandManager.createNew('Arc', {
|
|
185
|
-
points: [arcCentre, firstTangentPoint, secondTangentPoint],
|
|
186
|
-
direction: arcDirection,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
|
|
190
|
-
const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
|
|
191
|
-
|
|
192
178
|
if (!trimMode) {
|
|
193
|
-
DesignCore.Scene.commit([new AddState(arc)]);
|
|
179
|
+
if (geo.arc) DesignCore.Scene.commit([new AddState(geo.arc)]);
|
|
194
180
|
return;
|
|
195
181
|
}
|
|
196
182
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
DesignCore.Scene.commit(stateChanges);
|
|
203
|
-
} else {
|
|
204
|
-
const stateChanges = this.#trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre);
|
|
205
|
-
DesignCore.Scene.commit(stateChanges);
|
|
206
|
-
}
|
|
183
|
+
const [firstCornerPoint, secondCornerPoint] = geo.arc ?
|
|
184
|
+
geo.arc.toPolylinePoints() :
|
|
185
|
+
[this.intersectionPoint, this.intersectionPoint];
|
|
186
|
+
const stateChanges = this.applyCornerTrim(firstCornerPoint, secondCornerPoint, geo.arc ?? null);
|
|
187
|
+
DesignCore.Scene.commit(stateChanges);
|
|
207
188
|
}
|
|
208
189
|
|
|
209
190
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* @param {
|
|
214
|
-
* @
|
|
191
|
+
* Compute the fillet geometry for two picks meeting at intersectionPoint.
|
|
192
|
+
* Returns null if the lines are collinear or antiparallel.
|
|
193
|
+
* When filletRadius is 0, returns arc: null with both tangent points at the intersection.
|
|
194
|
+
* @param {CornerEntity} firstPick
|
|
195
|
+
* @param {CornerEntity} secondPick
|
|
196
|
+
* @param {Point} intersectionPoint
|
|
197
|
+
* @param {number} filletRadius
|
|
198
|
+
* @return {{arc, firstTangentPoint, secondTangentPoint, tangentsInBounds}|null}
|
|
215
199
|
*/
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstTangentPoint] }),
|
|
220
|
-
new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondTangentPoint] }),
|
|
221
|
-
];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Apply trim and arc for a Polyline + Polyline (same entity) fillet.
|
|
226
|
-
* Handles both the open-ends case and the consecutive-segments case.
|
|
227
|
-
* @param {Point} firstTangentPoint
|
|
228
|
-
* @param {Point} secondTangentPoint
|
|
229
|
-
* @param {number} arcDirection
|
|
230
|
-
* @param {Point} arcCentre
|
|
231
|
-
* @param {Arc} arc
|
|
232
|
-
* @return {Array}
|
|
233
|
-
*/
|
|
234
|
-
#trimPolyAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre, arc) {
|
|
235
|
-
const closeSegIdx = this.firstPick.entity.points.length;
|
|
236
|
-
const lastIdx = closeSegIdx - 1;
|
|
237
|
-
const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
|
|
238
|
-
const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
|
|
239
|
-
(this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
|
|
240
|
-
(this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
|
|
241
|
-
);
|
|
242
|
-
const newPoints = this.firstPick.entity.points.map((p) => p.clone());
|
|
243
|
-
const stateChanges = [];
|
|
244
|
-
|
|
245
|
-
if (isOpenEnds) {
|
|
246
|
-
stateChanges.push(new AddState(arc));
|
|
247
|
-
if (this.firstPick.segmentIndex === 1) {
|
|
248
|
-
newPoints[0] = firstTangentPoint.clone();
|
|
249
|
-
newPoints[lastIdx] = secondTangentPoint.clone();
|
|
250
|
-
} else {
|
|
251
|
-
newPoints[0] = secondTangentPoint.clone();
|
|
252
|
-
newPoints[lastIdx] = firstTangentPoint.clone();
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
const seg1 = this.firstPick.segmentIndex;
|
|
256
|
-
const seg2 = this.secondPick.segmentIndex;
|
|
257
|
-
const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
|
|
258
|
-
const isFirstLower = seg1 < seg2;
|
|
259
|
-
const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
|
|
260
|
-
const lowerTangent = (isClosingWrap ? seg1 !== closeSegIdx : !isFirstLower) ? secondTangentPoint : firstTangentPoint;
|
|
261
|
-
const upperTangent = (isClosingWrap ? seg1 !== closeSegIdx : !isFirstLower) ? firstTangentPoint : secondTangentPoint;
|
|
262
|
-
const polyArcDir = (isClosingWrap ? seg1 !== closeSegIdx : !isFirstLower) ? -arcDirection : arcDirection;
|
|
263
|
-
const startAngle = arcCentre.angle(lowerTangent);
|
|
264
|
-
const endAngle = arcCentre.angle(upperTangent);
|
|
265
|
-
const includedAngle = ((endAngle - startAngle) * polyArcDir + 4 * Math.PI) % (2 * Math.PI);
|
|
266
|
-
const bulge = polyArcDir * Math.tan(includedAngle / 4);
|
|
267
|
-
const arcStartPoint = lowerTangent.clone();
|
|
268
|
-
arcStartPoint.bulge = bulge;
|
|
269
|
-
newPoints.splice(cornerIdx, 1, arcStartPoint, upperTangent.clone());
|
|
200
|
+
#computeFillet(firstPick, secondPick, intersectionPoint, filletRadius) {
|
|
201
|
+
if (filletRadius === 0) {
|
|
202
|
+
return { arc: null, firstTangentPoint: intersectionPoint, secondTangentPoint: intersectionPoint, tangentsInBounds: true };
|
|
270
203
|
}
|
|
271
204
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* Apply trim and arc for a Line + Polyline (or Polyline + Line) fillet.
|
|
278
|
-
* The line is consumed into the polyline with the arc encoded as a bulge.
|
|
279
|
-
* @param {Point} firstTangentPoint
|
|
280
|
-
* @param {Point} secondTangentPoint
|
|
281
|
-
* @param {number} arcDirection
|
|
282
|
-
* @param {Point} arcCentre
|
|
283
|
-
* @return {Array}
|
|
284
|
-
*/
|
|
285
|
-
#trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre) {
|
|
286
|
-
const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
|
|
287
|
-
const [poly, line] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
|
|
288
|
-
const lineTangentPoint = line === this.firstPick ? firstTangentPoint : secondTangentPoint;
|
|
289
|
-
const polyTangentPoint = poly === this.firstPick ? firstTangentPoint : secondTangentPoint;
|
|
290
|
-
const polyToLineDir = firstIsPolyline ? arcDirection : -arcDirection;
|
|
291
|
-
|
|
292
|
-
const startAngle = arcCentre.angle(polyTangentPoint);
|
|
293
|
-
const endAngle = arcCentre.angle(lineTangentPoint);
|
|
294
|
-
const includedAngle = ((endAngle - startAngle) * polyToLineDir + 4 * Math.PI) % (2 * Math.PI);
|
|
295
|
-
const bulge = polyToLineDir * Math.tan(includedAngle / 4);
|
|
296
|
-
const arcStartPoint = polyTangentPoint.clone();
|
|
297
|
-
arcStartPoint.bulge = bulge;
|
|
205
|
+
const firstUnit = firstPick.clickUnit(intersectionPoint);
|
|
206
|
+
const secondUnit = secondPick.clickUnit(intersectionPoint);
|
|
207
|
+
const cosAngle = Math.min(1, Math.max(-1, firstUnit.dot(secondUnit)));
|
|
208
|
+
const cornerAngle = Math.acos(cosAngle);
|
|
209
|
+
if (cornerAngle < Constants.Tolerance.EPSILON || cornerAngle > Math.PI - Constants.Tolerance.EPSILON) return null;
|
|
298
210
|
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
line.lineKeptEnd(this.intersectionPoint).clone(),
|
|
318
|
-
revArcStartPoint,
|
|
319
|
-
polyTangentPoint.clone(),
|
|
320
|
-
...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
|
|
321
|
-
];
|
|
322
|
-
}
|
|
211
|
+
const bisectorUnit = firstUnit.add(secondUnit).normalise();
|
|
212
|
+
const arcCentre = intersectionPoint.add(bisectorUnit.scale(filletRadius / Math.sin(cornerAngle / 2)));
|
|
213
|
+
const firstTangentPoint = arcCentre.perpendicular(firstPick.lineStart, firstPick.lineEnd);
|
|
214
|
+
const secondTangentPoint = arcCentre.perpendicular(secondPick.lineStart, secondPick.lineEnd);
|
|
215
|
+
|
|
216
|
+
const firstKeptDir = firstPick.lineKeptEnd(intersectionPoint).subtract(intersectionPoint);
|
|
217
|
+
const secondKeptDir = secondPick.lineKeptEnd(intersectionPoint).subtract(intersectionPoint);
|
|
218
|
+
const firstDot = firstTangentPoint.subtract(intersectionPoint).dot(firstKeptDir);
|
|
219
|
+
const secondDot = secondTangentPoint.subtract(intersectionPoint).dot(secondKeptDir);
|
|
220
|
+
const tangentsInBounds = firstDot >= 0 && firstDot <= firstKeptDir.dot(firstKeptDir) &&
|
|
221
|
+
secondDot >= 0 && secondDot <= secondKeptDir.dot(secondKeptDir);
|
|
222
|
+
|
|
223
|
+
const windingCross = firstTangentPoint.subtract(arcCentre).cross(secondTangentPoint.subtract(arcCentre));
|
|
224
|
+
const arc = DesignCore.CommandManager.createNew('Arc', {
|
|
225
|
+
points: [arcCentre, firstTangentPoint, secondTangentPoint],
|
|
226
|
+
direction: windingCross > 0 ? 1 : -1,
|
|
227
|
+
...Utils.cloneProperties(firstPick.entity),
|
|
228
|
+
});
|
|
323
229
|
|
|
324
|
-
return
|
|
325
|
-
new RemoveState(line.entity),
|
|
326
|
-
new UpdateState(poly.entity, { points: newPoints }),
|
|
327
|
-
];
|
|
230
|
+
return { arc, firstTangentPoint, secondTangentPoint, tangentsInBounds };
|
|
328
231
|
}
|
|
329
232
|
|
|
330
233
|
/**
|