@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
package/core/tools/chamfer.js
CHANGED
|
@@ -3,10 +3,12 @@ import { ChamferFilletBase } from './chamferFilletBase.js';
|
|
|
3
3
|
import { Intersection } from '../lib/intersect.js';
|
|
4
4
|
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
5
5
|
import { Logging } from '../lib/logging.js';
|
|
6
|
+
import { Utils } from '../lib/utils.js';
|
|
6
7
|
import { Point } from '../entities/point.js';
|
|
7
8
|
import { Line } from '../entities/line.js';
|
|
8
|
-
import {
|
|
9
|
-
import { AddState
|
|
9
|
+
import { PolylineBase } from '../entities/polylineBase.js';
|
|
10
|
+
import { AddState } from '../lib/stateManager.js';
|
|
11
|
+
import { Colour } from '../lib/colour.js';
|
|
10
12
|
|
|
11
13
|
import { DesignCore } from '../designCore.js';
|
|
12
14
|
|
|
@@ -96,9 +98,9 @@ export class Chamfer extends ChamferFilletBase {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
// input1 is a selection — validate and store first entity
|
|
99
|
-
const firstEntity = DesignCore.Scene.entities.get(input1.
|
|
101
|
+
const firstEntity = DesignCore.Scene.entities.get(input1.selectedEntityIndex);
|
|
100
102
|
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
101
|
-
if (!(firstEntity instanceof Line) && !(firstEntity instanceof
|
|
103
|
+
if (!(firstEntity instanceof Line) && !(firstEntity instanceof PolylineBase)) {
|
|
102
104
|
DesignCore.Core.notify(`${firstEntity.type} - ${Strings.Message.NOCHAMFER}`);
|
|
103
105
|
continue;
|
|
104
106
|
}
|
|
@@ -110,16 +112,16 @@ export class Chamfer extends ChamferFilletBase {
|
|
|
110
112
|
const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
111
113
|
if (input2 === undefined) return;
|
|
112
114
|
|
|
113
|
-
const candidate = DesignCore.Scene.entities.get(input2.
|
|
115
|
+
const candidate = DesignCore.Scene.entities.get(input2.selectedEntityIndex);
|
|
114
116
|
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
115
|
-
if (!(candidate instanceof Line) && !(candidate instanceof
|
|
117
|
+
if (!(candidate instanceof Line) && !(candidate instanceof PolylineBase)) {
|
|
116
118
|
DesignCore.Core.notify(`${this.type} - ${candidate.type} ${Strings.Message.NOCHAMFER}`);
|
|
117
119
|
continue;
|
|
118
120
|
}
|
|
119
121
|
if (!this.secondPick.setPick(candidate, input2.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOCHAMFER}`)) continue;
|
|
120
122
|
// If both selections are the same polyline, segments must be consecutive
|
|
121
123
|
// or they must be the open-end segments of an open polyline.
|
|
122
|
-
if (candidate === firstEntity && firstEntity instanceof
|
|
124
|
+
if (candidate === firstEntity && firstEntity instanceof PolylineBase) {
|
|
123
125
|
const isConsecutive = firstEntity.areConsecutiveSegments(this.firstPick.segmentIndex, this.secondPick.segmentIndex);
|
|
124
126
|
const lastIdx = firstEntity.points.length - 1;
|
|
125
127
|
const isOpenEnds = !firstEntity.flags.hasFlag(1) &&
|
|
@@ -146,7 +148,46 @@ export class Chamfer extends ChamferFilletBase {
|
|
|
146
148
|
* Preview the command during execution
|
|
147
149
|
*/
|
|
148
150
|
preview() {
|
|
149
|
-
|
|
151
|
+
const selection = this.validateSelection();
|
|
152
|
+
if (!selection) return;
|
|
153
|
+
|
|
154
|
+
const chamferMode = DesignCore.Scene.headers.chamferMode;
|
|
155
|
+
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
156
|
+
const geo = this.#computeChamfer(this.firstPick, selection.tempSecond, selection.intersectionPoint);
|
|
157
|
+
if (!geo) return;
|
|
158
|
+
if (trimMode && !geo.chamferInBounds) return;
|
|
159
|
+
|
|
160
|
+
const isSharpCorner = !chamferMode &&
|
|
161
|
+
DesignCore.Scene.headers.chamferDistanceA === 0 &&
|
|
162
|
+
DesignCore.Scene.headers.chamferDistanceB === 0;
|
|
163
|
+
|
|
164
|
+
if (!trimMode) {
|
|
165
|
+
if (!isSharpCorner) DesignCore.Scene.previewEntities.add(geo.chamferLine);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// trimMode: dull the original segments then show the shortened versions + chamfer line
|
|
170
|
+
const firstSeg = Utils.cloneObject(this.firstPick.activeSeg);
|
|
171
|
+
firstSeg.setProperty('colour', Colour.blend(this.firstPick.entity.getDrawColour(), DesignCore.Settings.canvasbackgroundcolour, DesignCore.Settings.previewBlendFactor));
|
|
172
|
+
DesignCore.Scene.previewEntities.add(firstSeg);
|
|
173
|
+
|
|
174
|
+
const secondSeg = Utils.cloneObject(selection.tempSecond.activeSeg);
|
|
175
|
+
secondSeg.setProperty('colour', Colour.blend(selection.tempSecond.entity.getDrawColour(), DesignCore.Settings.canvasbackgroundcolour, DesignCore.Settings.previewBlendFactor));
|
|
176
|
+
DesignCore.Scene.previewEntities.add(secondSeg);
|
|
177
|
+
|
|
178
|
+
const firstTrimLine = DesignCore.CommandManager.createNew('Line', {
|
|
179
|
+
points: [this.firstPick.lineKeptEnd(selection.intersectionPoint), geo.firstChamferPoint],
|
|
180
|
+
...Utils.cloneProperties(this.firstPick.entity),
|
|
181
|
+
});
|
|
182
|
+
DesignCore.Scene.previewEntities.add(firstTrimLine);
|
|
183
|
+
|
|
184
|
+
const secondTrimLine = DesignCore.CommandManager.createNew('Line', {
|
|
185
|
+
points: [selection.tempSecond.lineKeptEnd(selection.intersectionPoint), geo.secondChamferPoint],
|
|
186
|
+
...Utils.cloneProperties(selection.candidate),
|
|
187
|
+
});
|
|
188
|
+
DesignCore.Scene.previewEntities.add(secondTrimLine);
|
|
189
|
+
|
|
190
|
+
DesignCore.Scene.previewEntities.add(geo.chamferLine);
|
|
150
191
|
}
|
|
151
192
|
|
|
152
193
|
/**
|
|
@@ -156,174 +197,107 @@ export class Chamfer extends ChamferFilletBase {
|
|
|
156
197
|
if (!this.firstPick.entity || !this.secondPick.entity) return;
|
|
157
198
|
if (!this.resolveCornerGeometry(Strings.Message.NOCHAMFER)) return;
|
|
158
199
|
|
|
159
|
-
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
160
200
|
const chamferMode = DesignCore.Scene.headers.chamferMode;
|
|
161
|
-
const
|
|
201
|
+
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
162
202
|
|
|
163
|
-
//
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
DesignCore.
|
|
203
|
+
// Validate angle before computing — parallel-lines error would be misleading
|
|
204
|
+
if (chamferMode) {
|
|
205
|
+
const alpha = DesignCore.Scene.headers.chamferAngle * (Math.PI / 180);
|
|
206
|
+
if (alpha <= 0 || alpha >= Math.PI) {
|
|
207
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.INVALIDNUMBER}`);
|
|
208
|
+
return;
|
|
168
209
|
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const geo = this.#computeChamfer(this.firstPick, this.secondPick, this.intersectionPoint);
|
|
213
|
+
if (!geo) {
|
|
214
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// dist=0 in distance mode: sharp corner trim with no chamfer line (mirrors fillet radius=0)
|
|
219
|
+
const isSharpCorner = !chamferMode &&
|
|
220
|
+
DesignCore.Scene.headers.chamferDistanceA === 0 &&
|
|
221
|
+
DesignCore.Scene.headers.chamferDistanceB === 0;
|
|
222
|
+
|
|
223
|
+
if (trimMode && !geo.chamferInBounds) {
|
|
224
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.MAXVALUE}`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!trimMode) {
|
|
229
|
+
if (!isSharpCorner) DesignCore.Scene.commit([new AddState(geo.chamferLine)]);
|
|
169
230
|
return;
|
|
170
231
|
}
|
|
171
232
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
233
|
+
const stateChanges = this.applyCornerTrim(
|
|
234
|
+
geo.firstChamferPoint, geo.secondChamferPoint,
|
|
235
|
+
isSharpCorner ? null : geo.chamferLine,
|
|
236
|
+
);
|
|
237
|
+
DesignCore.Scene.commit(stateChanges);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Compute the chamfer geometry for two picks meeting at intersectionPoint.
|
|
242
|
+
* Returns null if the lines are parallel/collinear or the chamfer direction is parallel to line2 (angle mode).
|
|
243
|
+
* @param {CornerEntity} firstPick
|
|
244
|
+
* @param {CornerEntity} secondPick
|
|
245
|
+
* @param {Point} intersectionPoint
|
|
246
|
+
* @return {{chamferLine, firstChamferPoint, secondChamferPoint, chamferInBounds}|null}
|
|
247
|
+
*/
|
|
248
|
+
#computeChamfer(firstPick, secondPick, intersectionPoint) {
|
|
249
|
+
const chamferMode = DesignCore.Scene.headers.chamferMode;
|
|
250
|
+
const distA = DesignCore.Scene.headers.chamferDistanceA;
|
|
251
|
+
const firstUnit = firstPick.clickUnit(intersectionPoint);
|
|
252
|
+
const secondUnit = secondPick.clickUnit(intersectionPoint);
|
|
253
|
+
|
|
175
254
|
let firstChamferPoint;
|
|
176
255
|
let secondChamferPoint;
|
|
177
256
|
|
|
178
257
|
if (!chamferMode) {
|
|
179
|
-
// Distance method:
|
|
258
|
+
// Distance method: distA = distB = 0 means sharp corner trim with no chamfer line
|
|
180
259
|
const distB = DesignCore.Scene.headers.chamferDistanceB;
|
|
181
|
-
firstChamferPoint =
|
|
182
|
-
secondChamferPoint =
|
|
260
|
+
firstChamferPoint = intersectionPoint.add(firstUnit.scale(distA));
|
|
261
|
+
secondChamferPoint = intersectionPoint.add(secondUnit.scale(distB));
|
|
183
262
|
} else {
|
|
184
|
-
// Angle method
|
|
185
|
-
// where the chamfer line meets line2.
|
|
186
|
-
// chamferAngle is stored in degrees (as entered by the user).
|
|
263
|
+
// Angle method
|
|
187
264
|
const chamferLength = DesignCore.Scene.headers.chamferLength;
|
|
188
265
|
const alpha = DesignCore.Scene.headers.chamferAngle * (Math.PI / 180);
|
|
266
|
+
if (alpha <= 0 || alpha >= Math.PI) return null;
|
|
189
267
|
|
|
190
|
-
|
|
191
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.INVALIDNUMBER}`);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
firstChamferPoint = this.intersectionPoint.add(firstUnit.scale(chamferLength));
|
|
268
|
+
firstChamferPoint = intersectionPoint.add(firstUnit.scale(chamferLength));
|
|
196
269
|
|
|
197
|
-
// Rotate firstUnit by ±(π - alpha) to get the chamfer direction from
|
|
198
|
-
// firstChamferPoint. Choose the rotation that points toward line2 (positive
|
|
199
|
-
// dot product with secondUnit).
|
|
200
270
|
const rotAngle = Math.PI - alpha;
|
|
201
271
|
const origin = new Point(0, 0);
|
|
202
272
|
const candidate1 = firstUnit.rotate(origin, rotAngle);
|
|
203
273
|
const chamferDir = candidate1.dot(secondUnit) >= 0 ? candidate1 : firstUnit.rotate(origin, -rotAngle);
|
|
204
274
|
|
|
205
|
-
// Intersect chamfer ray from firstChamferPoint with the infinite line2
|
|
206
275
|
const chamferResult = Intersection.intersectSegmentSegment(
|
|
207
276
|
firstChamferPoint, firstChamferPoint.add(chamferDir),
|
|
208
|
-
|
|
277
|
+
secondPick.lineStart, secondPick.lineEnd,
|
|
209
278
|
true, true,
|
|
210
279
|
);
|
|
280
|
+
if (chamferResult.status === Intersection.Status.PARALLEL ||
|
|
281
|
+
chamferResult.status === Intersection.Status.OVERLAPPING ||
|
|
282
|
+
chamferResult.status === Intersection.Status.COINCIDENT) return null;
|
|
211
283
|
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
chamferStatus === Intersection.Status.OVERLAPPING ||
|
|
215
|
-
chamferStatus === Intersection.Status.COINCIDENT) {
|
|
216
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
secondChamferPoint = chamferResult.points[0] || null;
|
|
221
|
-
if (!secondChamferPoint) {
|
|
222
|
-
// Chamfer direction is parallel to line2 — no intersection
|
|
223
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
284
|
+
secondChamferPoint = chamferResult.points[0];
|
|
285
|
+
if (!secondChamferPoint) return null;
|
|
226
286
|
}
|
|
227
287
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const firstChamferDot = firstChamferPoint.subtract(this.intersectionPoint).dot(firstKeptDir);
|
|
235
|
-
const secondChamferDot = secondChamferPoint.subtract(this.intersectionPoint).dot(secondKeptDir);
|
|
236
|
-
if (firstChamferDot < 0 || firstChamferDot > firstKeptDir.dot(firstKeptDir) ||
|
|
237
|
-
secondChamferDot < 0 || secondChamferDot > secondKeptDir.dot(secondKeptDir)) {
|
|
238
|
-
DesignCore.Core.notify(`${this.type} - ${Strings.Error.MAXVALUE}`);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
288
|
+
const firstKeptDir = firstPick.lineKeptEnd(intersectionPoint).subtract(intersectionPoint);
|
|
289
|
+
const secondKeptDir = secondPick.lineKeptEnd(intersectionPoint).subtract(intersectionPoint);
|
|
290
|
+
const firstDot = firstChamferPoint.subtract(intersectionPoint).dot(firstKeptDir);
|
|
291
|
+
const secondDot = secondChamferPoint.subtract(intersectionPoint).dot(secondKeptDir);
|
|
292
|
+
const chamferInBounds = firstDot >= 0 && firstDot <= firstKeptDir.dot(firstKeptDir) &&
|
|
293
|
+
secondDot >= 0 && secondDot <= secondKeptDir.dot(secondKeptDir);
|
|
242
294
|
|
|
243
295
|
const chamferLine = DesignCore.CommandManager.createNew('Line', {
|
|
244
296
|
points: [firstChamferPoint, secondChamferPoint],
|
|
297
|
+
...Utils.cloneProperties(firstPick.entity),
|
|
245
298
|
});
|
|
246
299
|
|
|
247
|
-
|
|
248
|
-
const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
|
|
249
|
-
|
|
250
|
-
// The standalone chamfer Line entity is added when no polyline trimming is involved.
|
|
251
|
-
// For polyline trim cases the chamfer segment is encoded as a straight polyline segment,
|
|
252
|
-
// except for the open-ends case where the chamfer line fills the gap between the two
|
|
253
|
-
// open ends and is added separately there.
|
|
254
|
-
const stateChanges = (!trimMode || (!firstIsPolyline && !secondIsPolyline)) ?
|
|
255
|
-
[new AddState(chamferLine)] :
|
|
256
|
-
[];
|
|
257
|
-
|
|
258
|
-
if (trimMode) {
|
|
259
|
-
if (!firstIsPolyline && !secondIsPolyline) {
|
|
260
|
-
// Line + Line: trim both lines
|
|
261
|
-
stateChanges.push(new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstChamferPoint] }));
|
|
262
|
-
stateChanges.push(new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondChamferPoint] }));
|
|
263
|
-
} else if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
|
|
264
|
-
const closeSegIdx = this.firstPick.entity.points.length;
|
|
265
|
-
const lastIdx = closeSegIdx - 1;
|
|
266
|
-
const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
|
|
267
|
-
const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
|
|
268
|
-
(this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
|
|
269
|
-
(this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
|
|
270
|
-
);
|
|
271
|
-
const newPoints = this.firstPick.entity.points.map((p) => p.clone());
|
|
272
|
-
if (isOpenEnds) {
|
|
273
|
-
// Open ends: trim both endpoints to their chamfer points; chamfer line fills the gap.
|
|
274
|
-
stateChanges.push(new AddState(chamferLine));
|
|
275
|
-
if (this.firstPick.segmentIndex === 1) {
|
|
276
|
-
newPoints[0] = firstChamferPoint.clone();
|
|
277
|
-
newPoints[lastIdx] = secondChamferPoint.clone();
|
|
278
|
-
} else {
|
|
279
|
-
newPoints[0] = secondChamferPoint.clone();
|
|
280
|
-
newPoints[lastIdx] = firstChamferPoint.clone();
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
// Consecutive segments: replace the shared corner vertex with the two chamfer points
|
|
284
|
-
// as a straight polyline segment — no separate chamfer Line entity needed.
|
|
285
|
-
const seg1 = this.firstPick.segmentIndex;
|
|
286
|
-
const seg2 = this.secondPick.segmentIndex;
|
|
287
|
-
const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
|
|
288
|
-
const isFirstLower = seg1 < seg2;
|
|
289
|
-
const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
|
|
290
|
-
const lowerChamfer = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? secondChamferPoint : firstChamferPoint;
|
|
291
|
-
const upperChamfer = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? firstChamferPoint : secondChamferPoint;
|
|
292
|
-
newPoints.splice(cornerIdx, 1, lowerChamfer.clone(), upperChamfer.clone());
|
|
293
|
-
}
|
|
294
|
-
stateChanges.push(new UpdateState(this.firstPick.entity, { points: newPoints }));
|
|
295
|
-
} else {
|
|
296
|
-
// Line + Polyline or Polyline + Line: consume the line into the polyline.
|
|
297
|
-
const [poly, line] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
|
|
298
|
-
const polyChamferPoint = poly === this.firstPick ? firstChamferPoint : secondChamferPoint;
|
|
299
|
-
const lineChamferPoint = line === this.firstPick ? firstChamferPoint : secondChamferPoint;
|
|
300
|
-
const polySegIdx = poly.segmentIndex;
|
|
301
|
-
const keepStart = poly.keepStart(this.intersectionPoint);
|
|
302
|
-
let newPoints;
|
|
303
|
-
if (keepStart) {
|
|
304
|
-
// Keep start portion: points[0..polySegIdx-1], then chamfer, then line end
|
|
305
|
-
newPoints = [
|
|
306
|
-
...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
|
|
307
|
-
polyChamferPoint.clone(),
|
|
308
|
-
lineChamferPoint.clone(),
|
|
309
|
-
line.lineKeptEnd(this.intersectionPoint).clone(),
|
|
310
|
-
];
|
|
311
|
-
} else {
|
|
312
|
-
// Keep end portion: line end, then chamfer, then points[polySegIdx..end]
|
|
313
|
-
newPoints = [
|
|
314
|
-
line.lineKeptEnd(this.intersectionPoint).clone(),
|
|
315
|
-
lineChamferPoint.clone(),
|
|
316
|
-
polyChamferPoint.clone(),
|
|
317
|
-
...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
|
|
318
|
-
];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
stateChanges.push(new RemoveState(line.entity));
|
|
322
|
-
stateChanges.push(new UpdateState(poly.entity, { points: newPoints }));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
DesignCore.Scene.commit(stateChanges);
|
|
300
|
+
return { chamferLine, firstChamferPoint, secondChamferPoint, chamferInBounds };
|
|
327
301
|
}
|
|
328
302
|
|
|
329
303
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Tool } from './tool.js';
|
|
2
2
|
import { Strings } from '../lib/strings.js';
|
|
3
3
|
import { Constants } from '../lib/constants.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Line } from '../entities/line.js';
|
|
5
|
+
import { PolylineBase } from '../entities/polylineBase.js';
|
|
6
|
+
import { AddState, RemoveState, UpdateState } from '../lib/stateManager.js';
|
|
6
7
|
import { CornerEntity } from './cornerEntity.js';
|
|
7
8
|
import { Intersection } from '../lib/intersect.js';
|
|
8
9
|
|
|
@@ -11,7 +12,7 @@ import { DesignCore } from '../designCore.js';
|
|
|
11
12
|
/**
|
|
12
13
|
* ChamferFilletBase Class
|
|
13
14
|
* Base class for Chamfer and Fillet commands, providing shared entity selection
|
|
14
|
-
* state, segment resolution, and
|
|
15
|
+
* state, segment resolution, and shared corner-trim logic.
|
|
15
16
|
* @extends Tool
|
|
16
17
|
*/
|
|
17
18
|
export class ChamferFilletBase extends Tool {
|
|
@@ -90,74 +91,174 @@ export class ChamferFilletBase extends Tool {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* @return {Array} - array of state changes to be committed by the caller
|
|
94
|
+
* Resolves the preview second-pick and virtual intersection for the mouse position.
|
|
95
|
+
* Returns { candidate, tempSecond, intersectionPoint } or null if no valid preview exists.
|
|
96
|
+
* @return {{candidate, tempSecond, intersectionPoint}|null}
|
|
97
97
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
validateSelection() {
|
|
99
|
+
if (!this.firstPick.entity) return null;
|
|
100
|
+
if (!this.firstPick.resolveEndpoints()) return null;
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
103
|
+
const index = DesignCore.Scene.selectionManager.findClosestItem(mousePoint);
|
|
104
|
+
if (index === undefined) return null;
|
|
105
|
+
|
|
106
|
+
const candidate = DesignCore.Scene.entities.get(index);
|
|
107
|
+
if (!(candidate instanceof Line) && !(candidate instanceof PolylineBase)) return null;
|
|
108
|
+
|
|
109
|
+
const tempSecond = new CornerEntity();
|
|
110
|
+
if (!tempSecond.setPick(candidate, mousePoint, '')) return null;
|
|
111
|
+
if (!tempSecond.resolveEndpoints()) return null;
|
|
112
|
+
|
|
113
|
+
const result = Intersection.intersectSegmentSegment(
|
|
114
|
+
this.firstPick.lineStart, this.firstPick.lineEnd,
|
|
115
|
+
tempSecond.lineStart, tempSecond.lineEnd,
|
|
116
|
+
true, true,
|
|
117
|
+
);
|
|
118
|
+
if (result.status === Intersection.Status.PARALLEL ||
|
|
119
|
+
result.status === Intersection.Status.OVERLAPPING ||
|
|
120
|
+
result.status === Intersection.Status.COINCIDENT) return null;
|
|
121
|
+
|
|
122
|
+
const intersectionPoint = result.points[0];
|
|
123
|
+
if (!intersectionPoint) return null;
|
|
124
|
+
|
|
125
|
+
return { candidate, tempSecond, intersectionPoint };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Unified corner-trim method for sharp trims, chamfer, and fillet.
|
|
130
|
+
* Computes and applies state changes based on entity types.
|
|
131
|
+
*
|
|
132
|
+
* For sharp trims (newEntity === null), both corner points are the intersection
|
|
133
|
+
* point and a single point is spliced/assigned (not two copies).
|
|
134
|
+
*
|
|
135
|
+
* For fillet, firstCornerPoint carries the arc bulge from arc.toPolylinePoints().
|
|
136
|
+
* This method transfers the bulge to whichever point comes first in the polyline
|
|
137
|
+
* traversal order, ensuring correct arc encoding.
|
|
138
|
+
*
|
|
139
|
+
* @param {Point} firstCornerPoint - trim/chamfer/tangent point on the first entity
|
|
140
|
+
* @param {Point} secondCornerPoint - trim/chamfer/tangent point on the second entity
|
|
141
|
+
* @param {Entity|null} newEntity - entity to add (Arc for fillet, Line for chamfer, null for sharp trim)
|
|
142
|
+
* @return {Array} - array of state changes
|
|
143
|
+
*/
|
|
144
|
+
applyCornerTrim(firstCornerPoint, secondCornerPoint, newEntity) {
|
|
145
|
+
const isSharpTrim = newEntity === null;
|
|
146
|
+
const firstIsPolyline = this.firstPick.entity instanceof PolylineBase;
|
|
147
|
+
const secondIsPolyline = this.secondPick.entity instanceof PolylineBase;
|
|
148
|
+
|
|
149
|
+
// Two standalone entities: update both, optionally add the new entity.
|
|
103
150
|
if (!firstIsPolyline && !secondIsPolyline) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
151
|
+
const stateChanges = isSharpTrim ? [] : [new AddState(newEntity)];
|
|
152
|
+
// Corner points may carry a bulge value from arc.toPolylinePoints() — a Line
|
|
153
|
+
// endpoint must never have a non-zero bulge or it renders as an arc.
|
|
154
|
+
// toPolylinePoints() returns fresh Point objects so direct mutation is safe.
|
|
155
|
+
firstCornerPoint.bulge = 0;
|
|
156
|
+
secondCornerPoint.bulge = 0;
|
|
157
|
+
stateChanges.push(
|
|
158
|
+
new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstCornerPoint] }),
|
|
159
|
+
new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondCornerPoint] }),
|
|
160
|
+
);
|
|
161
|
+
return stateChanges;
|
|
108
162
|
}
|
|
109
163
|
|
|
110
|
-
//
|
|
111
|
-
// with the intersection point.
|
|
164
|
+
// Same polyline: splice the corner points into the point array.
|
|
112
165
|
if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
|
|
113
|
-
const
|
|
166
|
+
const closeSegIdx = this.firstPick.entity.points.length;
|
|
167
|
+
const lastIdx = closeSegIdx - 1;
|
|
114
168
|
const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
|
|
115
169
|
const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
|
|
116
170
|
(this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
|
|
117
171
|
(this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
|
|
118
172
|
);
|
|
119
|
-
|
|
120
173
|
const newPoints = this.firstPick.entity.points.map((p) => p.clone());
|
|
174
|
+
const stateChanges = [];
|
|
121
175
|
|
|
122
176
|
if (isOpenEnds) {
|
|
123
|
-
|
|
124
|
-
|
|
177
|
+
if (!isSharpTrim) stateChanges.push(new AddState(newEntity));
|
|
178
|
+
if (this.firstPick.segmentIndex === 1) {
|
|
179
|
+
newPoints[0] = firstCornerPoint.clone();
|
|
180
|
+
newPoints[lastIdx] = secondCornerPoint.clone();
|
|
181
|
+
} else {
|
|
182
|
+
newPoints[0] = secondCornerPoint.clone();
|
|
183
|
+
newPoints[lastIdx] = firstCornerPoint.clone();
|
|
184
|
+
}
|
|
125
185
|
} else {
|
|
126
|
-
const closeSegIdx = this.firstPick.entity.points.length;
|
|
127
186
|
const seg1 = this.firstPick.segmentIndex;
|
|
128
187
|
const seg2 = this.secondPick.segmentIndex;
|
|
129
188
|
const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
|
|
189
|
+
const isFirstLower = seg1 < seg2;
|
|
130
190
|
const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
|
|
131
|
-
|
|
191
|
+
|
|
192
|
+
if (isSharpTrim) {
|
|
193
|
+
// Sharp trim: splice in a single copy of the intersection point.
|
|
194
|
+
newPoints.splice(cornerIdx, 1, firstCornerPoint.clone());
|
|
195
|
+
} else {
|
|
196
|
+
// Chamfer/fillet: splice in two points. Ensure the bulge (if any) is on
|
|
197
|
+
// the lower point (the one that comes first in polyline traversal).
|
|
198
|
+
const swapped = isClosingWrap ? seg1 !== closeSegIdx : !isFirstLower;
|
|
199
|
+
const lowerSrc = swapped ? secondCornerPoint : firstCornerPoint;
|
|
200
|
+
const upperSrc = swapped ? firstCornerPoint : secondCornerPoint;
|
|
201
|
+
const arcBulge = firstCornerPoint.bulge || 0;
|
|
202
|
+
const lowerClone = lowerSrc.clone();
|
|
203
|
+
const upperClone = upperSrc.clone();
|
|
204
|
+
lowerClone.bulge = swapped ? -arcBulge : arcBulge;
|
|
205
|
+
upperClone.bulge = 0;
|
|
206
|
+
newPoints.splice(cornerIdx, 1, lowerClone, upperClone);
|
|
207
|
+
}
|
|
132
208
|
}
|
|
133
|
-
|
|
209
|
+
|
|
210
|
+
stateChanges.push(new UpdateState(this.firstPick.entity, { points: newPoints }));
|
|
211
|
+
return stateChanges;
|
|
134
212
|
}
|
|
135
213
|
|
|
136
|
-
//
|
|
137
|
-
const poly = firstIsPolyline ? this.firstPick : this.secondPick;
|
|
138
|
-
const
|
|
214
|
+
// Standalone + Polyline: consume the standalone entity into the polyline.
|
|
215
|
+
const [poly, standalone] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
|
|
216
|
+
const polyCornerPoint = poly === this.firstPick ? firstCornerPoint : secondCornerPoint;
|
|
217
|
+
const standaloneCornerPoint = poly === this.firstPick ? secondCornerPoint : firstCornerPoint;
|
|
218
|
+
const arcBulge = firstCornerPoint.bulge || 0;
|
|
139
219
|
const polySegIdx = poly.segmentIndex;
|
|
140
220
|
const keepStart = poly.keepStart(this.intersectionPoint);
|
|
141
221
|
let newPoints;
|
|
142
|
-
if (
|
|
143
|
-
//
|
|
144
|
-
|
|
222
|
+
if (isSharpTrim) {
|
|
223
|
+
// Sharp trim: single intersection point between poly and standalone kept ends.
|
|
224
|
+
if (keepStart) {
|
|
225
|
+
newPoints = [
|
|
226
|
+
...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
|
|
227
|
+
polyCornerPoint.clone(),
|
|
228
|
+
standalone.lineKeptEnd(this.intersectionPoint).clone(),
|
|
229
|
+
];
|
|
230
|
+
} else {
|
|
231
|
+
newPoints = [
|
|
232
|
+
standalone.lineKeptEnd(this.intersectionPoint).clone(),
|
|
233
|
+
polyCornerPoint.clone(),
|
|
234
|
+
...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
} else if (keepStart) {
|
|
238
|
+
// Keep start: [...polyBefore, polyCorner(with bulge), standaloneCorner, lineKeptEnd]
|
|
239
|
+
// The arc runs from polyCorner to standaloneCorner in polyline traversal order.
|
|
240
|
+
const polyClone = polyCornerPoint.clone();
|
|
241
|
+
polyClone.bulge = poly === this.firstPick ? arcBulge : -arcBulge;
|
|
145
242
|
newPoints = [
|
|
146
243
|
...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
|
|
147
|
-
|
|
148
|
-
|
|
244
|
+
polyClone,
|
|
245
|
+
standaloneCornerPoint.clone(),
|
|
246
|
+
standalone.lineKeptEnd(this.intersectionPoint).clone(),
|
|
149
247
|
];
|
|
150
248
|
} else {
|
|
151
|
-
// Keep
|
|
152
|
-
//
|
|
249
|
+
// Keep end: [lineKeptEnd, standaloneCorner(with negated bulge), polyCorner, ...polyAfter]
|
|
250
|
+
// The arc runs from standaloneCorner to polyCorner in reversed traversal.
|
|
251
|
+
const standaloneClone = standaloneCornerPoint.clone();
|
|
252
|
+
standaloneClone.bulge = poly === this.firstPick ? -arcBulge : arcBulge;
|
|
153
253
|
newPoints = [
|
|
154
|
-
|
|
155
|
-
|
|
254
|
+
standalone.lineKeptEnd(this.intersectionPoint).clone(),
|
|
255
|
+
standaloneClone,
|
|
256
|
+
polyCornerPoint.clone(),
|
|
156
257
|
...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
|
|
157
258
|
];
|
|
158
259
|
}
|
|
159
260
|
return [
|
|
160
|
-
new RemoveState(
|
|
261
|
+
new RemoveState(standalone.entity),
|
|
161
262
|
new UpdateState(poly.entity, { points: newPoints }),
|
|
162
263
|
];
|
|
163
264
|
}
|
package/core/tools/copy.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Point } from '../entities/point.js';
|
|
|
7
7
|
import { AddState } 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
|
* Copy Command Class
|
|
@@ -77,17 +78,16 @@ export class Copy extends Tool {
|
|
|
77
78
|
if (this.points.length >= 1) {
|
|
78
79
|
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
79
80
|
|
|
80
|
-
// Draw a line
|
|
81
|
-
|
|
82
|
-
DesignCore.Scene.tempEntities.create('Line', { points: points });
|
|
81
|
+
// Draw a rubber-band line from base point to mouse
|
|
82
|
+
DesignCore.Scene.auxiliaryEntities.add(new RubberBand([this.points.at(-1), mousePoint]));
|
|
83
83
|
|
|
84
84
|
const delta = mousePoint.subtract(this.lastMousePoint || this.points[0]);
|
|
85
85
|
this.lastMousePoint = mousePoint;
|
|
86
86
|
|
|
87
|
-
for (let i = 0; i < DesignCore.Scene.selectionManager.
|
|
88
|
-
const
|
|
89
|
-
const offsetPoints =
|
|
90
|
-
|
|
87
|
+
for (let i = 0; i < DesignCore.Scene.selectionManager.selectedEntities.length; i++) {
|
|
88
|
+
const entity = DesignCore.Scene.selectionManager.selectedEntities[i];
|
|
89
|
+
const offsetPoints = entity.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
|
|
90
|
+
entity.setProperty('points', offsetPoints);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -103,10 +103,10 @@ export class Copy extends Tool {
|
|
|
103
103
|
const stateChanges = [];
|
|
104
104
|
|
|
105
105
|
for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
|
|
106
|
-
const
|
|
107
|
-
const offsetPoints =
|
|
108
|
-
|
|
109
|
-
const stateChange = new AddState(
|
|
106
|
+
const copyOfEntity = Utils.cloneObject(DesignCore.Scene.entities.get(DesignCore.Scene.selectionManager.selectionSet.selectionSet[i]));
|
|
107
|
+
const offsetPoints = copyOfEntity.points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).add(delta));
|
|
108
|
+
copyOfEntity.setProperty('points', offsetPoints);
|
|
109
|
+
const stateChange = new AddState(copyOfEntity);
|
|
110
110
|
stateChanges.push(stateChange);
|
|
111
111
|
}
|
|
112
112
|
|
package/core/tools/copybase.js
CHANGED
|
@@ -56,11 +56,11 @@ export class Copybase extends Tool {
|
|
|
56
56
|
* Perform the command
|
|
57
57
|
*/
|
|
58
58
|
action() {
|
|
59
|
-
// generate clipboard data from selected
|
|
60
|
-
const
|
|
59
|
+
// generate clipboard data from selected entities
|
|
60
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
61
61
|
|
|
62
62
|
// create clipboard data
|
|
63
|
-
DesignCore.Clipboard.Entities =
|
|
63
|
+
DesignCore.Clipboard.Entities = selectedEntities;
|
|
64
64
|
// set base point
|
|
65
65
|
DesignCore.Clipboard.BasePoint = this.points[0];
|
|
66
66
|
}
|
package/core/tools/copyclip.js
CHANGED
|
@@ -52,10 +52,10 @@ export class Copyclip extends Tool {
|
|
|
52
52
|
* Perform the command
|
|
53
53
|
*/
|
|
54
54
|
action() {
|
|
55
|
-
// generate clipboard data from selected
|
|
56
|
-
const
|
|
55
|
+
// generate clipboard data from selected entities
|
|
56
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
57
57
|
|
|
58
58
|
// create clipboard data
|
|
59
|
-
DesignCore.Clipboard.Entities =
|
|
59
|
+
DesignCore.Clipboard.Entities = selectedEntities;
|
|
60
60
|
}
|
|
61
61
|
}
|