@design-core/core 0.0.1 → 0.0.3
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 +14 -7
- package/.github/workflows/DesignCoreQA.yml +3 -1
- package/README.md +11 -4
- package/TODO.md +19 -0
- package/core/core/core.js +49 -42
- package/core/designCore.js +61 -2
- package/core/dimensions/alignedDimension.js +142 -0
- package/core/dimensions/angularDimension.js +550 -0
- package/core/dimensions/baseCircularDimension.js +244 -0
- package/core/dimensions/baseDimension.js +558 -0
- package/core/dimensions/baseLinearDimension.js +371 -0
- package/core/dimensions/diametricDimension.js +82 -0
- package/core/dimensions/dimension.js +298 -0
- package/core/dimensions/linearDimension.js +29 -0
- package/core/dimensions/radialDimension.js +78 -0
- package/core/dimensions/rotatedDimension.js +167 -0
- package/core/entities/arc.js +314 -60
- package/core/entities/arctext.js +642 -0
- package/core/entities/basePolyline.js +553 -94
- package/core/entities/circle.js +181 -54
- package/core/entities/entity.js +154 -74
- package/core/entities/hatch.js +807 -0
- package/core/entities/insert.js +200 -39
- package/core/entities/line.js +196 -134
- package/core/entities/lwpolyline.js +21 -44
- package/core/entities/point.js +194 -29
- package/core/entities/polyline.js +54 -7
- package/core/entities/rectangle.js +43 -13
- package/core/entities/solid.js +43 -6
- package/core/entities/text.js +324 -181
- package/core/lib/boundingBox.js +73 -13
- package/core/lib/canvas.js +256 -65
- package/core/lib/clipboard.js +198 -0
- package/core/lib/colour.js +144 -0
- package/core/lib/colours.js +367 -301
- package/core/lib/commandLine.js +16 -28
- package/core/lib/commandManager.js +115 -58
- package/core/lib/constants.js +23 -0
- package/core/lib/dxf/dxf.js +249 -34
- package/core/lib/dxf/dxfFile.js +23 -38
- package/core/lib/dxf/dxfIterator.js +17 -5
- package/core/lib/dxf/dxfRead.js +23 -7
- package/core/lib/dxf/dxfWrite.js +39 -129
- package/core/lib/dxf/handleManager.js +97 -0
- package/core/lib/dxf/sections/blocks.js +16 -2
- package/core/lib/dxf/sections/entities.js +17 -3
- package/core/lib/dxf/sections/header.js +10 -1
- package/core/lib/dxf/sections/objects.js +36 -0
- package/core/lib/dxf/sections/section.js +43 -5
- package/core/lib/dxf/sections/tables.js +17 -3
- package/core/lib/entityManager.js +178 -0
- package/core/lib/fileio.js +14 -4
- package/core/lib/headers.js +229 -0
- package/core/lib/input.js +46 -0
- package/core/lib/inputManager.js +199 -194
- package/core/lib/intersect.js +341 -468
- package/core/lib/logging.js +25 -1
- package/core/lib/matrix.js +6 -8
- package/core/lib/mouse.js +24 -79
- package/core/lib/patterns.js +690 -0
- package/core/lib/promptOptions.js +133 -0
- package/core/lib/scene.js +71 -174
- package/core/lib/selectionManager.js +74 -48
- package/core/lib/selectionWindow.js +24 -10
- package/core/lib/settings.js +18 -11
- package/core/lib/snapping.js +129 -21
- package/core/lib/stateManager.js +288 -0
- package/core/lib/strings.js +57 -5
- package/core/lib/utils.js +95 -11
- package/core/objects/dictionary.js +43 -0
- package/core/objects/dictionaryManager.js +80 -0
- package/core/properties/dimType.js +117 -0
- package/core/properties/flags.js +66 -0
- package/core/properties/property.js +26 -0
- package/core/properties/propertyManager.js +65 -19
- package/core/tables/appId.js +31 -0
- package/core/tables/appIdManager.js +44 -0
- package/core/tables/block.js +323 -0
- package/core/tables/blockManager.js +48 -0
- package/core/tables/blockRecord.js +29 -0
- package/core/tables/blockRecordManager.js +37 -0
- package/core/tables/dimStyle.js +627 -0
- package/core/tables/dimStyleManager.js +187 -0
- package/core/tables/endblk.js +28 -0
- package/core/tables/layer.js +219 -0
- package/core/tables/layerManager.js +47 -0
- package/core/tables/ltype.js +85 -0
- package/core/tables/ltypeManager.js +65 -0
- package/core/tables/style.js +145 -0
- package/core/tables/styleManager.js +55 -0
- package/core/tables/styleManagerBase.js +110 -0
- package/core/tables/tableManagerBase.js +354 -0
- package/core/tables/ucs.js +28 -0
- package/core/tables/ucsManager.js +44 -0
- package/core/tables/view.js +28 -0
- package/core/tables/viewManager.js +44 -0
- package/core/tables/vport.js +70 -0
- package/core/tables/vportManager.js +65 -0
- package/core/tools/chamfer.js +343 -0
- package/core/tools/chamferFilletBase.js +162 -0
- package/core/tools/copy.js +68 -40
- package/core/tools/copybase.js +65 -0
- package/core/tools/copyclip.js +59 -0
- package/core/tools/cornerEntity.js +138 -0
- package/core/tools/cutclip.js +72 -0
- package/core/tools/distance.js +37 -9
- package/core/tools/erase.js +43 -13
- package/core/tools/explode.js +117 -0
- package/core/tools/extend.js +71 -32
- package/core/tools/fillet.js +336 -0
- package/core/tools/identify.js +33 -6
- package/core/tools/matchProp.js +106 -0
- package/core/tools/mirror.js +141 -0
- package/core/tools/move.js +65 -32
- package/core/tools/offset.js +321 -0
- package/core/tools/pan.js +54 -0
- package/core/tools/pasteclip.js +93 -0
- package/core/tools/purge.js +90 -0
- package/core/tools/rotate.js +105 -70
- package/core/tools/scale.js +166 -0
- package/core/tools/tool.js +14 -4
- package/core/tools/trim.js +71 -32
- package/core/tools/zoom.js +152 -0
- package/package.json +3 -305
- package/test/dimensions/alignedDimension.test.js +211 -0
- package/test/dimensions/angularDimension.test.js +243 -0
- package/test/dimensions/baseDimension.test.js +239 -0
- package/test/dimensions/diametricDimension.test.js +203 -0
- package/test/dimensions/dimension.test.js +102 -0
- package/test/dimensions/radialDimension.test.js +203 -0
- package/test/dimensions/rotatedDimension.test.js +224 -0
- package/test/entities/arc.test.js +463 -41
- package/test/entities/arctext.test.js +329 -0
- package/test/entities/basePolyline.test.js +459 -10
- package/test/entities/circle.test.js +312 -7
- package/test/entities/entity.test.js +28 -9
- package/test/entities/hatch.test.js +606 -0
- package/test/entities/insert.test.js +90 -0
- package/test/entities/line.test.js +182 -6
- package/test/entities/point.test.js +375 -37
- package/test/entities/solid.test.js +156 -0
- package/test/entities/text.test.js +533 -7
- package/test/integration/exportIntegration.reference.dxf +1706 -0
- package/test/integration/exportIntegration.test.js +186 -0
- package/test/integration/importExportIntegration.test.js +126 -0
- package/test/integration/intersectionIntegration.test.js +880 -0
- package/test/integration/userInputIntegration.reference.dxf +1410 -0
- package/test/integration/userInputIntegration.test.js +269 -0
- package/test/lib/boundingBox.test.js +26 -2
- package/test/lib/canvas.test.js +613 -5
- package/test/lib/clipboard.test.js +70 -0
- package/test/lib/colour.test.js +114 -0
- package/test/lib/colours.test.js +63 -18
- package/test/lib/commandLine.test.js +159 -22
- package/test/lib/commandManager.test.js +4 -4
- package/test/lib/constants.test.js +9 -0
- package/test/lib/dxf/dxfFile.test.js +6 -2
- package/test/lib/dxf/dxfIterator.test.js +1 -1
- package/test/lib/dxf/dxfWrite.test.js +173 -0
- package/test/lib/dxf/handleManager.test.js +137 -0
- package/test/lib/dxf/sections/blocks.test.js +5 -5
- package/test/lib/dxf/sections/entities.test.js +4 -4
- package/test/lib/dxf/sections/header.test.js +18 -4
- package/test/lib/dxf/sections/objects.test.js +132 -0
- package/test/lib/dxf/sections/section.test.js +29 -29
- package/test/lib/dxf/sections/tables.test.js +3 -3
- package/test/lib/entityManager.test.js +163 -0
- package/test/lib/headers.test.js +233 -0
- package/test/lib/inputManager.test.js +1144 -5
- package/test/lib/intersect.test.js +454 -331
- package/test/lib/logging.test.js +1 -1
- package/test/lib/matrix.test.js +1 -1
- package/test/lib/mouse.test.js +327 -4
- package/test/lib/patterns.test.js +105 -0
- package/test/lib/promptOptions.test.js +179 -0
- package/test/lib/scene.test.js +16 -52
- package/test/lib/selectionManager.test.js +17 -5
- package/test/lib/settings.test.js +111 -5
- package/test/lib/snapping.test.js +142 -4
- package/test/lib/stateManager.test.js +155 -0
- package/test/lib/utils.test.js +81 -1
- package/test/objects/dictionary.test.js +81 -0
- package/test/objects/dictionaryManager.test.js +34 -0
- package/test/properties/dimType.test.js +62 -0
- package/test/properties/flags.test.js +164 -0
- package/test/properties/property.test.js +13 -0
- package/test/properties/propertyManager.test.js +10 -11
- package/test/tables/appId.test.js +46 -0
- package/test/tables/appIdManager.test.js +72 -0
- package/test/tables/block.test.js +157 -0
- package/test/tables/blockManager.test.js +52 -0
- package/test/tables/blockRecord.test.js +39 -0
- package/test/tables/blockRecordManager.test.js +60 -0
- package/test/tables/dimstyle.test.js +72 -0
- package/test/tables/endblk.test.js +30 -0
- package/test/tables/layer.test.js +95 -0
- package/test/tables/layerManager.test.js +172 -0
- package/test/{styles → tables}/ltype.test.js +7 -7
- package/test/{styles → tables}/ltypeManager.test.js +2 -2
- package/test/tables/style.test.js +44 -0
- package/test/tables/stylemanagerbase.test.js +220 -0
- package/test/tables/tableManagerBase.test.js +194 -0
- package/test/tables/ucs.test.js +37 -0
- package/test/tables/ucsManager.test.js +48 -0
- package/test/tables/view.test.js +37 -0
- package/test/tables/viewManager.test.js +48 -0
- package/test/tables/vport.test.js +117 -0
- package/test/tables/vportManager.test.js +54 -0
- package/test/test-helpers/test-helpers.js +66 -0
- package/test/tools/chamfer.test.js +970 -0
- package/test/tools/chamferFilletBase.test.js +446 -0
- package/test/tools/copy.test.js +22 -14
- package/test/tools/copybase.test.js +93 -0
- package/test/tools/copyclip.test.js +92 -0
- package/test/tools/cornerEntity.test.js +201 -0
- package/test/tools/cutclip.test.js +94 -0
- package/test/tools/distance.test.js +140 -18
- package/test/tools/erase.test.js +12 -12
- package/test/tools/explode.test.js +45 -0
- package/test/tools/extend.test.js +326 -44
- package/test/tools/fillet.test.js +831 -0
- package/test/tools/identify.test.js +115 -16
- package/test/tools/matchProp.test.js +84 -0
- package/test/tools/mirror.test.js +475 -0
- package/test/tools/move.test.js +75 -19
- package/test/tools/offset.test.js +748 -0
- package/test/tools/pan.test.js +72 -0
- package/test/tools/pasteclip.test.js +100 -0
- package/test/tools/purge.test.js +89 -0
- package/test/tools/rotate.test.js +89 -23
- package/test/tools/scale.test.js +529 -0
- package/test/tools/trim.test.js +419 -84
- package/test/tools/zoom.test.js +504 -0
- package/core/entities/alignedDimension.js +0 -279
- package/core/entities/angularDimension.js +0 -194
- package/core/entities/baseDimension.js +0 -227
- package/core/entities/block.js +0 -247
- package/core/entities/diametricDimension.js +0 -133
- package/core/entities/dimension.js +0 -202
- package/core/entities/radialDimension.js +0 -126
- package/core/styles/dimStyle.js +0 -719
- package/core/styles/dimStyleManager.js +0 -47
- package/core/styles/layer.js +0 -133
- package/core/styles/layerManager.js +0 -40
- package/core/styles/ltype.js +0 -86
- package/core/styles/ltypeManager.js +0 -50
- package/core/styles/style.js +0 -158
- package/core/styles/styleManager.js +0 -43
- package/core/styles/styleManagerBase.js +0 -299
- package/test/entities/block.test.js +0 -22
- package/test/styles/dimstyle.test.js +0 -19
- package/test/styles/layerManager.test.js +0 -150
- package/test/styles/style.test.js +0 -33
- package/test/styles/stylemanagerbase.test.js +0 -219
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { Strings } from '../lib/strings.js';
|
|
2
|
+
import { ChamferFilletBase } from './chamferFilletBase.js';
|
|
3
|
+
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
4
|
+
import { Logging } from '../lib/logging.js';
|
|
5
|
+
import { Constants } from '../lib/constants.js';
|
|
6
|
+
import { Line } from '../entities/line.js';
|
|
7
|
+
import { BasePolyline } from '../entities/basePolyline.js';
|
|
8
|
+
import { AddState, RemoveState, UpdateState } from '../lib/stateManager.js';
|
|
9
|
+
|
|
10
|
+
import { DesignCore } from '../designCore.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fillet Command Class
|
|
14
|
+
* @extends ChamferFilletBase
|
|
15
|
+
*/
|
|
16
|
+
export class Fillet extends ChamferFilletBase {
|
|
17
|
+
/**
|
|
18
|
+
* Register the command
|
|
19
|
+
* @return {Object}
|
|
20
|
+
* command = name of the command
|
|
21
|
+
* shortcut = shortcut for the command
|
|
22
|
+
* type = type to group command in toolbars (omitted if not shown)
|
|
23
|
+
*/
|
|
24
|
+
static register() {
|
|
25
|
+
const command = { command: 'Fillet', shortcut: 'F' }; // , type: 'Tool' };
|
|
26
|
+
return command;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute method
|
|
31
|
+
* executes the workflow, requesting input required to perform the command
|
|
32
|
+
*/
|
|
33
|
+
async execute() {
|
|
34
|
+
try {
|
|
35
|
+
this.#notifySettings();
|
|
36
|
+
|
|
37
|
+
while (true) {
|
|
38
|
+
const op1 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION], ['Radius', 'Trim']);
|
|
39
|
+
const input1 = await DesignCore.Scene.inputManager.requestInput(op1);
|
|
40
|
+
if (input1 === undefined) return;
|
|
41
|
+
|
|
42
|
+
if (Input.getType(input1) === Input.Type.STRING) {
|
|
43
|
+
if (input1 === 'Radius') {
|
|
44
|
+
const rop = new PromptOptions(Strings.Input.RADIUS, [Input.Type.NUMBER]);
|
|
45
|
+
const r = await DesignCore.Scene.inputManager.requestInput(rop);
|
|
46
|
+
if (r === undefined) return;
|
|
47
|
+
if (r < 0) {
|
|
48
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.INVALIDNUMBER}`);
|
|
49
|
+
} else {
|
|
50
|
+
DesignCore.Scene.headers.filletRadius = r;
|
|
51
|
+
}
|
|
52
|
+
this.#notifySettings();
|
|
53
|
+
} else if (input1 === 'Trim') {
|
|
54
|
+
const top = new PromptOptions(Strings.Input.OPTION, [], ['Trim', 'No trim']);
|
|
55
|
+
const trimInput = await DesignCore.Scene.inputManager.requestInput(top);
|
|
56
|
+
if (trimInput === undefined) return;
|
|
57
|
+
DesignCore.Scene.headers.trimMode = (trimInput === 'Trim');
|
|
58
|
+
this.#notifySettings();
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// input1 is a selection — validate and store first entity
|
|
64
|
+
const firstEntity = DesignCore.Scene.entities.get(input1.selectedItemIndex);
|
|
65
|
+
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
66
|
+
if (!(firstEntity instanceof Line) && !(firstEntity instanceof BasePolyline)) {
|
|
67
|
+
DesignCore.Core.notify(`${this.type} - ${firstEntity.type} ${Strings.Message.NOFILLET}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!this.firstPick.setPick(firstEntity, input1.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOFILLET}`)) continue;
|
|
71
|
+
|
|
72
|
+
// Prompt for second object
|
|
73
|
+
const op2 = new PromptOptions(Strings.Input.SELECT, [Input.Type.SINGLESELECTION]);
|
|
74
|
+
while (true) {
|
|
75
|
+
const input2 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
76
|
+
if (input2 === undefined) return;
|
|
77
|
+
|
|
78
|
+
const candidate = DesignCore.Scene.entities.get(input2.selectedItemIndex);
|
|
79
|
+
DesignCore.Scene.selectionManager.removeLastSelection();
|
|
80
|
+
if (!(candidate instanceof Line) && !(candidate instanceof BasePolyline)) {
|
|
81
|
+
DesignCore.Core.notify(`${this.type} - ${candidate.type} ${Strings.Message.NOFILLET}`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!this.secondPick.setPick(candidate, input2.selectedPoint, `${Strings.Strings.ARC} - ${Strings.Message.NOFILLET}`)) continue;
|
|
85
|
+
// If both selections are the same polyline, segments must be consecutive
|
|
86
|
+
// or they must be the open-end segments of an open polyline.
|
|
87
|
+
if (candidate === firstEntity && firstEntity instanceof BasePolyline) {
|
|
88
|
+
const isConsecutive = firstEntity.areConsecutiveSegments(this.firstPick.segmentIndex, this.secondPick.segmentIndex);
|
|
89
|
+
const lastIdx = firstEntity.points.length - 1;
|
|
90
|
+
const isOpenEnds = !firstEntity.flags.hasFlag(1) &&
|
|
91
|
+
((this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
|
|
92
|
+
(this.secondPick.segmentIndex === 1 && this.firstPick.segmentIndex === lastIdx));
|
|
93
|
+
if (!isConsecutive && !isOpenEnds) {
|
|
94
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Message.NONCONSECUTIVESEGMENTS}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Apply fillet then reset
|
|
102
|
+
DesignCore.Scene.inputManager.executeCommand();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
Logging.instance.error(`${this.type} - ${err}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Preview the command during execution
|
|
112
|
+
*/
|
|
113
|
+
preview() {
|
|
114
|
+
// No preview
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Perform the fillet
|
|
119
|
+
*/
|
|
120
|
+
action() {
|
|
121
|
+
if (!this.firstPick.entity || !this.secondPick.entity) return;
|
|
122
|
+
if (!this.resolveCornerGeometry(Strings.Message.NOFILLET)) return;
|
|
123
|
+
|
|
124
|
+
// radius = 0: trim/extend both lines to the sharp intersection with no arc
|
|
125
|
+
const filletRadius = DesignCore.Scene.headers.filletRadius;
|
|
126
|
+
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
127
|
+
if (filletRadius === 0) {
|
|
128
|
+
if (trimMode) {
|
|
129
|
+
const stateChanges = this.applySharpTrim();
|
|
130
|
+
DesignCore.Scene.commit(stateChanges);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Angle between the two clicked-side direction vectors (i.e. the opening angle of the chosen corner)
|
|
136
|
+
const firstUnit = this.firstPick.clickUnit(this.intersectionPoint);
|
|
137
|
+
const secondUnit = this.secondPick.clickUnit(this.intersectionPoint);
|
|
138
|
+
const cosAngle = Math.min(1, Math.max(-1, firstUnit.dot(secondUnit)));
|
|
139
|
+
const cornerAngle = Math.acos(cosAngle);
|
|
140
|
+
|
|
141
|
+
// Collinear or antiparallel lines cannot be filleted
|
|
142
|
+
if (cornerAngle < Constants.Tolerance.EPSILON || cornerAngle > Math.PI - Constants.Tolerance.EPSILON) {
|
|
143
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.PARALLELLINES}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Bisector unit vector: points into the chosen corner toward the fillet centre
|
|
148
|
+
const bisectorUnit = firstUnit.add(secondUnit).normalise();
|
|
149
|
+
|
|
150
|
+
// Distance from the intersection point to the fillet centre along the bisector
|
|
151
|
+
const intersectToCentreDistance = filletRadius / Math.sin(cornerAngle / 2);
|
|
152
|
+
|
|
153
|
+
// Fillet arc centre point
|
|
154
|
+
const arcCentre = this.intersectionPoint.add(bisectorUnit.scale(intersectToCentreDistance));
|
|
155
|
+
|
|
156
|
+
// Tangent points where the fillet arc meets each line (foot of perpendicular from centre to line)
|
|
157
|
+
const firstTangentPoint = arcCentre.perpendicular(this.firstPick.lineStart, this.firstPick.lineEnd);
|
|
158
|
+
const secondTangentPoint = arcCentre.perpendicular(this.secondPick.lineStart, this.secondPick.lineEnd);
|
|
159
|
+
|
|
160
|
+
// Verify the tangent points are reachable — same direction as the kept end from the
|
|
161
|
+
// intersection and not farther than the kept end (which would flip the line direction).
|
|
162
|
+
if (trimMode) {
|
|
163
|
+
const firstKeptDir = this.firstPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
|
|
164
|
+
const secondKeptDir = this.secondPick.lineKeptEnd(this.intersectionPoint).subtract(this.intersectionPoint);
|
|
165
|
+
const firstTangentDot = firstTangentPoint.subtract(this.intersectionPoint).dot(firstKeptDir);
|
|
166
|
+
const secondTangentDot = secondTangentPoint.subtract(this.intersectionPoint).dot(secondKeptDir);
|
|
167
|
+
if (firstTangentDot < 0 || firstTangentDot > firstKeptDir.dot(firstKeptDir) ||
|
|
168
|
+
secondTangentDot < 0 || secondTangentDot > secondKeptDir.dot(secondKeptDir)) {
|
|
169
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ERROR}:${Strings.Error.RADIUSTOOLARGE}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Determine arc winding direction: if the cross product of the two centre-to-tangent
|
|
175
|
+
// vectors is positive, the short arc from firstTangentPoint to secondTangentPoint
|
|
176
|
+
// travels CCW (direction = 1), otherwise CW (direction = -1).
|
|
177
|
+
const centreToFirst = firstTangentPoint.subtract(arcCentre);
|
|
178
|
+
const centreToSecond = secondTangentPoint.subtract(arcCentre);
|
|
179
|
+
const windingCross = centreToFirst.cross(centreToSecond);
|
|
180
|
+
const arcDirection = windingCross > 0 ? 1 : -1;
|
|
181
|
+
|
|
182
|
+
const arc = DesignCore.CommandManager.createNew('Arc', {
|
|
183
|
+
points: [arcCentre, firstTangentPoint, secondTangentPoint],
|
|
184
|
+
direction: arcDirection,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
|
|
188
|
+
const secondIsPolyline = this.secondPick.entity instanceof BasePolyline;
|
|
189
|
+
|
|
190
|
+
if (!trimMode) {
|
|
191
|
+
DesignCore.Scene.commit([new AddState(arc)]);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!firstIsPolyline && !secondIsPolyline) {
|
|
196
|
+
const stateChanges = this.#trimLineAndLine(firstTangentPoint, secondTangentPoint, arc);
|
|
197
|
+
DesignCore.Scene.commit(stateChanges);
|
|
198
|
+
} else if (firstIsPolyline && secondIsPolyline && this.firstPick.entity === this.secondPick.entity) {
|
|
199
|
+
const stateChanges = this.#trimPolyAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre, arc);
|
|
200
|
+
DesignCore.Scene.commit(stateChanges);
|
|
201
|
+
} else {
|
|
202
|
+
const stateChanges = this.#trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre);
|
|
203
|
+
DesignCore.Scene.commit(stateChanges);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Apply trim and arc for a Line + Line fillet.
|
|
209
|
+
* @param {Point} firstTangentPoint
|
|
210
|
+
* @param {Point} secondTangentPoint
|
|
211
|
+
* @param {Arc} arc
|
|
212
|
+
* @return {Array}
|
|
213
|
+
*/
|
|
214
|
+
#trimLineAndLine(firstTangentPoint, secondTangentPoint, arc) {
|
|
215
|
+
return [
|
|
216
|
+
new AddState(arc),
|
|
217
|
+
new UpdateState(this.firstPick.entity, { points: [this.firstPick.lineKeptEnd(this.intersectionPoint), firstTangentPoint] }),
|
|
218
|
+
new UpdateState(this.secondPick.entity, { points: [this.secondPick.lineKeptEnd(this.intersectionPoint), secondTangentPoint] }),
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Apply trim and arc for a Polyline + Polyline (same entity) fillet.
|
|
224
|
+
* Handles both the open-ends case and the consecutive-segments case.
|
|
225
|
+
* @param {Point} firstTangentPoint
|
|
226
|
+
* @param {Point} secondTangentPoint
|
|
227
|
+
* @param {number} arcDirection
|
|
228
|
+
* @param {Point} arcCentre
|
|
229
|
+
* @param {Arc} arc
|
|
230
|
+
* @return {Array}
|
|
231
|
+
*/
|
|
232
|
+
#trimPolyAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre, arc) {
|
|
233
|
+
const closeSegIdx = this.firstPick.entity.points.length;
|
|
234
|
+
const lastIdx = closeSegIdx - 1;
|
|
235
|
+
const segDiff = Math.abs(this.firstPick.segmentIndex - this.secondPick.segmentIndex);
|
|
236
|
+
const isOpenEnds = !this.firstPick.entity.flags.hasFlag(1) && segDiff !== 1 && (
|
|
237
|
+
(this.firstPick.segmentIndex === 1 && this.secondPick.segmentIndex === lastIdx) ||
|
|
238
|
+
(this.firstPick.segmentIndex === lastIdx && this.secondPick.segmentIndex === 1)
|
|
239
|
+
);
|
|
240
|
+
const newPoints = this.firstPick.entity.points.map((p) => p.clone());
|
|
241
|
+
const stateChanges = [];
|
|
242
|
+
|
|
243
|
+
if (isOpenEnds) {
|
|
244
|
+
stateChanges.push(new AddState(arc));
|
|
245
|
+
if (this.firstPick.segmentIndex === 1) {
|
|
246
|
+
newPoints[0] = firstTangentPoint.clone();
|
|
247
|
+
newPoints[lastIdx] = secondTangentPoint.clone();
|
|
248
|
+
} else {
|
|
249
|
+
newPoints[0] = secondTangentPoint.clone();
|
|
250
|
+
newPoints[lastIdx] = firstTangentPoint.clone();
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
const seg1 = this.firstPick.segmentIndex;
|
|
254
|
+
const seg2 = this.secondPick.segmentIndex;
|
|
255
|
+
const isClosingWrap = (seg1 === closeSegIdx && seg2 === 1) || (seg2 === closeSegIdx && seg1 === 1);
|
|
256
|
+
const isFirstLower = seg1 < seg2;
|
|
257
|
+
const cornerIdx = isClosingWrap ? 0 : Math.min(seg1, seg2);
|
|
258
|
+
const lowerTangent = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? secondTangentPoint : firstTangentPoint;
|
|
259
|
+
const upperTangent = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? firstTangentPoint : secondTangentPoint;
|
|
260
|
+
const polyArcDir = (isClosingWrap ? seg1 === closeSegIdx : !isFirstLower) ? -arcDirection : arcDirection;
|
|
261
|
+
const startAngle = arcCentre.angle(lowerTangent);
|
|
262
|
+
const endAngle = arcCentre.angle(upperTangent);
|
|
263
|
+
const includedAngle = ((endAngle - startAngle) * polyArcDir + 4 * Math.PI) % (2 * Math.PI);
|
|
264
|
+
const bulge = polyArcDir * Math.tan(includedAngle / 4);
|
|
265
|
+
const arcStartPoint = lowerTangent.clone();
|
|
266
|
+
arcStartPoint.bulge = bulge;
|
|
267
|
+
newPoints.splice(cornerIdx, 1, arcStartPoint, upperTangent.clone());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
stateChanges.push(new UpdateState(this.firstPick.entity, { points: newPoints }));
|
|
271
|
+
return stateChanges;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Apply trim and arc for a Line + Polyline (or Polyline + Line) fillet.
|
|
276
|
+
* The line is consumed into the polyline with the arc encoded as a bulge.
|
|
277
|
+
* @param {Point} firstTangentPoint
|
|
278
|
+
* @param {Point} secondTangentPoint
|
|
279
|
+
* @param {number} arcDirection
|
|
280
|
+
* @param {Point} arcCentre
|
|
281
|
+
* @return {Array}
|
|
282
|
+
*/
|
|
283
|
+
#trimLineAndPoly(firstTangentPoint, secondTangentPoint, arcDirection, arcCentre) {
|
|
284
|
+
const firstIsPolyline = this.firstPick.entity instanceof BasePolyline;
|
|
285
|
+
const [poly, line] = firstIsPolyline ? [this.firstPick, this.secondPick] : [this.secondPick, this.firstPick];
|
|
286
|
+
const lineTangentPoint = line === this.firstPick ? firstTangentPoint : secondTangentPoint;
|
|
287
|
+
const polyTangentPoint = poly === this.firstPick ? firstTangentPoint : secondTangentPoint;
|
|
288
|
+
const polyToLineDir = firstIsPolyline ? arcDirection : -arcDirection;
|
|
289
|
+
|
|
290
|
+
const startAngle = arcCentre.angle(polyTangentPoint);
|
|
291
|
+
const endAngle = arcCentre.angle(lineTangentPoint);
|
|
292
|
+
const includedAngle = ((endAngle - startAngle) * polyToLineDir + 4 * Math.PI) % (2 * Math.PI);
|
|
293
|
+
const bulge = polyToLineDir * Math.tan(includedAngle / 4);
|
|
294
|
+
const arcStartPoint = polyTangentPoint.clone();
|
|
295
|
+
arcStartPoint.bulge = bulge;
|
|
296
|
+
|
|
297
|
+
const polySegIdx = poly.segmentIndex;
|
|
298
|
+
const keepStart = poly.keepStart(this.intersectionPoint);
|
|
299
|
+
let newPoints;
|
|
300
|
+
if (keepStart) {
|
|
301
|
+
// Keep the polyline points up to (not including) the selected segment start,
|
|
302
|
+
// then the arc start (with bulge), the line tangent point, and the kept end of the line.
|
|
303
|
+
newPoints = [
|
|
304
|
+
...poly.entity.points.slice(0, polySegIdx).map((p) => p.clone()),
|
|
305
|
+
arcStartPoint,
|
|
306
|
+
lineTangentPoint.clone(),
|
|
307
|
+
line.lineKeptEnd(this.intersectionPoint).clone(),
|
|
308
|
+
];
|
|
309
|
+
} else {
|
|
310
|
+
// Keep the polyline points from the selected segment start onwards,
|
|
311
|
+
// prepending the kept end of the line, the reversed arc start (with negated bulge), and the poly tangent point.
|
|
312
|
+
const revArcStartPoint = lineTangentPoint.clone();
|
|
313
|
+
revArcStartPoint.bulge = -bulge;
|
|
314
|
+
newPoints = [
|
|
315
|
+
line.lineKeptEnd(this.intersectionPoint).clone(),
|
|
316
|
+
revArcStartPoint,
|
|
317
|
+
polyTangentPoint.clone(),
|
|
318
|
+
...poly.entity.points.slice(polySegIdx).map((p) => p.clone()),
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return [
|
|
323
|
+
new RemoveState(line.entity),
|
|
324
|
+
new UpdateState(poly.entity, { points: newPoints }),
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Display the current fillet settings to the command line
|
|
330
|
+
*/
|
|
331
|
+
#notifySettings() {
|
|
332
|
+
const filletRadius = DesignCore.Scene.headers.filletRadius;
|
|
333
|
+
const trimMode = DesignCore.Scene.headers.trimMode;
|
|
334
|
+
DesignCore.Core.notify(`Current settings: Mode = ${trimMode ? 'TRIM' : 'NOTRIM'}, Radius = ${filletRadius}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
package/core/tools/identify.js
CHANGED
|
@@ -1,24 +1,41 @@
|
|
|
1
|
-
import {Strings} from '../lib/strings.js';
|
|
2
|
-
import {Tool} from './tool.js';
|
|
3
|
-
import {Input, PromptOptions} from '../lib/inputManager.js';
|
|
4
|
-
import {Logging} from '../lib/logging.js';
|
|
1
|
+
import { Strings } from '../lib/strings.js';
|
|
2
|
+
import { Tool } from './tool.js';
|
|
3
|
+
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
4
|
+
import { Logging } from '../lib/logging.js';
|
|
5
5
|
|
|
6
|
-
import {DesignCore} from '../designCore.js';
|
|
6
|
+
import { DesignCore } from '../designCore.js';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Identity Command Class
|
|
10
|
+
* @extends Tool
|
|
11
|
+
*/
|
|
8
12
|
export class Identify extends Tool {
|
|
13
|
+
/** Create an Identify command */
|
|
9
14
|
constructor() {
|
|
10
15
|
super();
|
|
11
16
|
}
|
|
12
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Register the command
|
|
20
|
+
* @return {Object}
|
|
21
|
+
* command = name of the command
|
|
22
|
+
* shortcut = shortcut for the command
|
|
23
|
+
* type = type to group command in toolbars (omitted if not shown)
|
|
24
|
+
*/
|
|
13
25
|
static register() {
|
|
14
|
-
const command = {command: 'Identify', shortcut: 'ID'};
|
|
26
|
+
const command = { command: 'Identify', shortcut: 'ID' };
|
|
15
27
|
return command;
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Execute method
|
|
32
|
+
* executes the workflow, requesting input required to perform the command
|
|
33
|
+
*/
|
|
18
34
|
async execute() {
|
|
19
35
|
try {
|
|
20
36
|
const op = new PromptOptions(Strings.Input.POINT, [Input.Type.POINT]);
|
|
21
37
|
const pt1 = await DesignCore.Scene.inputManager.requestInput(op);
|
|
38
|
+
if (pt1 === undefined) return;
|
|
22
39
|
this.points.push(pt1);
|
|
23
40
|
|
|
24
41
|
DesignCore.Scene.inputManager.executeCommand();
|
|
@@ -27,6 +44,16 @@ export class Identify extends Tool {
|
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Preview the command during execution
|
|
49
|
+
*/
|
|
50
|
+
preview() {
|
|
51
|
+
// No Preview
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Perform the command
|
|
56
|
+
*/
|
|
30
57
|
action() {
|
|
31
58
|
const x = this.points.at(-1).x.toFixed(1);
|
|
32
59
|
const y = this.points.at(-1).y.toFixed(1);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Tool } from './tool.js';
|
|
2
|
+
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
3
|
+
import { Strings } from '../lib/strings.js';
|
|
4
|
+
import { Logging } from '../lib/logging.js';
|
|
5
|
+
import { UpdateState } from '../lib/stateManager.js';
|
|
6
|
+
|
|
7
|
+
import { DesignCore } from '../designCore.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MatchProp Command Class
|
|
11
|
+
* Provides: All, Extents, Window
|
|
12
|
+
* @extends Tool
|
|
13
|
+
*/
|
|
14
|
+
export class MatchProp extends Tool {
|
|
15
|
+
/** Create a MatchProp command */
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
// index of source item
|
|
19
|
+
this.sourceIndex = null;
|
|
20
|
+
// array of destination item indices
|
|
21
|
+
this.destinationSetIndices = [];
|
|
22
|
+
// properties to match
|
|
23
|
+
this.properties = ['colour', 'layer', 'lineType', 'lineWidth', 'height', 'styleName', 'dimensionStyle'];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register the command
|
|
28
|
+
* @return {Object}
|
|
29
|
+
*/
|
|
30
|
+
static register() {
|
|
31
|
+
const command = { command: 'MatchProp', shortcut: 'MA' };
|
|
32
|
+
return command;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Execute method
|
|
37
|
+
*/
|
|
38
|
+
async execute() {
|
|
39
|
+
try {
|
|
40
|
+
if (DesignCore.Scene.selectionManager.selectionSet.selectionSet.length === 1) {
|
|
41
|
+
// use selected item as source if only one selected
|
|
42
|
+
const selections = DesignCore.Scene.selectionManager.selectionSet.selectionSet.slice();
|
|
43
|
+
this.sourceIndex = selections[0];
|
|
44
|
+
} else {
|
|
45
|
+
// prompt for source item
|
|
46
|
+
DesignCore.Scene.selectionManager.reset();
|
|
47
|
+
const sourceSelection = new PromptOptions(Strings.Input.SOURCE, [Input.Type.SINGLESELECTION], []);
|
|
48
|
+
const input = await DesignCore.Scene.inputManager.requestInput(sourceSelection);
|
|
49
|
+
if (input === undefined) return;
|
|
50
|
+
this.sourceIndex = input.selectedItemIndex;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const destinationSelection = new PromptOptions(Strings.Input.DESTINATIONSET, [Input.Type.SELECTIONSET]);
|
|
54
|
+
const destInput = await DesignCore.Scene.inputManager.requestInput(destinationSelection);
|
|
55
|
+
if (destInput === undefined) return;
|
|
56
|
+
this.destinationSetIndices = destInput.selectionSet;
|
|
57
|
+
|
|
58
|
+
DesignCore.Scene.inputManager.executeCommand();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
Logging.instance.error(`${this.type} - ${err}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Perform the command
|
|
66
|
+
*/
|
|
67
|
+
action() {
|
|
68
|
+
// MatchProp
|
|
69
|
+
|
|
70
|
+
// return if no source or destination set
|
|
71
|
+
if (this.sourceIndex === null || this.destinationSetIndices.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// get source item
|
|
76
|
+
const sourceItem = DesignCore.Scene.entities.get(this.sourceIndex);
|
|
77
|
+
|
|
78
|
+
const stateChanges = [];
|
|
79
|
+
|
|
80
|
+
// loop through destination set
|
|
81
|
+
for (let i = 0; i < this.destinationSetIndices.length; i++) {
|
|
82
|
+
const targetItem = DesignCore.Scene.entities.get(this.destinationSetIndices[i]);
|
|
83
|
+
const propertySet = {};
|
|
84
|
+
// loop through properties and match
|
|
85
|
+
for (let p = 0; p < this.properties.length; p++) {
|
|
86
|
+
// check property exists on both items
|
|
87
|
+
const prop = this.properties[p];
|
|
88
|
+
if (sourceItem.hasOwnProperty(prop)) {
|
|
89
|
+
propertySet[prop] = sourceItem[prop];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// only add state change if there are properties to change
|
|
94
|
+
if (Object.keys(propertySet).length > 0) {
|
|
95
|
+
const stateChange = new UpdateState(targetItem, propertySet);
|
|
96
|
+
stateChanges.push(stateChange);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// apply the updates
|
|
100
|
+
if (stateChanges.length > 0) {
|
|
101
|
+
DesignCore.Scene.commit(stateChanges);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Utils } from '../lib/utils.js';
|
|
2
|
+
import { Strings } from '../lib/strings.js';
|
|
3
|
+
import { Tool } from './tool.js';
|
|
4
|
+
import { Input, PromptOptions } from '../lib/inputManager.js';
|
|
5
|
+
import { Logging } from '../lib/logging.js';
|
|
6
|
+
import { Point } from '../entities/point.js';
|
|
7
|
+
import { AddState, UpdateState } from '../lib/stateManager.js';
|
|
8
|
+
|
|
9
|
+
import { DesignCore } from '../designCore.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mirror Command Class
|
|
13
|
+
* @extends Tool
|
|
14
|
+
*/
|
|
15
|
+
export class Mirror extends Tool {
|
|
16
|
+
/** Create a Mirror command */
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.eraseSource = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register the command
|
|
24
|
+
* @return {Object}
|
|
25
|
+
* command = name of the command
|
|
26
|
+
* shortcut = shortcut for the command
|
|
27
|
+
* type = type to group command in toolbars (omitted if not shown)
|
|
28
|
+
*/
|
|
29
|
+
static register() {
|
|
30
|
+
const command = { command: 'Mirror', shortcut: 'MI' }; // , type: 'Tool' };
|
|
31
|
+
return command;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Execute method
|
|
36
|
+
* executes the workflow, requesting input required to perform the command
|
|
37
|
+
*/
|
|
38
|
+
async execute() {
|
|
39
|
+
try {
|
|
40
|
+
const op = new PromptOptions(Strings.Input.SELECTIONSET, [Input.Type.SELECTIONSET]);
|
|
41
|
+
|
|
42
|
+
if (!DesignCore.Scene.selectionManager.selectionSet.selectionSet.length) {
|
|
43
|
+
await DesignCore.Scene.inputManager.requestInput(op);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const op2 = new PromptOptions(Strings.Input.MIRRORFIRST, [Input.Type.POINT]);
|
|
47
|
+
const pt1 = await DesignCore.Scene.inputManager.requestInput(op2);
|
|
48
|
+
if (pt1 === undefined) return;
|
|
49
|
+
this.points.push(pt1);
|
|
50
|
+
|
|
51
|
+
const op3 = new PromptOptions(Strings.Input.MIRRORSECOND, [Input.Type.POINT]);
|
|
52
|
+
const pt2 = await DesignCore.Scene.inputManager.requestInput(op3);
|
|
53
|
+
if (pt2 === undefined) return;
|
|
54
|
+
this.points.push(pt2);
|
|
55
|
+
|
|
56
|
+
const op4 = new PromptOptions(Strings.Input.ERASESOURCE, [], ['Yes', 'No'], 'N');
|
|
57
|
+
const eraseInput = await DesignCore.Scene.inputManager.requestInput(op4);
|
|
58
|
+
this.eraseSource = (eraseInput === 'Yes');
|
|
59
|
+
|
|
60
|
+
DesignCore.Scene.inputManager.executeCommand();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
Logging.instance.error(`${this.type} - ${err}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Preview the command during execution
|
|
68
|
+
*/
|
|
69
|
+
preview() {
|
|
70
|
+
if (this.points.length >= 1) {
|
|
71
|
+
const mousePoint = DesignCore.Mouse.pointOnScene();
|
|
72
|
+
const pt1 = this.points[0];
|
|
73
|
+
const pt2 = this.points.length >= 2 ? this.points[1] : mousePoint;
|
|
74
|
+
|
|
75
|
+
// Draw the mirror line
|
|
76
|
+
DesignCore.Scene.tempEntities.create('Line', { points: [pt1, pt2] });
|
|
77
|
+
|
|
78
|
+
// Preview mirrored entities (skip if mirror line has zero length)
|
|
79
|
+
if (!pt1.isSame(pt2)) {
|
|
80
|
+
for (let i = 0; i < DesignCore.Scene.selectionManager.selectedItems.length; i++) {
|
|
81
|
+
const item = DesignCore.Scene.selectionManager.selectedItems[i];
|
|
82
|
+
const entityIndex = DesignCore.Scene.selectionManager.selectionSet.selectionSet[i];
|
|
83
|
+
const originalItem = DesignCore.Scene.entities.get(entityIndex);
|
|
84
|
+
item.setProperty('points', this.getMirroredPoints(originalItem.points, pt1, pt2));
|
|
85
|
+
if (originalItem.direction !== undefined) {
|
|
86
|
+
item.setProperty('direction', -originalItem.direction);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Perform the command
|
|
95
|
+
*/
|
|
96
|
+
action() {
|
|
97
|
+
const pt1 = this.points[0];
|
|
98
|
+
const pt2 = this.points[1];
|
|
99
|
+
|
|
100
|
+
if (pt1.isSame(pt2)) {
|
|
101
|
+
DesignCore.Core.notify(`${this.type} - ${Strings.Error.ZEROLENGTH}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const stateChanges = [];
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < DesignCore.Scene.selectionManager.selectionSet.selectionSet.length; i++) {
|
|
108
|
+
const entityIndex = DesignCore.Scene.selectionManager.selectionSet.selectionSet[i];
|
|
109
|
+
const item = DesignCore.Scene.entities.get(entityIndex);
|
|
110
|
+
const mirroredPoints = this.getMirroredPoints(item.points, pt1, pt2);
|
|
111
|
+
|
|
112
|
+
if (this.eraseSource) {
|
|
113
|
+
// Replace the original with its mirrored version
|
|
114
|
+
const updates = { points: mirroredPoints };
|
|
115
|
+
if (item.direction !== undefined) updates.direction = -item.direction;
|
|
116
|
+
const stateChange = new UpdateState(item, updates);
|
|
117
|
+
stateChanges.push(stateChange);
|
|
118
|
+
} else {
|
|
119
|
+
// Keep the original and add a mirrored copy
|
|
120
|
+
const copyOfItem = Utils.cloneObject(item);
|
|
121
|
+
copyOfItem.setProperty('points', mirroredPoints);
|
|
122
|
+
if (copyOfItem.direction !== undefined) copyOfItem.setProperty('direction', -copyOfItem.direction);
|
|
123
|
+
const stateChange = new AddState(copyOfItem);
|
|
124
|
+
stateChanges.push(stateChange);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
DesignCore.Scene.commit(stateChanges);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get mirrored points across the line defined by pt1 and pt2
|
|
133
|
+
* @param {Array} points
|
|
134
|
+
* @param {Point} pt1 - first point on the mirror line
|
|
135
|
+
* @param {Point} pt2 - second point on the mirror line
|
|
136
|
+
* @return {Array} mirroredPoints
|
|
137
|
+
*/
|
|
138
|
+
getMirroredPoints(points, pt1, pt2) {
|
|
139
|
+
return points.map((p) => new Point(p.x, p.y, p.bulge, p.sequence).mirror(pt1, pt2));
|
|
140
|
+
}
|
|
141
|
+
}
|