@design-core/core 0.0.6 → 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 +30 -282
- 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 +35 -11
- 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 +68 -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 +181 -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
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
|
|
|
@@ -70,7 +73,26 @@ 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
|
/**
|
|
@@ -78,36 +100,127 @@ export class Extend extends Tool {
|
|
|
78
100
|
*/
|
|
79
101
|
action() {
|
|
80
102
|
if (this.selectedItem && this.selectedBoundaryItems.length) {
|
|
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
|
-
}
|
|
103
|
+
if (typeof this.selectedItem.fromPolylinePoints !== 'function') {
|
|
104
|
+
DesignCore.Core.notify(`${this.type} - ${this.selectedItem.type} ${Strings.Message.NOEXTEND}`);
|
|
105
|
+
this.selectedItem = null;
|
|
106
|
+
return;
|
|
97
107
|
}
|
|
98
108
|
|
|
109
|
+
const intersectPoints = this.#collectIntersectPoints(this.selectedItem);
|
|
110
|
+
|
|
99
111
|
if (intersectPoints.length) {
|
|
100
|
-
const stateChanges = this.selectedItem
|
|
112
|
+
const stateChanges = this.#extendEntity(this.selectedItem, intersectPoints);
|
|
101
113
|
if (stateChanges?.length) {
|
|
102
114
|
DesignCore.Scene.commit(stateChanges);
|
|
115
|
+
} else {
|
|
116
|
+
DesignCore.Core.notify(`${this.type} - ${this.selectedItem.type} ${Strings.Message.NOEXTEND}`);
|
|
103
117
|
}
|
|
104
118
|
} else {
|
|
105
119
|
DesignCore.Core.notify(`${this.type} - ${this.selectedItem.type} ${Strings.Message.NOEXTEND}`);
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
// reset selected item
|
|
110
123
|
this.selectedItem = null;
|
|
111
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, BasePolyline, 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;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
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 })];
|
|
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
8
|
import { BasePolyline } from '../entities/basePolyline.js';
|
|
8
|
-
import { AddState
|
|
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
|
|
|
@@ -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
|
/**
|
package/core/tools/matchProp.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Tool } from './tool.js';
|
|
|
2
2
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
3
3
|
import { Strings } from '../lib/strings.js';
|
|
4
4
|
import { Logging } from '../lib/logging.js';
|
|
5
|
+
import { Utils } from '../lib/utils.js';
|
|
5
6
|
import { UpdateState } from '../lib/stateManager.js';
|
|
6
7
|
|
|
7
8
|
import { DesignCore } from '../designCore.js';
|
|
@@ -21,8 +22,6 @@ export class MatchProp extends Tool {
|
|
|
21
22
|
this.sourceIndex = null;
|
|
22
23
|
// array of destination item indices
|
|
23
24
|
this.destinationSetIndices = [];
|
|
24
|
-
// properties to match
|
|
25
|
-
this.properties = ['colour', 'layer', 'lineType', 'lineWidth', 'height', 'styleName', 'dimensionStyle'];
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -76,21 +75,13 @@ export class MatchProp extends Tool {
|
|
|
76
75
|
|
|
77
76
|
// get source item
|
|
78
77
|
const sourceItem = DesignCore.Scene.entities.get(this.sourceIndex);
|
|
78
|
+
const propertySet = Utils.cloneProperties(sourceItem);
|
|
79
79
|
|
|
80
80
|
const stateChanges = [];
|
|
81
81
|
|
|
82
82
|
// loop through destination set
|
|
83
83
|
for (let i = 0; i < this.destinationSetIndices.length; i++) {
|
|
84
84
|
const targetItem = DesignCore.Scene.entities.get(this.destinationSetIndices[i]);
|
|
85
|
-
const propertySet = {};
|
|
86
|
-
// loop through properties and match
|
|
87
|
-
for (let p = 0; p < this.properties.length; p++) {
|
|
88
|
-
// check property exists on both items
|
|
89
|
-
const prop = this.properties[p];
|
|
90
|
-
if (sourceItem.hasOwnProperty(prop)) {
|
|
91
|
-
propertySet[prop] = sourceItem[prop];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
85
|
|
|
95
86
|
// only add state change if there are properties to change
|
|
96
87
|
if (Object.keys(propertySet).length > 0) {
|
package/core/tools/mirror.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Point } from '../entities/point.js';
|
|
|
7
7
|
import { AddState, UpdateState } from '../lib/stateManager.js';
|
|
8
8
|
|
|
9
9
|
import { DesignCore } from '../designCore.js';
|
|
10
|
+
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Mirror Command Class
|
|
@@ -74,8 +75,8 @@ export class Mirror extends Tool {
|
|
|
74
75
|
const pt1 = this.points[0];
|
|
75
76
|
const pt2 = this.points.length >= 2 ? this.points[1] : mousePoint;
|
|
76
77
|
|
|
77
|
-
// Draw the mirror line
|
|
78
|
-
DesignCore.Scene.
|
|
78
|
+
// Draw the mirror line as a rubber-band indicator
|
|
79
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([pt1, pt2]));
|
|
79
80
|
|
|
80
81
|
// Preview mirrored entities (skip if mirror line has zero length)
|
|
81
82
|
if (!pt1.isSame(pt2)) {
|
|
@@ -84,8 +85,8 @@ export class Mirror extends Tool {
|
|
|
84
85
|
const entityIndex = DesignCore.Scene.selectionManager.selectionSet.selectionSet[i];
|
|
85
86
|
const originalItem = DesignCore.Scene.entities.get(entityIndex);
|
|
86
87
|
item.setProperty('points', this.getMirroredPoints(originalItem.points, pt1, pt2));
|
|
87
|
-
if (originalItem.direction
|
|
88
|
-
item.setProperty('direction', -originalItem.direction);
|
|
88
|
+
if (originalItem.properties?.has('direction')) {
|
|
89
|
+
item.setProperty('direction', -originalItem.getProperty('direction'));
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -114,14 +115,14 @@ export class Mirror extends Tool {
|
|
|
114
115
|
if (this.eraseSource) {
|
|
115
116
|
// Replace the original with its mirrored version
|
|
116
117
|
const updates = { points: mirroredPoints };
|
|
117
|
-
if (item.direction
|
|
118
|
+
if (item.properties?.has('direction')) updates.direction = -item.getProperty('direction');
|
|
118
119
|
const stateChange = new UpdateState(item, updates);
|
|
119
120
|
stateChanges.push(stateChange);
|
|
120
121
|
} else {
|
|
121
122
|
// Keep the original and add a mirrored copy
|
|
122
123
|
const copyOfItem = Utils.cloneObject(item);
|
|
123
124
|
copyOfItem.setProperty('points', mirroredPoints);
|
|
124
|
-
if (copyOfItem.direction
|
|
125
|
+
if (copyOfItem.properties?.has('direction')) copyOfItem.setProperty('direction', -copyOfItem.getProperty('direction'));
|
|
125
126
|
const stateChange = new AddState(copyOfItem);
|
|
126
127
|
stateChanges.push(stateChange);
|
|
127
128
|
}
|
package/core/tools/move.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Point } from '../entities/point.js';
|
|
|
6
6
|
|
|
7
7
|
import { DesignCore } from '../designCore.js';
|
|
8
8
|
import { UpdateState } from '../lib/stateManager.js';
|
|
9
|
+
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Move Command Class
|
|
@@ -66,9 +67,8 @@ export class Move extends Tool {
|
|
|
66
67
|
if (this.points.length >= 1) {
|
|
67
68
|
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
68
69
|
|
|
69
|
-
// Draw a line
|
|
70
|
-
|
|
71
|
-
DesignCore.Scene.tempEntities.create('Line', { points: points });
|
|
70
|
+
// Draw a rubber-band line from base point to mouse
|
|
71
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([this.points.at(-1), mousePoint]));
|
|
72
72
|
|
|
73
73
|
// Get the delta from the last mouse point
|
|
74
74
|
const delta = mousePoint.subtract(this.lastMousePoint || this.points[0]);
|
package/core/tools/offset.js
CHANGED
|
@@ -107,9 +107,8 @@ export class Offset extends Tool {
|
|
|
107
107
|
const offsetPoints = this.getOffsetPoints(this.selectedItem, mousePoint, offsetDistance);
|
|
108
108
|
|
|
109
109
|
if (offsetPoints) {
|
|
110
|
-
const previewEntity = Utils.cloneObject(this.selectedItem);
|
|
111
|
-
|
|
112
|
-
DesignCore.Scene.tempEntities.add(previewEntity);
|
|
110
|
+
const previewEntity = Utils.cloneObject(this.selectedItem).fromPolylinePoints(offsetPoints);
|
|
111
|
+
DesignCore.Scene.previewEntities.add(previewEntity);
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
}
|
|
@@ -130,8 +129,7 @@ export class Offset extends Tool {
|
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
const offsetEntity = Utils.cloneObject(entity);
|
|
134
|
-
offsetEntity.fromPolylinePoints(offsetPoints);
|
|
132
|
+
const offsetEntity = Utils.cloneObject(entity).fromPolylinePoints(offsetPoints);
|
|
135
133
|
|
|
136
134
|
const stateChange = new AddState(offsetEntity);
|
|
137
135
|
DesignCore.Scene.commit([stateChange]);
|
package/core/tools/pasteclip.js
CHANGED
|
@@ -66,7 +66,7 @@ export class Pasteclip extends Tool {
|
|
|
66
66
|
const tempEntity = Utils.cloneObject(entity);
|
|
67
67
|
const offsetPoints = tempEntity.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
|
|
68
68
|
tempEntity.setProperty('points', offsetPoints);
|
|
69
|
-
DesignCore.Scene.
|
|
69
|
+
DesignCore.Scene.previewEntities.add(tempEntity);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
package/core/tools/rotate.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Point } from '../entities/point.js';
|
|
|
7
7
|
import { UpdateState } from '../lib/stateManager.js';
|
|
8
8
|
|
|
9
9
|
import { DesignCore } from '../designCore.js';
|
|
10
|
+
import { RubberBand } from '../lib/auxiliary/rubberBand.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Rotate Command Class
|
|
@@ -72,6 +73,9 @@ export class Rotate extends Tool {
|
|
|
72
73
|
this.baseAngle = refBase.angle(refEnd);
|
|
73
74
|
// set the base point to null
|
|
74
75
|
this.basePoint = null;
|
|
76
|
+
// restore inputPoint to the rotation center so the tracking line
|
|
77
|
+
// uses the correct origin for the final angle input
|
|
78
|
+
DesignCore.Scene.inputManager.inputPoint = this.points[0];
|
|
75
79
|
} else if (Input.getType(input2) === Input.Type.NUMBER) {
|
|
76
80
|
const basePoint = this.points.at(0);
|
|
77
81
|
const angle = Utils.degrees2radians(input2);
|
|
@@ -95,10 +99,9 @@ export class Rotate extends Tool {
|
|
|
95
99
|
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
96
100
|
|
|
97
101
|
if (this.points.length >= 1 && this.baseAngle !== null || this.basePoint !== null) {
|
|
98
|
-
// Draw a line
|
|
102
|
+
// Draw a rubber-band line from center to mouse
|
|
99
103
|
const center = this.basePoint === null ? this.points.at(0) : this.basePoint;
|
|
100
|
-
|
|
101
|
-
DesignCore.Scene.tempEntities.create('Line', { points: points });
|
|
104
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([center, mousePoint]));
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
if (this.points.length >= 1) {
|