@itwin/editor-frontend 4.0.0-dev.8 → 4.0.0-dev.81

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/lib/cjs/CreateElementTool.d.ts +100 -100
  3. package/lib/cjs/CreateElementTool.js +325 -327
  4. package/lib/cjs/CreateElementTool.js.map +1 -1
  5. package/lib/cjs/DeleteElementsTool.d.ts +13 -13
  6. package/lib/cjs/DeleteElementsTool.js +38 -38
  7. package/lib/cjs/DeleteElementsTool.js.map +1 -1
  8. package/lib/cjs/EditTool.d.ts +38 -38
  9. package/lib/cjs/EditTool.js +80 -81
  10. package/lib/cjs/EditTool.js.map +1 -1
  11. package/lib/cjs/EditToolIpc.d.ts +18 -18
  12. package/lib/cjs/EditToolIpc.js +28 -28
  13. package/lib/cjs/ElementGeometryTool.d.ts +147 -147
  14. package/lib/cjs/ElementGeometryTool.js +704 -709
  15. package/lib/cjs/ElementGeometryTool.js.map +1 -1
  16. package/lib/cjs/ModifyCurveTools.d.ts +139 -139
  17. package/lib/cjs/ModifyCurveTools.js +776 -784
  18. package/lib/cjs/ModifyCurveTools.js.map +1 -1
  19. package/lib/cjs/ModifyElementTool.d.ts +47 -47
  20. package/lib/cjs/ModifyElementTool.js +177 -177
  21. package/lib/cjs/ModifyElementTool.js.map +1 -1
  22. package/lib/cjs/ProjectLocation/ProjectExtentsDecoration.d.ts +135 -135
  23. package/lib/cjs/ProjectLocation/ProjectExtentsDecoration.js +822 -822
  24. package/lib/cjs/ProjectLocation/ProjectExtentsDecoration.js.map +1 -1
  25. package/lib/cjs/ProjectLocation/ProjectGeolocation.d.ts +135 -135
  26. package/lib/cjs/ProjectLocation/ProjectGeolocation.js +532 -532
  27. package/lib/cjs/ProjectLocation/ProjectGeolocation.js.map +1 -1
  28. package/lib/cjs/SketchTools.d.ts +304 -304
  29. package/lib/cjs/SketchTools.js +1704 -1705
  30. package/lib/cjs/SketchTools.js.map +1 -1
  31. package/lib/cjs/SolidModelingTools.d.ts +380 -380
  32. package/lib/cjs/SolidModelingTools.js +1452 -1462
  33. package/lib/cjs/SolidModelingTools.js.map +1 -1
  34. package/lib/cjs/SolidPrimitiveTools.d.ts +318 -318
  35. package/lib/cjs/SolidPrimitiveTools.js +1372 -1378
  36. package/lib/cjs/SolidPrimitiveTools.js.map +1 -1
  37. package/lib/cjs/TransformElementsTool.d.ts +164 -164
  38. package/lib/cjs/TransformElementsTool.js +652 -652
  39. package/lib/cjs/TransformElementsTool.js.map +1 -1
  40. package/lib/cjs/UndoRedoTool.d.ts +16 -16
  41. package/lib/cjs/UndoRedoTool.js +41 -42
  42. package/lib/cjs/UndoRedoTool.js.map +1 -1
  43. package/lib/cjs/editor-frontend.d.ts +17 -17
  44. package/lib/cjs/editor-frontend.js +37 -33
  45. package/lib/cjs/editor-frontend.js.map +1 -1
  46. package/lib/esm/CreateElementTool.d.ts +100 -100
  47. package/lib/esm/CreateElementTool.js +317 -319
  48. package/lib/esm/CreateElementTool.js.map +1 -1
  49. package/lib/esm/DeleteElementsTool.d.ts +13 -13
  50. package/lib/esm/DeleteElementsTool.js +35 -34
  51. package/lib/esm/DeleteElementsTool.js.map +1 -1
  52. package/lib/esm/EditTool.d.ts +38 -38
  53. package/lib/esm/EditTool.js +77 -77
  54. package/lib/esm/EditTool.js.map +1 -1
  55. package/lib/esm/EditToolIpc.d.ts +18 -18
  56. package/lib/esm/EditToolIpc.js +24 -24
  57. package/lib/esm/ElementGeometryTool.d.ts +147 -147
  58. package/lib/esm/ElementGeometryTool.js +696 -701
  59. package/lib/esm/ElementGeometryTool.js.map +1 -1
  60. package/lib/esm/ModifyCurveTools.d.ts +139 -139
  61. package/lib/esm/ModifyCurveTools.js +771 -775
  62. package/lib/esm/ModifyCurveTools.js.map +1 -1
  63. package/lib/esm/ModifyElementTool.d.ts +47 -47
  64. package/lib/esm/ModifyElementTool.js +172 -172
  65. package/lib/esm/ModifyElementTool.js.map +1 -1
  66. package/lib/esm/ProjectLocation/ProjectExtentsDecoration.d.ts +135 -135
  67. package/lib/esm/ProjectLocation/ProjectExtentsDecoration.js +818 -814
  68. package/lib/esm/ProjectLocation/ProjectExtentsDecoration.js.map +1 -1
  69. package/lib/esm/ProjectLocation/ProjectGeolocation.d.ts +135 -135
  70. package/lib/esm/ProjectLocation/ProjectGeolocation.js +529 -526
  71. package/lib/esm/ProjectLocation/ProjectGeolocation.js.map +1 -1
  72. package/lib/esm/SketchTools.d.ts +304 -304
  73. package/lib/esm/SketchTools.js +1700 -1695
  74. package/lib/esm/SketchTools.js.map +1 -1
  75. package/lib/esm/SolidModelingTools.d.ts +380 -380
  76. package/lib/esm/SolidModelingTools.js +1444 -1437
  77. package/lib/esm/SolidModelingTools.js.map +1 -1
  78. package/lib/esm/SolidPrimitiveTools.d.ts +318 -318
  79. package/lib/esm/SolidPrimitiveTools.js +1368 -1367
  80. package/lib/esm/SolidPrimitiveTools.js.map +1 -1
  81. package/lib/esm/TransformElementsTool.d.ts +164 -164
  82. package/lib/esm/TransformElementsTool.js +647 -644
  83. package/lib/esm/TransformElementsTool.js.map +1 -1
  84. package/lib/esm/UndoRedoTool.d.ts +16 -16
  85. package/lib/esm/UndoRedoTool.js +38 -36
  86. package/lib/esm/UndoRedoTool.js.map +1 -1
  87. package/lib/esm/editor-frontend.d.ts +17 -17
  88. package/lib/esm/editor-frontend.js +21 -21
  89. package/package.json +20 -20
@@ -1,645 +1,648 @@
1
- /*---------------------------------------------------------------------------------------------
2
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
- * See LICENSE.md in the project root for license terms and full copyright notice.
4
- *--------------------------------------------------------------------------------------------*/
5
- import { DialogProperty, PropertyDescriptionHelper, PropertyEditorParamTypes, } from "@itwin/appui-abstract";
6
- import { BentleyError, Id64 } from "@itwin/core-bentley";
7
- import { Code, ColorDef, IModelStatus, isPlacement2dProps, LinePixels, Placement2d, Placement3d, } from "@itwin/core-common";
8
- import { AccuDrawHintBuilder, AngleDescription, CoreTools, ElementSetTool, GraphicBranch, GraphicType, IModelApp, IpcApp, ModifyElementSource, NotifyMessageDetails, OutputMessagePriority, readElementGraphics, } from "@itwin/core-frontend";
9
- import { Angle, Geometry, Matrix3d, Point3d, Transform, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
10
- import { editorBuiltInCmdIds } from "@itwin/editor-common";
11
- import { EditTools } from "./EditTool";
12
- import { basicManipulationIpc } from "./EditToolIpc";
13
- /** @alpha */
14
- export class TransformGraphicsProvider {
15
- constructor(iModel, prefix) {
16
- /** Chord tolerance to use to stroke the element's geometry in meters. */
17
- this.chordTolerance = 0.01;
18
- this.iModel = iModel;
19
- this.prefix = prefix;
20
- this.data = new Array();
21
- this.pending = new Map();
22
- }
23
- getRequestId(id) { return `${this.prefix}-${id}`; }
24
- getToleranceLog10() { return Math.floor(Math.log10(this.chordTolerance)); }
25
- async createRequest(id) {
26
- const elementProps = (await this.iModel.elements.getProps(id));
27
- if (0 === elementProps.length)
28
- return;
29
- const placementProps = elementProps[0].placement;
30
- if (undefined === placementProps)
31
- return;
32
- const placement = isPlacement2dProps(placementProps) ? Placement2d.fromJSON(placementProps) : Placement3d.fromJSON(placementProps);
33
- if (!placement.isValid)
34
- return; // Ignore assembly parents w/o geometry, etc...
35
- const requestProps = {
36
- id: this.getRequestId(id),
37
- elementId: id,
38
- toleranceLog10: this.getToleranceLog10(),
39
- };
40
- this.pending.set(id, requestProps.id); // keep track of requests so they can be cancelled...
41
- const graphicData = await IModelApp.tileAdmin.requestElementGraphics(this.iModel, requestProps);
42
- if (undefined === graphicData)
43
- return;
44
- const graphic = await readElementGraphics(graphicData, this.iModel, elementProps[0].model, placement.is3d, { noFlash: true, noHilite: true });
45
- if (undefined === graphic)
46
- return;
47
- return { id, placement, graphic: IModelApp.renderSystem.createGraphicOwner(graphic) };
48
- }
49
- disposeOfGraphics() {
50
- this.data.forEach((data) => {
51
- data.graphic.disposeGraphic();
52
- });
53
- this.data.length = 0;
54
- }
55
- async cancelPendingRequests() {
56
- const requests = new Array();
57
- for (const [_key, id] of this.pending)
58
- requests.push(id);
59
- this.pending.clear();
60
- if (0 === requests.length)
61
- return;
62
- return IpcApp.appFunctionIpc.cancelElementGraphicsRequests(this.iModel.key, requests);
63
- }
64
- /** Call to request a RenderGraphic for the supplied element id.
65
- * @see [[cleanupGraphics]] Must be called when the tool exits.
66
- */
67
- async createSingleGraphic(id) {
68
- try {
69
- const info = await this.createRequest(id);
70
- if (undefined !== (info === null || info === void 0 ? void 0 : info.id))
71
- this.pending.delete(info.id);
72
- if (undefined === (info === null || info === void 0 ? void 0 : info.graphic))
73
- return false;
74
- this.data.push(info);
75
- return true;
76
- }
77
- catch {
78
- return false;
79
- }
80
- }
81
- /** Call to request RenderGraphics for the supplied element ids. Does not wait for results as
82
- * generating graphics for a large number of elements can take time. Instead an array of [[RenderGraphicOwner]]
83
- * is populated as requests are resolved and the current dynamics frame displays what is available.
84
- * @see [[cleanupGraphics]] Must be called when the tool exits.
85
- */
86
- createGraphics(elements) {
87
- if (0 === Id64.sizeOf(elements))
88
- return;
89
- try {
90
- for (const id of Id64.iterable(elements)) {
91
- const promise = this.createRequest(id);
92
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
93
- promise.then((info) => {
94
- if (undefined !== (info === null || info === void 0 ? void 0 : info.id))
95
- this.pending.delete(info.id);
96
- if (undefined !== (info === null || info === void 0 ? void 0 : info.graphic))
97
- this.data.push(info);
98
- });
99
- }
100
- }
101
- catch { }
102
- }
103
- /** Call to dispose of [[RenderGraphic]] held by [[RenderGraphicOwner]] and cancel requests that are still pending.
104
- * @note Must be called when the tool exits to avoid leaks of graphics memory or other webgl resources.
105
- */
106
- async cleanupGraphics() {
107
- await this.cancelPendingRequests();
108
- this.disposeOfGraphics();
109
- }
110
- addSingleGraphic(graphic, transform, context) {
111
- const branch = new GraphicBranch(false);
112
- branch.add(graphic);
113
- const branchGraphic = context.createBranch(branch, transform);
114
- context.addGraphic(branchGraphic);
115
- }
116
- addGraphics(transform, context) {
117
- if (0 === this.data.length)
118
- return;
119
- const branch = new GraphicBranch(false);
120
- for (const data of this.data)
121
- branch.add(data.graphic);
122
- const branchGraphic = context.createBranch(branch, transform);
123
- context.addGraphic(branchGraphic);
124
- }
125
- }
126
- /** @alpha Base class for applying a transform to element placements. */
127
- export class TransformElementsTool extends ElementSetTool {
128
- get allowSelectionSet() { return true; }
129
- get allowGroups() { return true; }
130
- get allowDragSelect() { return true; }
131
- get controlKeyContinuesSelection() { return true; }
132
- get wantAccuSnap() { return true; }
133
- get wantDynamics() { return true; }
134
- get wantMakeCopy() { return false; }
135
- get wantRepeatOperation() { return this.wantMakeCopy && !this.agenda.isEmpty; }
136
- async createAgendaGraphics(changed) {
137
- if (changed) {
138
- if (undefined === this._graphicsProvider)
139
- return; // Not yet needed...
140
- }
141
- else {
142
- if (undefined !== this._graphicsProvider)
143
- return; // Use existing graphics...
144
- }
145
- if (undefined === this._graphicsProvider)
146
- this._graphicsProvider = new TransformGraphicsProvider(this.iModel, this.toolId);
147
- else
148
- await this._graphicsProvider.cleanupGraphics();
149
- if (1 === this.agenda.length) {
150
- await this._graphicsProvider.createSingleGraphic(this.agenda.elements[0]);
151
- return;
152
- }
153
- this._graphicsProvider.createGraphics(this.agenda.elements);
154
- }
155
- async clearAgendaGraphics() {
156
- if (undefined === this._graphicsProvider)
157
- return;
158
- await this._graphicsProvider.cleanupGraphics();
159
- this._graphicsProvider = undefined;
160
- }
161
- async onAgendaModified() {
162
- await this.createAgendaGraphics(true);
163
- }
164
- async initAgendaDynamics() {
165
- await this.createAgendaGraphics(false);
166
- return super.initAgendaDynamics();
167
- }
168
- transformAgendaDynamics(transform, context) {
169
- if (undefined !== this._graphicsProvider)
170
- this._graphicsProvider.addGraphics(transform, context);
171
- }
172
- onDynamicFrame(ev, context) {
173
- const transform = this.calculateTransform(ev);
174
- if (undefined === transform)
175
- return;
176
- this.transformAgendaDynamics(transform, context);
177
- }
178
- updateAnchorLocation(transform) {
179
- // Update anchor point to support creating additional copies (repeat vs. restart)...
180
- if (undefined === this.anchorPoint)
181
- return;
182
- transform.multiplyPoint3d(this.anchorPoint, this.anchorPoint);
183
- const hints = new AccuDrawHintBuilder();
184
- hints.setOrigin(this.anchorPoint);
185
- hints.sendHints();
186
- }
187
- async startCommand() {
188
- if (undefined !== this._startedCmd)
189
- return this._startedCmd;
190
- return EditTools.startCommand({ commandId: editorBuiltInCmdIds.cmdBasicManipulation, iModelKey: this.iModel.key });
191
- }
192
- async replaceAgenda(newIds) {
193
- this.agenda.clear();
194
- if (undefined !== newIds)
195
- this.agenda.add(newIds);
196
- if (this.isSelectionSetModify) {
197
- if (this.agenda.isEmpty)
198
- this.iModel.selectionSet.emptyAll();
199
- else
200
- this.iModel.selectionSet.replace(this.agenda.elements);
201
- this.agenda.setSource(ModifyElementSource.SelectionSet);
202
- this.setPreferredElementSource(); // Update "use selection set" flag...
203
- }
204
- return this.onAgendaModified();
205
- }
206
- async transformAndCopyAgenda(_transform) {
207
- return undefined;
208
- }
209
- async transformAgenda(transform) {
210
- try {
211
- this._startedCmd = await this.startCommand();
212
- if (IModelStatus.Success === await basicManipulationIpc.transformPlacement(this.agenda.compressIds(), transform.toJSON()))
213
- await this.saveChanges();
214
- }
215
- catch (err) {
216
- IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
217
- }
218
- }
219
- async processAgenda(ev) {
220
- const transform = this.calculateTransform(ev);
221
- if (undefined === transform)
222
- return;
223
- if (this.wantMakeCopy)
224
- await this.replaceAgenda(await this.transformAndCopyAgenda(transform));
225
- else
226
- await this.transformAgenda(transform);
227
- this.updateAnchorLocation(transform);
228
- }
229
- async onProcessComplete() {
230
- if (this.wantRepeatOperation)
231
- return; // Continue with current agenda instead of restarting (ex. create additional copies)
232
- return super.onProcessComplete();
233
- }
234
- async onCleanup() {
235
- await this.clearAgendaGraphics();
236
- return super.onCleanup();
237
- }
238
- }
239
- /** @alpha Move elements by applying translation to placement. */
240
- export class MoveElementsTool extends TransformElementsTool {
241
- calculateTransform(ev) {
242
- if (undefined === this.anchorPoint)
243
- return undefined;
244
- return Transform.createTranslation(ev.point.minus(this.anchorPoint));
245
- }
246
- provideToolAssistance(_mainInstrText, _additionalInstr) {
247
- let mainMsg;
248
- if (!this.isSelectByPoints && !this.wantAdditionalElements)
249
- mainMsg = CoreTools.translate(this.wantAdditionalInput ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint");
250
- super.provideToolAssistance(mainMsg);
251
- }
252
- async onRestartTool() {
253
- const tool = new MoveElementsTool();
254
- if (!await tool.run())
255
- return this.exitTool();
256
- }
257
- }
258
- MoveElementsTool.toolId = "MoveElements";
259
- MoveElementsTool.iconSpec = "icon-move";
260
- /** Create new elements with translation applied to placement.
261
- * This is a brute force implementation strictly for example and testing purposes.
262
- * The new elements are Generic:PhysicalObject or BisCore:DrawingGraphic using the model and category of original.
263
- * Does not preserve assemblies and geometric elements without geometry are not copied.
264
- * Using loadProps to return json format geometry to the frontend for each element in the tool agenda is very inefficient.
265
- * Applications that wish to support copy are expected to sub-class TransformElementsTool and register their
266
- * own EditCommand that can correctly copy their application elements.
267
- * @alpha
268
- */
269
- export class CopyElementsTool extends MoveElementsTool {
270
- static get minArgs() { return 0; }
271
- static get maxArgs() { return 1; }
272
- get numCopiesProperty() {
273
- if (!this._numCopiesProperty)
274
- this._numCopiesProperty = new DialogProperty(PropertyDescriptionHelper.buildNumberEditorDescription("numCopies", EditTools.translate("CopyElements.Label.NumCopies"), { type: PropertyEditorParamTypes.Range, minimum: 1 }), 1);
275
- return this._numCopiesProperty;
276
- }
277
- get numCopies() { return this.numCopiesProperty.value; }
278
- set numCopies(value) { this.numCopiesProperty.value = value; }
279
- get wantMakeCopy() { return this.numCopies > 0; }
280
- updateAnchorLocation(transform) {
281
- // Account for additional copies for repeat operation anchor point...
282
- for (let iCopy = 0; iCopy < this.numCopies; ++iCopy)
283
- super.updateAnchorLocation(transform);
284
- }
285
- async doTransformedCopy(ids, transform, numCopies) {
286
- if (numCopies < 1 || 0 === ids.length)
287
- return undefined;
288
- this._startedCmd = await this.startCommand();
289
- const newIds = [];
290
- for (const id of ids) {
291
- // NOTE: For testing only. Using loadProps to return json format geometry to the frontend for each element in the tool agenda is very inefficient.
292
- const props = await this.iModel.elements.loadProps(id, { wantGeometry: true, wantBRepData: true });
293
- if (undefined === props.placement)
294
- continue;
295
- const placement = isPlacement2dProps(props.placement) ? Placement2d.fromJSON(props.placement) : Placement3d.fromJSON(props.placement);
296
- if (!placement.isValid)
297
- continue; // Ignore assembly parents w/o geometry, etc...
298
- const classFullName = (placement.is3d ? "Generic:PhysicalObject" : "BisCore:DrawingGraphic");
299
- const newProps = { classFullName, model: props.model, category: props.category, code: Code.createEmpty(), placement, geom: props.geom };
300
- let newId;
301
- for (let iCopy = 0; iCopy < numCopies; ++iCopy) {
302
- placement.multiplyTransform(transform);
303
- newId = await basicManipulationIpc.insertGeometricElement(newProps);
304
- }
305
- if (undefined !== newId)
306
- newIds.push(newId); // When numCopies > 1 ids are return for just the final copy...
307
- }
308
- return (0 === newIds.length ? undefined : newIds);
309
- }
310
- async transformAndCopyAgenda(transform) {
311
- try {
312
- const newIds = await this.doTransformedCopy(this.agenda.elements, transform, this.numCopies);
313
- if (undefined !== newIds)
314
- await this.saveChanges();
315
- return newIds;
316
- }
317
- catch (err) {
318
- IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
319
- return undefined;
320
- }
321
- }
322
- async applyToolSettingPropertyChange(updatedValue) {
323
- // NOTE: Don't call changeToolSettingPropertyValue, value of numCopies should not be saved...
324
- if (updatedValue.propertyName !== this.numCopiesProperty.name || undefined === updatedValue.value.value)
325
- return false;
326
- this.numCopies = updatedValue.value.value;
327
- return true;
328
- }
329
- supplyToolSettingsProperties() {
330
- // NOTE: Don't call initializeToolSettingPropertyValues, value of numCopies is not saved...
331
- const toolSettings = new Array();
332
- toolSettings.push(this.numCopiesProperty.toDialogItem({ rowPriority: 1, columnIndex: 2 }));
333
- return toolSettings;
334
- }
335
- async onRestartTool() {
336
- const tool = new CopyElementsTool();
337
- tool.numCopies = this.numCopies; // Preserve numCopies on restart...
338
- if (!await tool.run())
339
- return this.exitTool();
340
- }
341
- /** The keyin takes the following arguments, all of which are optional:
342
- * - `numCopies=number` Number of copies of each element to create, default is 1.
343
- */
344
- async parseAndRun(...inputArgs) {
345
- for (const arg of inputArgs) {
346
- const parts = arg.split("=");
347
- if (2 !== parts.length)
348
- continue;
349
- if (parts[0].toLowerCase().startsWith("num")) {
350
- const copies = Number.parseInt(parts[1], 10);
351
- if (copies >= 1)
352
- this.numCopies = copies; // NOTE: Don't call saveToolSettingPropertyValue, always default to single copy...
353
- }
354
- }
355
- return this.run();
356
- }
357
- }
358
- CopyElementsTool.toolId = "CopyElements";
359
- CopyElementsTool.iconSpec = "icon-move"; // Need better icon...
360
- /** @alpha */
361
- export var RotateMethod;
362
- (function (RotateMethod) {
363
- RotateMethod[RotateMethod["By3Points"] = 0] = "By3Points";
364
- RotateMethod[RotateMethod["ByAngle"] = 1] = "ByAngle";
365
- })(RotateMethod || (RotateMethod = {}));
366
- /** @alpha */
367
- export var RotateAbout;
368
- (function (RotateAbout) {
369
- RotateAbout[RotateAbout["Point"] = 0] = "Point";
370
- RotateAbout[RotateAbout["Origin"] = 1] = "Origin";
371
- RotateAbout[RotateAbout["Center"] = 2] = "Center";
372
- })(RotateAbout || (RotateAbout = {}));
373
- /** @alpha Rotate elements by applying transform to placement. */
374
- export class RotateElementsTool extends TransformElementsTool {
375
- constructor() {
376
- super(...arguments);
377
- this.havePivotPoint = false;
378
- this.haveFinalPoint = false;
379
- }
380
- static get minArgs() { return 0; }
381
- static get maxArgs() { return 3; }
382
- static methodMessage(str) { return EditTools.translate(`RotateElements.Method.${str}`); }
383
- get methodProperty() {
384
- if (!this._methodProperty)
385
- this._methodProperty = new DialogProperty(PropertyDescriptionHelper.buildEnumPicklistEditorDescription("rotateMethod", EditTools.translate("RotateElements.Label.Method"), RotateElementsTool.getMethodChoices()), RotateMethod.By3Points);
386
- return this._methodProperty;
387
- }
388
- get rotateMethod() { return this.methodProperty.value; }
389
- set rotateMethod(method) { this.methodProperty.value = method; }
390
- static aboutMessage(str) { return EditTools.translate(`RotateElements.About.${str}`); }
391
- get aboutProperty() {
392
- if (!this._aboutProperty)
393
- this._aboutProperty = new DialogProperty(PropertyDescriptionHelper.buildEnumPicklistEditorDescription("rotateAbout", EditTools.translate("RotateElements.Label.About"), RotateElementsTool.getAboutChoices()), RotateAbout.Point);
394
- return this._aboutProperty;
395
- }
396
- get rotateAbout() { return this.aboutProperty.value; }
397
- set rotateAbout(method) { this.aboutProperty.value = method; }
398
- get angleProperty() {
399
- if (!this._angleProperty)
400
- this._angleProperty = new DialogProperty(new AngleDescription("rotateAngle", EditTools.translate("RotateElements.Label.Angle")), 0.0);
401
- return this._angleProperty;
402
- }
403
- get rotateAngle() { return this.angleProperty.value; }
404
- set rotateAngle(value) { this.angleProperty.value = value; }
405
- get requireAcceptForSelectionSetDynamics() { return RotateMethod.ByAngle !== this.rotateMethod; }
406
- calculateTransform(ev) {
407
- if (undefined === ev.viewport)
408
- return undefined;
409
- if (RotateMethod.ByAngle === this.rotateMethod) {
410
- const rotMatrix = AccuDrawHintBuilder.getCurrentRotation(ev.viewport, true, true);
411
- if (undefined === rotMatrix)
412
- return undefined;
413
- const invMatrix = rotMatrix.inverse();
414
- if (undefined === invMatrix)
415
- return undefined;
416
- const angMatrix = YawPitchRollAngles.createRadians(this.rotateAngle, 0, 0).toMatrix3d();
417
- if (undefined === angMatrix)
418
- return undefined;
419
- angMatrix.multiplyMatrixMatrix(invMatrix, invMatrix);
420
- rotMatrix.multiplyMatrixMatrix(invMatrix, rotMatrix);
421
- return Transform.createFixedPointAndMatrix(ev.point, rotMatrix);
422
- }
423
- if (undefined === this.anchorPoint || undefined === this.xAxisPoint)
424
- return undefined;
425
- const vec1 = Vector3d.createStartEnd(this.anchorPoint, this.xAxisPoint);
426
- const vec2 = Vector3d.createStartEnd(this.anchorPoint, ev.point);
427
- if (!vec1.normalizeInPlace() || !vec2.normalizeInPlace())
428
- return undefined;
429
- const dot = vec1.dotProduct(vec2);
430
- if (dot > (1.0 - Geometry.smallAngleRadians))
431
- return undefined;
432
- if (dot < (-1.0 + Geometry.smallAngleRadians)) {
433
- const rotMatrix = AccuDrawHintBuilder.getCurrentRotation(ev.viewport, true, true);
434
- if (undefined === rotMatrix)
435
- return undefined;
436
- const invMatrix = rotMatrix.inverse();
437
- if (undefined === invMatrix)
438
- return undefined;
439
- const angMatrix = YawPitchRollAngles.createRadians(Math.PI, 0, 0).toMatrix3d(); // 180 degree rotation...
440
- if (undefined === angMatrix)
441
- return undefined;
442
- angMatrix.multiplyMatrixMatrix(invMatrix, invMatrix);
443
- rotMatrix.multiplyMatrixMatrix(invMatrix, rotMatrix);
444
- return Transform.createFixedPointAndMatrix(this.anchorPoint, rotMatrix);
445
- }
446
- const zVec = vec1.unitCrossProduct(vec2);
447
- if (undefined === zVec)
448
- return undefined;
449
- const yVec = zVec.unitCrossProduct(vec1);
450
- if (undefined === yVec)
451
- return undefined;
452
- const matrix1 = Matrix3d.createRows(vec1, yVec, zVec);
453
- zVec.unitCrossProduct(vec2, yVec);
454
- const matrix2 = Matrix3d.createColumns(vec2, yVec, zVec);
455
- const matrix = matrix2.multiplyMatrixMatrix(matrix1);
456
- if (undefined === matrix)
457
- return undefined;
458
- return Transform.createFixedPointAndMatrix(this.anchorPoint, matrix);
459
- }
460
- transformAgendaDynamics(transform, context) {
461
- if (RotateAbout.Point === this.rotateAbout)
462
- return super.transformAgendaDynamics(transform, context);
463
- if (undefined === this._graphicsProvider)
464
- return;
465
- const rotatePoint = Point3d.create();
466
- for (const data of this._graphicsProvider.data) {
467
- if (RotateAbout.Origin === this.rotateAbout)
468
- rotatePoint.setFrom(data.placement.origin);
469
- else
470
- rotatePoint.setFrom(data.placement.calculateRange().center);
471
- const rotateTrans = Transform.createFixedPointAndMatrix(rotatePoint, transform.matrix);
472
- this._graphicsProvider.addSingleGraphic(data.graphic, rotateTrans, context);
473
- }
474
- }
475
- async transformAgenda(transform) {
476
- if (RotateAbout.Point === this.rotateAbout)
477
- return super.transformAgenda(transform);
478
- try {
479
- this._startedCmd = await this.startCommand();
480
- if (IModelStatus.Success === await basicManipulationIpc.rotatePlacement(this.agenda.compressIds(), transform.matrix.toJSON(), RotateAbout.Center === this.rotateAbout))
481
- await this.saveChanges();
482
- }
483
- catch (err) {
484
- IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
485
- }
486
- }
487
- onDynamicFrame(ev, context) {
488
- const transform = this.calculateTransform(ev);
489
- if (undefined !== transform)
490
- return this.transformAgendaDynamics(transform, context);
491
- if (undefined === this.anchorPoint)
492
- return;
493
- const builder = context.createGraphic({ type: GraphicType.WorldOverlay });
494
- builder.setSymbology(context.viewport.getContrastToBackgroundColor(), ColorDef.black, 1, LinePixels.Code2);
495
- builder.addLineString([this.anchorPoint.clone(), ev.point.clone()]);
496
- context.addGraphic(builder.finish());
497
- }
498
- get wantAdditionalInput() {
499
- if (RotateMethod.ByAngle === this.rotateMethod)
500
- return super.wantAdditionalInput;
501
- return !this.haveFinalPoint;
502
- }
503
- wantProcessAgenda(ev) {
504
- if (RotateMethod.ByAngle === this.rotateMethod)
505
- return super.wantProcessAgenda(ev);
506
- if (!this.havePivotPoint)
507
- this.havePivotPoint = true; // Uses anchorPoint...
508
- else if (undefined === this.xAxisPoint)
509
- this.xAxisPoint = ev.point.clone();
510
- else if (!this.haveFinalPoint)
511
- this.haveFinalPoint = true; // Uses button event...
512
- return super.wantProcessAgenda(ev);
513
- }
514
- setupAndPromptForNextAction() {
515
- super.setupAndPromptForNextAction();
516
- if (RotateMethod.ByAngle === this.rotateMethod)
517
- return;
518
- if (undefined === this.anchorPoint || undefined === this.xAxisPoint)
519
- return;
520
- const hints = new AccuDrawHintBuilder();
521
- hints.setXAxis(Vector3d.createStartEnd(this.anchorPoint, this.xAxisPoint));
522
- hints.setOrigin(this.anchorPoint);
523
- hints.setModePolar();
524
- hints.sendHints();
525
- }
526
- provideToolAssistance(_mainInstrText, _additionalInstr) {
527
- let mainMsg;
528
- if (RotateMethod.ByAngle === this.rotateMethod) {
529
- if (!this.isSelectByPoints && !this.wantAdditionalElements && this.wantAdditionalInput)
530
- mainMsg = EditTools.translate("RotateElements.Prompts.IdentifyPoint");
531
- }
532
- else {
533
- if (!this.isSelectByPoints && !this.wantAdditionalElements) {
534
- if (!this.havePivotPoint)
535
- mainMsg = EditTools.translate("RotateElements.Prompts.IdentifyPoint");
536
- else if (undefined === this.xAxisPoint)
537
- mainMsg = EditTools.translate("RotateElements.Prompts.DefineStart");
538
- else
539
- mainMsg = EditTools.translate("RotateElements.Prompts.DefineAmount");
540
- }
541
- }
542
- super.provideToolAssistance(mainMsg);
543
- }
544
- async applyToolSettingPropertyChange(updatedValue) {
545
- if (!this.changeToolSettingPropertyValue(updatedValue))
546
- return false;
547
- if (this.methodProperty.name === updatedValue.propertyName)
548
- await this.onRestartTool(); // calling restart, not reinitialize to not exit tool for selection set...
549
- return true;
550
- }
551
- supplyToolSettingsProperties() {
552
- const toolSettings = new Array();
553
- toolSettings.push(this.methodProperty.toDialogItem({ rowPriority: 1, columnIndex: 2 }));
554
- toolSettings.push(this.aboutProperty.toDialogItem({ rowPriority: 2, columnIndex: 2 }));
555
- if (RotateMethod.ByAngle === this.rotateMethod)
556
- toolSettings.push(this.angleProperty.toDialogItem({ rowPriority: 3, columnIndex: 2 }));
557
- return toolSettings;
558
- }
559
- async onRestartTool() {
560
- const tool = new RotateElementsTool();
561
- if (!await tool.run())
562
- return this.exitTool();
563
- }
564
- async onInstall() {
565
- if (!await super.onInstall())
566
- return false;
567
- // Setup initial values here instead of supplyToolSettingsProperties to support keyin args w/o appui-react...
568
- this.initializeToolSettingPropertyValues([this.methodProperty, this.aboutProperty, this.angleProperty]);
569
- return true;
570
- }
571
- /** The keyin takes the following arguments, all of which are optional:
572
- * - `method=0|1` How rotate angle will be specified. 0 for by 3 points, 1 for by specified angle.
573
- * - `about=0|1|2` Location to rotate about. 0 for point, 1 for placement origin, and 2 for center of range.
574
- * - `angle=number` Rotation angle in degrees when not defining angle by points.
575
- */
576
- async parseAndRun(...inputArgs) {
577
- let rotateMethod;
578
- let rotateAbout;
579
- let rotateAngle;
580
- for (const arg of inputArgs) {
581
- const parts = arg.split("=");
582
- if (2 !== parts.length)
583
- continue;
584
- if (parts[0].toLowerCase().startsWith("me")) {
585
- const method = Number.parseInt(parts[1], 10);
586
- if (!Number.isNaN(method)) {
587
- switch (method) {
588
- case 0:
589
- rotateMethod = RotateMethod.By3Points;
590
- break;
591
- case 1:
592
- rotateMethod = RotateMethod.ByAngle;
593
- break;
594
- }
595
- }
596
- }
597
- else if (parts[0].toLowerCase().startsWith("ab")) {
598
- const about = Number.parseInt(parts[1], 10);
599
- if (!Number.isNaN(about)) {
600
- switch (about) {
601
- case 0:
602
- rotateAbout = RotateAbout.Point;
603
- break;
604
- case 1:
605
- rotateAbout = RotateAbout.Origin;
606
- break;
607
- case 2:
608
- rotateAbout = RotateAbout.Center;
609
- break;
610
- }
611
- }
612
- }
613
- else if (parts[0].toLowerCase().startsWith("an")) {
614
- const angle = Number.parseFloat(parts[1]);
615
- if (!Number.isNaN(angle)) {
616
- rotateAngle = Angle.createDegrees(angle).radians;
617
- }
618
- }
619
- }
620
- // Update current session values so keyin args are picked up for tool settings/restart...
621
- if (undefined !== rotateMethod)
622
- this.saveToolSettingPropertyValue(this.methodProperty, { value: rotateMethod });
623
- if (undefined !== rotateAbout)
624
- this.saveToolSettingPropertyValue(this.aboutProperty, { value: rotateAbout });
625
- if (undefined !== rotateAngle)
626
- this.saveToolSettingPropertyValue(this.angleProperty, { value: rotateAngle });
627
- return this.run();
628
- }
629
- }
630
- RotateElementsTool.toolId = "RotateElements";
631
- RotateElementsTool.iconSpec = "icon-rotate";
632
- RotateElementsTool.getMethodChoices = () => {
633
- return [
634
- { label: RotateElementsTool.methodMessage("3Points"), value: RotateMethod.By3Points },
635
- { label: RotateElementsTool.methodMessage("Angle"), value: RotateMethod.ByAngle },
636
- ];
637
- };
638
- RotateElementsTool.getAboutChoices = () => {
639
- return [
640
- { label: RotateElementsTool.aboutMessage("Point"), value: RotateAbout.Point },
641
- { label: RotateElementsTool.aboutMessage("Origin"), value: RotateAbout.Origin },
642
- { label: RotateElementsTool.aboutMessage("Center"), value: RotateAbout.Center },
643
- ];
644
- };
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { DialogProperty, PropertyDescriptionHelper, PropertyEditorParamTypes, } from "@itwin/appui-abstract";
6
+ import { BentleyError, Id64 } from "@itwin/core-bentley";
7
+ import { Code, ColorDef, IModelStatus, isPlacement2dProps, LinePixels, Placement2d, Placement3d, } from "@itwin/core-common";
8
+ import { AccuDrawHintBuilder, AngleDescription, CoreTools, ElementSetTool, GraphicBranch, GraphicType, IModelApp, IpcApp, ModifyElementSource, NotifyMessageDetails, OutputMessagePriority, readElementGraphics, } from "@itwin/core-frontend";
9
+ import { Angle, Geometry, Matrix3d, Point3d, Transform, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
10
+ import { editorBuiltInCmdIds } from "@itwin/editor-common";
11
+ import { EditTools } from "./EditTool";
12
+ import { basicManipulationIpc } from "./EditToolIpc";
13
+ /** @alpha */
14
+ export class TransformGraphicsProvider {
15
+ constructor(iModel, prefix) {
16
+ /** Chord tolerance to use to stroke the element's geometry in meters. */
17
+ this.chordTolerance = 0.01;
18
+ this.iModel = iModel;
19
+ this.prefix = prefix;
20
+ this.data = new Array();
21
+ this.pending = new Map();
22
+ }
23
+ getRequestId(id) { return `${this.prefix}-${id}`; }
24
+ getToleranceLog10() { return Math.floor(Math.log10(this.chordTolerance)); }
25
+ async createRequest(id) {
26
+ const elementProps = (await this.iModel.elements.getProps(id));
27
+ if (0 === elementProps.length)
28
+ return;
29
+ const placementProps = elementProps[0].placement;
30
+ if (undefined === placementProps)
31
+ return;
32
+ const placement = isPlacement2dProps(placementProps) ? Placement2d.fromJSON(placementProps) : Placement3d.fromJSON(placementProps);
33
+ if (!placement.isValid)
34
+ return; // Ignore assembly parents w/o geometry, etc...
35
+ const requestProps = {
36
+ id: this.getRequestId(id),
37
+ elementId: id,
38
+ toleranceLog10: this.getToleranceLog10(),
39
+ };
40
+ this.pending.set(id, requestProps.id); // keep track of requests so they can be cancelled...
41
+ const graphicData = await IModelApp.tileAdmin.requestElementGraphics(this.iModel, requestProps);
42
+ if (undefined === graphicData)
43
+ return;
44
+ const graphic = await readElementGraphics(graphicData, this.iModel, elementProps[0].model, placement.is3d, { noFlash: true, noHilite: true });
45
+ if (undefined === graphic)
46
+ return;
47
+ return { id, placement, graphic: IModelApp.renderSystem.createGraphicOwner(graphic) };
48
+ }
49
+ disposeOfGraphics() {
50
+ this.data.forEach((data) => {
51
+ data.graphic.disposeGraphic();
52
+ });
53
+ this.data.length = 0;
54
+ }
55
+ async cancelPendingRequests() {
56
+ const requests = new Array();
57
+ for (const [_key, id] of this.pending)
58
+ requests.push(id);
59
+ this.pending.clear();
60
+ if (0 === requests.length)
61
+ return;
62
+ return IpcApp.appFunctionIpc.cancelElementGraphicsRequests(this.iModel.key, requests);
63
+ }
64
+ /** Call to request a RenderGraphic for the supplied element id.
65
+ * @see [[cleanupGraphics]] Must be called when the tool exits.
66
+ */
67
+ async createSingleGraphic(id) {
68
+ try {
69
+ const info = await this.createRequest(id);
70
+ if (undefined !== info?.id)
71
+ this.pending.delete(info.id);
72
+ if (undefined === info?.graphic)
73
+ return false;
74
+ this.data.push(info);
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /** Call to request RenderGraphics for the supplied element ids. Does not wait for results as
82
+ * generating graphics for a large number of elements can take time. Instead an array of [[RenderGraphicOwner]]
83
+ * is populated as requests are resolved and the current dynamics frame displays what is available.
84
+ * @see [[cleanupGraphics]] Must be called when the tool exits.
85
+ */
86
+ createGraphics(elements) {
87
+ if (0 === Id64.sizeOf(elements))
88
+ return;
89
+ try {
90
+ for (const id of Id64.iterable(elements)) {
91
+ const promise = this.createRequest(id);
92
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
93
+ promise.then((info) => {
94
+ if (undefined !== info?.id)
95
+ this.pending.delete(info.id);
96
+ if (undefined !== info?.graphic)
97
+ this.data.push(info);
98
+ });
99
+ }
100
+ }
101
+ catch { }
102
+ }
103
+ /** Call to dispose of [[RenderGraphic]] held by [[RenderGraphicOwner]] and cancel requests that are still pending.
104
+ * @note Must be called when the tool exits to avoid leaks of graphics memory or other webgl resources.
105
+ */
106
+ async cleanupGraphics() {
107
+ await this.cancelPendingRequests();
108
+ this.disposeOfGraphics();
109
+ }
110
+ addSingleGraphic(graphic, transform, context) {
111
+ const branch = new GraphicBranch(false);
112
+ branch.add(graphic);
113
+ const branchGraphic = context.createBranch(branch, transform);
114
+ context.addGraphic(branchGraphic);
115
+ }
116
+ addGraphics(transform, context) {
117
+ if (0 === this.data.length)
118
+ return;
119
+ const branch = new GraphicBranch(false);
120
+ for (const data of this.data)
121
+ branch.add(data.graphic);
122
+ const branchGraphic = context.createBranch(branch, transform);
123
+ context.addGraphic(branchGraphic);
124
+ }
125
+ }
126
+ /** @alpha Base class for applying a transform to element placements. */
127
+ export class TransformElementsTool extends ElementSetTool {
128
+ get allowSelectionSet() { return true; }
129
+ get allowGroups() { return true; }
130
+ get allowDragSelect() { return true; }
131
+ get controlKeyContinuesSelection() { return true; }
132
+ get wantAccuSnap() { return true; }
133
+ get wantDynamics() { return true; }
134
+ get wantMakeCopy() { return false; }
135
+ get wantRepeatOperation() { return this.wantMakeCopy && !this.agenda.isEmpty; }
136
+ async createAgendaGraphics(changed) {
137
+ if (changed) {
138
+ if (undefined === this._graphicsProvider)
139
+ return; // Not yet needed...
140
+ }
141
+ else {
142
+ if (undefined !== this._graphicsProvider)
143
+ return; // Use existing graphics...
144
+ }
145
+ if (undefined === this._graphicsProvider)
146
+ this._graphicsProvider = new TransformGraphicsProvider(this.iModel, this.toolId);
147
+ else
148
+ await this._graphicsProvider.cleanupGraphics();
149
+ if (1 === this.agenda.length) {
150
+ await this._graphicsProvider.createSingleGraphic(this.agenda.elements[0]);
151
+ return;
152
+ }
153
+ this._graphicsProvider.createGraphics(this.agenda.elements);
154
+ }
155
+ async clearAgendaGraphics() {
156
+ if (undefined === this._graphicsProvider)
157
+ return;
158
+ await this._graphicsProvider.cleanupGraphics();
159
+ this._graphicsProvider = undefined;
160
+ }
161
+ async onAgendaModified() {
162
+ await this.createAgendaGraphics(true);
163
+ }
164
+ async initAgendaDynamics() {
165
+ await this.createAgendaGraphics(false);
166
+ return super.initAgendaDynamics();
167
+ }
168
+ transformAgendaDynamics(transform, context) {
169
+ if (undefined !== this._graphicsProvider)
170
+ this._graphicsProvider.addGraphics(transform, context);
171
+ }
172
+ onDynamicFrame(ev, context) {
173
+ const transform = this.calculateTransform(ev);
174
+ if (undefined === transform)
175
+ return;
176
+ this.transformAgendaDynamics(transform, context);
177
+ }
178
+ updateAnchorLocation(transform) {
179
+ // Update anchor point to support creating additional copies (repeat vs. restart)...
180
+ if (undefined === this.anchorPoint)
181
+ return;
182
+ transform.multiplyPoint3d(this.anchorPoint, this.anchorPoint);
183
+ const hints = new AccuDrawHintBuilder();
184
+ hints.setOrigin(this.anchorPoint);
185
+ hints.sendHints();
186
+ }
187
+ async startCommand() {
188
+ if (undefined !== this._startedCmd)
189
+ return this._startedCmd;
190
+ return EditTools.startCommand({ commandId: editorBuiltInCmdIds.cmdBasicManipulation, iModelKey: this.iModel.key });
191
+ }
192
+ async replaceAgenda(newIds) {
193
+ this.agenda.clear();
194
+ if (undefined !== newIds)
195
+ this.agenda.add(newIds);
196
+ if (this.isSelectionSetModify) {
197
+ if (this.agenda.isEmpty)
198
+ this.iModel.selectionSet.emptyAll();
199
+ else
200
+ this.iModel.selectionSet.replace(this.agenda.elements);
201
+ this.agenda.setSource(ModifyElementSource.SelectionSet);
202
+ this.setPreferredElementSource(); // Update "use selection set" flag...
203
+ }
204
+ return this.onAgendaModified();
205
+ }
206
+ async transformAndCopyAgenda(_transform) {
207
+ return undefined;
208
+ }
209
+ async transformAgenda(transform) {
210
+ try {
211
+ this._startedCmd = await this.startCommand();
212
+ if (IModelStatus.Success === await basicManipulationIpc.transformPlacement(this.agenda.compressIds(), transform.toJSON()))
213
+ await this.saveChanges();
214
+ }
215
+ catch (err) {
216
+ IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
217
+ }
218
+ }
219
+ async processAgenda(ev) {
220
+ const transform = this.calculateTransform(ev);
221
+ if (undefined === transform)
222
+ return;
223
+ if (this.wantMakeCopy)
224
+ await this.replaceAgenda(await this.transformAndCopyAgenda(transform));
225
+ else
226
+ await this.transformAgenda(transform);
227
+ this.updateAnchorLocation(transform);
228
+ }
229
+ async onProcessComplete() {
230
+ if (this.wantRepeatOperation)
231
+ return; // Continue with current agenda instead of restarting (ex. create additional copies)
232
+ return super.onProcessComplete();
233
+ }
234
+ async onCleanup() {
235
+ await this.clearAgendaGraphics();
236
+ return super.onCleanup();
237
+ }
238
+ }
239
+ /** @alpha Move elements by applying translation to placement. */
240
+ class MoveElementsTool extends TransformElementsTool {
241
+ calculateTransform(ev) {
242
+ if (undefined === this.anchorPoint)
243
+ return undefined;
244
+ return Transform.createTranslation(ev.point.minus(this.anchorPoint));
245
+ }
246
+ provideToolAssistance(_mainInstrText, _additionalInstr) {
247
+ let mainMsg;
248
+ if (!this.isSelectByPoints && !this.wantAdditionalElements)
249
+ mainMsg = CoreTools.translate(this.wantAdditionalInput ? "ElementSet.Prompts.StartPoint" : "ElementSet.Prompts.EndPoint");
250
+ super.provideToolAssistance(mainMsg);
251
+ }
252
+ async onRestartTool() {
253
+ const tool = new MoveElementsTool();
254
+ if (!await tool.run())
255
+ return this.exitTool();
256
+ }
257
+ }
258
+ MoveElementsTool.toolId = "MoveElements";
259
+ MoveElementsTool.iconSpec = "icon-move";
260
+ export { MoveElementsTool };
261
+ /** Create new elements with translation applied to placement.
262
+ * This is a brute force implementation strictly for example and testing purposes.
263
+ * The new elements are Generic:PhysicalObject or BisCore:DrawingGraphic using the model and category of original.
264
+ * Does not preserve assemblies and geometric elements without geometry are not copied.
265
+ * Using loadProps to return json format geometry to the frontend for each element in the tool agenda is very inefficient.
266
+ * Applications that wish to support copy are expected to sub-class TransformElementsTool and register their
267
+ * own EditCommand that can correctly copy their application elements.
268
+ * @alpha
269
+ */
270
+ class CopyElementsTool extends MoveElementsTool {
271
+ static get minArgs() { return 0; }
272
+ static get maxArgs() { return 1; }
273
+ get numCopiesProperty() {
274
+ if (!this._numCopiesProperty)
275
+ this._numCopiesProperty = new DialogProperty(PropertyDescriptionHelper.buildNumberEditorDescription("numCopies", EditTools.translate("CopyElements.Label.NumCopies"), { type: PropertyEditorParamTypes.Range, minimum: 1 }), 1);
276
+ return this._numCopiesProperty;
277
+ }
278
+ get numCopies() { return this.numCopiesProperty.value; }
279
+ set numCopies(value) { this.numCopiesProperty.value = value; }
280
+ get wantMakeCopy() { return this.numCopies > 0; }
281
+ updateAnchorLocation(transform) {
282
+ // Account for additional copies for repeat operation anchor point...
283
+ for (let iCopy = 0; iCopy < this.numCopies; ++iCopy)
284
+ super.updateAnchorLocation(transform);
285
+ }
286
+ async doTransformedCopy(ids, transform, numCopies) {
287
+ if (numCopies < 1 || 0 === ids.length)
288
+ return undefined;
289
+ this._startedCmd = await this.startCommand();
290
+ const newIds = [];
291
+ for (const id of ids) {
292
+ // NOTE: For testing only. Using loadProps to return json format geometry to the frontend for each element in the tool agenda is very inefficient.
293
+ const props = await this.iModel.elements.loadProps(id, { wantGeometry: true, wantBRepData: true });
294
+ if (undefined === props.placement)
295
+ continue;
296
+ const placement = isPlacement2dProps(props.placement) ? Placement2d.fromJSON(props.placement) : Placement3d.fromJSON(props.placement);
297
+ if (!placement.isValid)
298
+ continue; // Ignore assembly parents w/o geometry, etc...
299
+ const classFullName = (placement.is3d ? "Generic:PhysicalObject" : "BisCore:DrawingGraphic");
300
+ const newProps = { classFullName, model: props.model, category: props.category, code: Code.createEmpty(), placement, geom: props.geom };
301
+ let newId;
302
+ for (let iCopy = 0; iCopy < numCopies; ++iCopy) {
303
+ placement.multiplyTransform(transform);
304
+ newId = await basicManipulationIpc.insertGeometricElement(newProps);
305
+ }
306
+ if (undefined !== newId)
307
+ newIds.push(newId); // When numCopies > 1 ids are return for just the final copy...
308
+ }
309
+ return (0 === newIds.length ? undefined : newIds);
310
+ }
311
+ async transformAndCopyAgenda(transform) {
312
+ try {
313
+ const newIds = await this.doTransformedCopy(this.agenda.elements, transform, this.numCopies);
314
+ if (undefined !== newIds)
315
+ await this.saveChanges();
316
+ return newIds;
317
+ }
318
+ catch (err) {
319
+ IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
320
+ return undefined;
321
+ }
322
+ }
323
+ async applyToolSettingPropertyChange(updatedValue) {
324
+ // NOTE: Don't call changeToolSettingPropertyValue, value of numCopies should not be saved...
325
+ if (updatedValue.propertyName !== this.numCopiesProperty.name || undefined === updatedValue.value.value)
326
+ return false;
327
+ this.numCopies = updatedValue.value.value;
328
+ return true;
329
+ }
330
+ supplyToolSettingsProperties() {
331
+ // NOTE: Don't call initializeToolSettingPropertyValues, value of numCopies is not saved...
332
+ const toolSettings = new Array();
333
+ toolSettings.push(this.numCopiesProperty.toDialogItem({ rowPriority: 1, columnIndex: 2 }));
334
+ return toolSettings;
335
+ }
336
+ async onRestartTool() {
337
+ const tool = new CopyElementsTool();
338
+ tool.numCopies = this.numCopies; // Preserve numCopies on restart...
339
+ if (!await tool.run())
340
+ return this.exitTool();
341
+ }
342
+ /** The keyin takes the following arguments, all of which are optional:
343
+ * - `numCopies=number` Number of copies of each element to create, default is 1.
344
+ */
345
+ async parseAndRun(...inputArgs) {
346
+ for (const arg of inputArgs) {
347
+ const parts = arg.split("=");
348
+ if (2 !== parts.length)
349
+ continue;
350
+ if (parts[0].toLowerCase().startsWith("num")) {
351
+ const copies = Number.parseInt(parts[1], 10);
352
+ if (copies >= 1)
353
+ this.numCopies = copies; // NOTE: Don't call saveToolSettingPropertyValue, always default to single copy...
354
+ }
355
+ }
356
+ return this.run();
357
+ }
358
+ }
359
+ CopyElementsTool.toolId = "CopyElements";
360
+ CopyElementsTool.iconSpec = "icon-move"; // Need better icon...
361
+ export { CopyElementsTool };
362
+ /** @alpha */
363
+ export var RotateMethod;
364
+ (function (RotateMethod) {
365
+ RotateMethod[RotateMethod["By3Points"] = 0] = "By3Points";
366
+ RotateMethod[RotateMethod["ByAngle"] = 1] = "ByAngle";
367
+ })(RotateMethod || (RotateMethod = {}));
368
+ /** @alpha */
369
+ export var RotateAbout;
370
+ (function (RotateAbout) {
371
+ RotateAbout[RotateAbout["Point"] = 0] = "Point";
372
+ RotateAbout[RotateAbout["Origin"] = 1] = "Origin";
373
+ RotateAbout[RotateAbout["Center"] = 2] = "Center";
374
+ })(RotateAbout || (RotateAbout = {}));
375
+ /** @alpha Rotate elements by applying transform to placement. */
376
+ class RotateElementsTool extends TransformElementsTool {
377
+ constructor() {
378
+ super(...arguments);
379
+ this.havePivotPoint = false;
380
+ this.haveFinalPoint = false;
381
+ }
382
+ static get minArgs() { return 0; }
383
+ static get maxArgs() { return 3; }
384
+ static methodMessage(str) { return EditTools.translate(`RotateElements.Method.${str}`); }
385
+ get methodProperty() {
386
+ if (!this._methodProperty)
387
+ this._methodProperty = new DialogProperty(PropertyDescriptionHelper.buildEnumPicklistEditorDescription("rotateMethod", EditTools.translate("RotateElements.Label.Method"), RotateElementsTool.getMethodChoices()), RotateMethod.By3Points);
388
+ return this._methodProperty;
389
+ }
390
+ get rotateMethod() { return this.methodProperty.value; }
391
+ set rotateMethod(method) { this.methodProperty.value = method; }
392
+ static aboutMessage(str) { return EditTools.translate(`RotateElements.About.${str}`); }
393
+ get aboutProperty() {
394
+ if (!this._aboutProperty)
395
+ this._aboutProperty = new DialogProperty(PropertyDescriptionHelper.buildEnumPicklistEditorDescription("rotateAbout", EditTools.translate("RotateElements.Label.About"), RotateElementsTool.getAboutChoices()), RotateAbout.Point);
396
+ return this._aboutProperty;
397
+ }
398
+ get rotateAbout() { return this.aboutProperty.value; }
399
+ set rotateAbout(method) { this.aboutProperty.value = method; }
400
+ get angleProperty() {
401
+ if (!this._angleProperty)
402
+ this._angleProperty = new DialogProperty(new AngleDescription("rotateAngle", EditTools.translate("RotateElements.Label.Angle")), 0.0);
403
+ return this._angleProperty;
404
+ }
405
+ get rotateAngle() { return this.angleProperty.value; }
406
+ set rotateAngle(value) { this.angleProperty.value = value; }
407
+ get requireAcceptForSelectionSetDynamics() { return RotateMethod.ByAngle !== this.rotateMethod; }
408
+ calculateTransform(ev) {
409
+ if (undefined === ev.viewport)
410
+ return undefined;
411
+ if (RotateMethod.ByAngle === this.rotateMethod) {
412
+ const rotMatrix = AccuDrawHintBuilder.getCurrentRotation(ev.viewport, true, true);
413
+ if (undefined === rotMatrix)
414
+ return undefined;
415
+ const invMatrix = rotMatrix.inverse();
416
+ if (undefined === invMatrix)
417
+ return undefined;
418
+ const angMatrix = YawPitchRollAngles.createRadians(this.rotateAngle, 0, 0).toMatrix3d();
419
+ if (undefined === angMatrix)
420
+ return undefined;
421
+ angMatrix.multiplyMatrixMatrix(invMatrix, invMatrix);
422
+ rotMatrix.multiplyMatrixMatrix(invMatrix, rotMatrix);
423
+ return Transform.createFixedPointAndMatrix(ev.point, rotMatrix);
424
+ }
425
+ if (undefined === this.anchorPoint || undefined === this.xAxisPoint)
426
+ return undefined;
427
+ const vec1 = Vector3d.createStartEnd(this.anchorPoint, this.xAxisPoint);
428
+ const vec2 = Vector3d.createStartEnd(this.anchorPoint, ev.point);
429
+ if (!vec1.normalizeInPlace() || !vec2.normalizeInPlace())
430
+ return undefined;
431
+ const dot = vec1.dotProduct(vec2);
432
+ if (dot > (1.0 - Geometry.smallAngleRadians))
433
+ return undefined;
434
+ if (dot < (-1.0 + Geometry.smallAngleRadians)) {
435
+ const rotMatrix = AccuDrawHintBuilder.getCurrentRotation(ev.viewport, true, true);
436
+ if (undefined === rotMatrix)
437
+ return undefined;
438
+ const invMatrix = rotMatrix.inverse();
439
+ if (undefined === invMatrix)
440
+ return undefined;
441
+ const angMatrix = YawPitchRollAngles.createRadians(Math.PI, 0, 0).toMatrix3d(); // 180 degree rotation...
442
+ if (undefined === angMatrix)
443
+ return undefined;
444
+ angMatrix.multiplyMatrixMatrix(invMatrix, invMatrix);
445
+ rotMatrix.multiplyMatrixMatrix(invMatrix, rotMatrix);
446
+ return Transform.createFixedPointAndMatrix(this.anchorPoint, rotMatrix);
447
+ }
448
+ const zVec = vec1.unitCrossProduct(vec2);
449
+ if (undefined === zVec)
450
+ return undefined;
451
+ const yVec = zVec.unitCrossProduct(vec1);
452
+ if (undefined === yVec)
453
+ return undefined;
454
+ const matrix1 = Matrix3d.createRows(vec1, yVec, zVec);
455
+ zVec.unitCrossProduct(vec2, yVec);
456
+ const matrix2 = Matrix3d.createColumns(vec2, yVec, zVec);
457
+ const matrix = matrix2.multiplyMatrixMatrix(matrix1);
458
+ if (undefined === matrix)
459
+ return undefined;
460
+ return Transform.createFixedPointAndMatrix(this.anchorPoint, matrix);
461
+ }
462
+ transformAgendaDynamics(transform, context) {
463
+ if (RotateAbout.Point === this.rotateAbout)
464
+ return super.transformAgendaDynamics(transform, context);
465
+ if (undefined === this._graphicsProvider)
466
+ return;
467
+ const rotatePoint = Point3d.create();
468
+ for (const data of this._graphicsProvider.data) {
469
+ if (RotateAbout.Origin === this.rotateAbout)
470
+ rotatePoint.setFrom(data.placement.origin);
471
+ else
472
+ rotatePoint.setFrom(data.placement.calculateRange().center);
473
+ const rotateTrans = Transform.createFixedPointAndMatrix(rotatePoint, transform.matrix);
474
+ this._graphicsProvider.addSingleGraphic(data.graphic, rotateTrans, context);
475
+ }
476
+ }
477
+ async transformAgenda(transform) {
478
+ if (RotateAbout.Point === this.rotateAbout)
479
+ return super.transformAgenda(transform);
480
+ try {
481
+ this._startedCmd = await this.startCommand();
482
+ if (IModelStatus.Success === await basicManipulationIpc.rotatePlacement(this.agenda.compressIds(), transform.matrix.toJSON(), RotateAbout.Center === this.rotateAbout))
483
+ await this.saveChanges();
484
+ }
485
+ catch (err) {
486
+ IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, BentleyError.getErrorMessage(err) || "An unknown error occurred."));
487
+ }
488
+ }
489
+ onDynamicFrame(ev, context) {
490
+ const transform = this.calculateTransform(ev);
491
+ if (undefined !== transform)
492
+ return this.transformAgendaDynamics(transform, context);
493
+ if (undefined === this.anchorPoint)
494
+ return;
495
+ const builder = context.createGraphic({ type: GraphicType.WorldOverlay });
496
+ builder.setSymbology(context.viewport.getContrastToBackgroundColor(), ColorDef.black, 1, LinePixels.Code2);
497
+ builder.addLineString([this.anchorPoint.clone(), ev.point.clone()]);
498
+ context.addGraphic(builder.finish());
499
+ }
500
+ get wantAdditionalInput() {
501
+ if (RotateMethod.ByAngle === this.rotateMethod)
502
+ return super.wantAdditionalInput;
503
+ return !this.haveFinalPoint;
504
+ }
505
+ wantProcessAgenda(ev) {
506
+ if (RotateMethod.ByAngle === this.rotateMethod)
507
+ return super.wantProcessAgenda(ev);
508
+ if (!this.havePivotPoint)
509
+ this.havePivotPoint = true; // Uses anchorPoint...
510
+ else if (undefined === this.xAxisPoint)
511
+ this.xAxisPoint = ev.point.clone();
512
+ else if (!this.haveFinalPoint)
513
+ this.haveFinalPoint = true; // Uses button event...
514
+ return super.wantProcessAgenda(ev);
515
+ }
516
+ setupAndPromptForNextAction() {
517
+ super.setupAndPromptForNextAction();
518
+ if (RotateMethod.ByAngle === this.rotateMethod)
519
+ return;
520
+ if (undefined === this.anchorPoint || undefined === this.xAxisPoint)
521
+ return;
522
+ const hints = new AccuDrawHintBuilder();
523
+ hints.setXAxis(Vector3d.createStartEnd(this.anchorPoint, this.xAxisPoint));
524
+ hints.setOrigin(this.anchorPoint);
525
+ hints.setModePolar();
526
+ hints.sendHints();
527
+ }
528
+ provideToolAssistance(_mainInstrText, _additionalInstr) {
529
+ let mainMsg;
530
+ if (RotateMethod.ByAngle === this.rotateMethod) {
531
+ if (!this.isSelectByPoints && !this.wantAdditionalElements && this.wantAdditionalInput)
532
+ mainMsg = EditTools.translate("RotateElements.Prompts.IdentifyPoint");
533
+ }
534
+ else {
535
+ if (!this.isSelectByPoints && !this.wantAdditionalElements) {
536
+ if (!this.havePivotPoint)
537
+ mainMsg = EditTools.translate("RotateElements.Prompts.IdentifyPoint");
538
+ else if (undefined === this.xAxisPoint)
539
+ mainMsg = EditTools.translate("RotateElements.Prompts.DefineStart");
540
+ else
541
+ mainMsg = EditTools.translate("RotateElements.Prompts.DefineAmount");
542
+ }
543
+ }
544
+ super.provideToolAssistance(mainMsg);
545
+ }
546
+ async applyToolSettingPropertyChange(updatedValue) {
547
+ if (!this.changeToolSettingPropertyValue(updatedValue))
548
+ return false;
549
+ if (this.methodProperty.name === updatedValue.propertyName)
550
+ await this.onRestartTool(); // calling restart, not reinitialize to not exit tool for selection set...
551
+ return true;
552
+ }
553
+ supplyToolSettingsProperties() {
554
+ const toolSettings = new Array();
555
+ toolSettings.push(this.methodProperty.toDialogItem({ rowPriority: 1, columnIndex: 2 }));
556
+ toolSettings.push(this.aboutProperty.toDialogItem({ rowPriority: 2, columnIndex: 2 }));
557
+ if (RotateMethod.ByAngle === this.rotateMethod)
558
+ toolSettings.push(this.angleProperty.toDialogItem({ rowPriority: 3, columnIndex: 2 }));
559
+ return toolSettings;
560
+ }
561
+ async onRestartTool() {
562
+ const tool = new RotateElementsTool();
563
+ if (!await tool.run())
564
+ return this.exitTool();
565
+ }
566
+ async onInstall() {
567
+ if (!await super.onInstall())
568
+ return false;
569
+ // Setup initial values here instead of supplyToolSettingsProperties to support keyin args w/o appui-react...
570
+ this.initializeToolSettingPropertyValues([this.methodProperty, this.aboutProperty, this.angleProperty]);
571
+ return true;
572
+ }
573
+ /** The keyin takes the following arguments, all of which are optional:
574
+ * - `method=0|1` How rotate angle will be specified. 0 for by 3 points, 1 for by specified angle.
575
+ * - `about=0|1|2` Location to rotate about. 0 for point, 1 for placement origin, and 2 for center of range.
576
+ * - `angle=number` Rotation angle in degrees when not defining angle by points.
577
+ */
578
+ async parseAndRun(...inputArgs) {
579
+ let rotateMethod;
580
+ let rotateAbout;
581
+ let rotateAngle;
582
+ for (const arg of inputArgs) {
583
+ const parts = arg.split("=");
584
+ if (2 !== parts.length)
585
+ continue;
586
+ if (parts[0].toLowerCase().startsWith("me")) {
587
+ const method = Number.parseInt(parts[1], 10);
588
+ if (!Number.isNaN(method)) {
589
+ switch (method) {
590
+ case 0:
591
+ rotateMethod = RotateMethod.By3Points;
592
+ break;
593
+ case 1:
594
+ rotateMethod = RotateMethod.ByAngle;
595
+ break;
596
+ }
597
+ }
598
+ }
599
+ else if (parts[0].toLowerCase().startsWith("ab")) {
600
+ const about = Number.parseInt(parts[1], 10);
601
+ if (!Number.isNaN(about)) {
602
+ switch (about) {
603
+ case 0:
604
+ rotateAbout = RotateAbout.Point;
605
+ break;
606
+ case 1:
607
+ rotateAbout = RotateAbout.Origin;
608
+ break;
609
+ case 2:
610
+ rotateAbout = RotateAbout.Center;
611
+ break;
612
+ }
613
+ }
614
+ }
615
+ else if (parts[0].toLowerCase().startsWith("an")) {
616
+ const angle = Number.parseFloat(parts[1]);
617
+ if (!Number.isNaN(angle)) {
618
+ rotateAngle = Angle.createDegrees(angle).radians;
619
+ }
620
+ }
621
+ }
622
+ // Update current session values so keyin args are picked up for tool settings/restart...
623
+ if (undefined !== rotateMethod)
624
+ this.saveToolSettingPropertyValue(this.methodProperty, { value: rotateMethod });
625
+ if (undefined !== rotateAbout)
626
+ this.saveToolSettingPropertyValue(this.aboutProperty, { value: rotateAbout });
627
+ if (undefined !== rotateAngle)
628
+ this.saveToolSettingPropertyValue(this.angleProperty, { value: rotateAngle });
629
+ return this.run();
630
+ }
631
+ }
632
+ RotateElementsTool.toolId = "RotateElements";
633
+ RotateElementsTool.iconSpec = "icon-rotate";
634
+ RotateElementsTool.getMethodChoices = () => {
635
+ return [
636
+ { label: RotateElementsTool.methodMessage("3Points"), value: RotateMethod.By3Points },
637
+ { label: RotateElementsTool.methodMessage("Angle"), value: RotateMethod.ByAngle },
638
+ ];
639
+ };
640
+ RotateElementsTool.getAboutChoices = () => {
641
+ return [
642
+ { label: RotateElementsTool.aboutMessage("Point"), value: RotateAbout.Point },
643
+ { label: RotateElementsTool.aboutMessage("Origin"), value: RotateAbout.Origin },
644
+ { label: RotateElementsTool.aboutMessage("Center"), value: RotateAbout.Center },
645
+ ];
646
+ };
647
+ export { RotateElementsTool };
645
648
  //# sourceMappingURL=TransformElementsTool.js.map