@itwin/editor-frontend 3.0.0-dev.98 → 3.1.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +76 -1
  2. package/LICENSE.md +1 -1
  3. package/README.md +9 -9
  4. package/lib/cjs/EditTool.d.ts.map +1 -1
  5. package/lib/cjs/EditTool.js +18 -7
  6. package/lib/cjs/EditTool.js.map +1 -1
  7. package/lib/cjs/ElementGeometryTool.d.ts +64 -80
  8. package/lib/cjs/ElementGeometryTool.d.ts.map +1 -1
  9. package/lib/cjs/ElementGeometryTool.js +333 -295
  10. package/lib/cjs/ElementGeometryTool.js.map +1 -1
  11. package/lib/cjs/ProjectLocation/ProjectGeolocation.d.ts +10 -17
  12. package/lib/cjs/ProjectLocation/ProjectGeolocation.d.ts.map +1 -1
  13. package/lib/cjs/ProjectLocation/ProjectGeolocation.js +54 -94
  14. package/lib/cjs/ProjectLocation/ProjectGeolocation.js.map +1 -1
  15. package/lib/cjs/SketchTools.d.ts +21 -9
  16. package/lib/cjs/SketchTools.d.ts.map +1 -1
  17. package/lib/cjs/SketchTools.js +197 -204
  18. package/lib/cjs/SketchTools.js.map +1 -1
  19. package/lib/cjs/SolidModelingTools.d.ts +381 -0
  20. package/lib/cjs/SolidModelingTools.d.ts.map +1 -0
  21. package/lib/cjs/SolidModelingTools.js +1460 -0
  22. package/lib/cjs/SolidModelingTools.js.map +1 -0
  23. package/lib/cjs/TransformElementsTool.d.ts.map +1 -1
  24. package/lib/cjs/TransformElementsTool.js +8 -28
  25. package/lib/cjs/TransformElementsTool.js.map +1 -1
  26. package/lib/cjs/UndoRedoTool.js +1 -1
  27. package/lib/cjs/UndoRedoTool.js.map +1 -1
  28. package/lib/cjs/editor-frontend.d.ts +1 -0
  29. package/lib/cjs/editor-frontend.d.ts.map +1 -1
  30. package/lib/cjs/editor-frontend.js +1 -0
  31. package/lib/cjs/editor-frontend.js.map +1 -1
  32. package/lib/esm/EditTool.d.ts.map +1 -1
  33. package/lib/esm/EditTool.js +14 -3
  34. package/lib/esm/EditTool.js.map +1 -1
  35. package/lib/esm/ElementGeometryTool.d.ts +64 -80
  36. package/lib/esm/ElementGeometryTool.d.ts.map +1 -1
  37. package/lib/esm/ElementGeometryTool.js +334 -290
  38. package/lib/esm/ElementGeometryTool.js.map +1 -1
  39. package/lib/esm/ProjectLocation/ProjectGeolocation.d.ts +10 -17
  40. package/lib/esm/ProjectLocation/ProjectGeolocation.d.ts.map +1 -1
  41. package/lib/esm/ProjectLocation/ProjectGeolocation.js +54 -94
  42. package/lib/esm/ProjectLocation/ProjectGeolocation.js.map +1 -1
  43. package/lib/esm/SketchTools.d.ts +21 -9
  44. package/lib/esm/SketchTools.d.ts.map +1 -1
  45. package/lib/esm/SketchTools.js +198 -205
  46. package/lib/esm/SketchTools.js.map +1 -1
  47. package/lib/esm/SolidModelingTools.d.ts +381 -0
  48. package/lib/esm/SolidModelingTools.d.ts.map +1 -0
  49. package/lib/esm/SolidModelingTools.js +1435 -0
  50. package/lib/esm/SolidModelingTools.js.map +1 -0
  51. package/lib/esm/TransformElementsTool.d.ts.map +1 -1
  52. package/lib/esm/TransformElementsTool.js +8 -28
  53. package/lib/esm/TransformElementsTool.js.map +1 -1
  54. package/lib/esm/UndoRedoTool.js +1 -1
  55. package/lib/esm/UndoRedoTool.js.map +1 -1
  56. package/lib/esm/editor-frontend.d.ts +1 -0
  57. package/lib/esm/editor-frontend.d.ts.map +1 -1
  58. package/lib/esm/editor-frontend.js +1 -0
  59. package/lib/esm/editor-frontend.js.map +1 -1
  60. package/lib/public/locales/en/Editor.json +141 -3
  61. package/package.json +17 -25
@@ -6,10 +6,10 @@
6
6
  * @module Editing
7
7
  */
8
8
  import { Id64 } from "@itwin/core-bentley";
9
- import { BooleanMode, editorBuiltInCmdIds, SubEntityType } from "@itwin/editor-common";
9
+ import { BRepEntityType, editorBuiltInCmdIds, SubEntityType } from "@itwin/editor-common";
10
10
  import { FeatureAppearance, FeatureAppearanceProvider, RgbColor } from "@itwin/core-common";
11
- import { Geometry, Point3d, Range3d, Transform, Vector3d } from "@itwin/core-geometry";
12
- import { AccuDrawHintBuilder, ElementSetTool, EventHandled, GraphicBranch, GraphicType, IModelApp, InputSource, readElementGraphics } from "@itwin/core-frontend";
11
+ import { Point3d, Range3d, Transform } from "@itwin/core-geometry";
12
+ import { AccuDrawHintBuilder, BeModifierKeys, CoordSource, CoreTools, ElementSetTool, EventHandled, GraphicBranch, GraphicType, IModelApp, InputSource, readElementGraphics, ToolAssistance, ToolAssistanceImage, ToolAssistanceInputMethod } from "@itwin/core-frontend";
13
13
  import { computeChordToleranceFromPoint } from "./CreateElementTool";
14
14
  import { EditTools } from "./EditTool";
15
15
  /** @alpha */
@@ -62,46 +62,51 @@ export class ElementGeometryGraphicsProvider {
62
62
  }
63
63
  }
64
64
  /** @alpha */
65
+ export function isSameSubEntity(a, b) {
66
+ if (a.type !== b.type)
67
+ return false;
68
+ if (a.id !== b.id)
69
+ return false;
70
+ if ((undefined !== a.index ? a.index : 0) !== (undefined !== b.index ? b.index : 0))
71
+ return false;
72
+ return true;
73
+ }
74
+ /** @alpha */
65
75
  export class SubEntityData {
66
- isSame(other) {
67
- if (undefined === this.info)
68
- return false;
69
- if (this.info.index !== other.index)
70
- return false;
71
- if (this.info.type !== other.type)
72
- return false;
73
- if (this.info.id !== other.id)
74
- return false;
75
- return true;
76
- }
76
+ constructor(props) { this._props = props; }
77
+ get props() { return this._props; }
78
+ set props(value) { this.cleanupGraphic(); this._props = value; }
79
+ get geometry() { return this._geometry; }
80
+ set geometry(value) { this._geometry = value; }
81
+ isSame(other) { return isSameSubEntity(this._props, other); }
77
82
  getAppearance(vp, accepted) {
78
- var _a, _b, _c, _d, _e;
83
+ var _a, _b, _c, _d;
79
84
  const color = vp.hilite.color;
80
85
  const rgb = RgbColor.fromColorDef(accepted ? color.inverse() : color);
81
86
  const transparency = 0.25;
82
87
  const emphasized = true; // Necessary for obscured sub-entities w/SceneGraphic...
83
88
  let weight;
84
- switch ((_a = this.info) === null || _a === void 0 ? void 0 : _a.type) {
89
+ switch (this.props.type) {
85
90
  case SubEntityType.Face:
86
91
  break;
87
92
  case SubEntityType.Edge:
88
- const edgeWeight = accepted ? 5 : 3;
89
- weight = ((_c = (_b = this.geom) === null || _b === void 0 ? void 0 : _b.appearance) === null || _c === void 0 ? void 0 : _c.weight) ? Math.min(this.geom.appearance.weight + edgeWeight, 31) : edgeWeight;
93
+ const edgeWeight = accepted ? 1 : 3;
94
+ weight = ((_b = (_a = this._geometry) === null || _a === void 0 ? void 0 : _a.appearance) === null || _b === void 0 ? void 0 : _b.weight) ? Math.min(this._geometry.appearance.weight + edgeWeight, 31) : edgeWeight;
90
95
  break;
91
96
  case SubEntityType.Vertex:
92
- const vertexWeight = accepted ? 12 : 10;
93
- weight = ((_e = (_d = this.geom) === null || _d === void 0 ? void 0 : _d.appearance) === null || _e === void 0 ? void 0 : _e.weight) ? Math.min(this.geom.appearance.weight + vertexWeight, 31) : vertexWeight;
97
+ const vertexWeight = accepted ? 8 : 10;
98
+ weight = ((_d = (_c = this._geometry) === null || _c === void 0 ? void 0 : _c.appearance) === null || _d === void 0 ? void 0 : _d.weight) ? Math.min(this._geometry.appearance.weight + vertexWeight, 31) : vertexWeight;
94
99
  break;
95
100
  }
96
101
  return FeatureAppearance.fromJSON({ rgb, transparency, weight, emphasized, nonLocatable: true });
97
102
  }
98
103
  async createGraphic(iModel) {
99
104
  var _a;
100
- if (undefined === ((_a = this.geom) === null || _a === void 0 ? void 0 : _a.graphic))
105
+ if (undefined === ((_a = this._geometry) === null || _a === void 0 ? void 0 : _a.graphic))
101
106
  return false;
102
107
  if (undefined === this._graphicsProvider)
103
108
  this._graphicsProvider = new ElementGeometryGraphicsProvider(iModel);
104
- return this._graphicsProvider.createGraphic(this.geom.graphic);
109
+ return this._graphicsProvider.createGraphic(this._geometry.graphic);
105
110
  }
106
111
  cleanupGraphic() {
107
112
  if (undefined === this._graphicsProvider)
@@ -117,16 +122,15 @@ export class SubEntityData {
117
122
  var _a, _b;
118
123
  if (undefined === ((_a = this._graphicsProvider) === null || _a === void 0 ? void 0 : _a.graphic))
119
124
  return;
120
- const appearanceProvider = FeatureAppearanceProvider.supplement((app) => {
121
- return app.extendAppearance(this.getAppearance(context.viewport, accepted));
122
- });
123
- const opts = { appearanceProvider };
124
- const range = (((_b = this.geom) === null || _b === void 0 ? void 0 : _b.range) ? Range3d.fromJSON(this.geom.range) : undefined);
125
+ const range = (((_b = this._geometry) === null || _b === void 0 ? void 0 : _b.range) ? Range3d.fromJSON(this._geometry.range) : undefined);
125
126
  const pixelSize = context.viewport.getPixelSizeAtPoint(range ? range.center : undefined);
126
127
  const offsetDir = context.viewport.view.getZVector();
127
128
  offsetDir.scaleToLength(3 * pixelSize, offsetDir);
128
129
  const offsetTrans = Transform.createTranslation(offsetDir);
129
- this._graphicsProvider.addDecoration(context, GraphicType.Scene, offsetTrans, opts);
130
+ const appearanceProvider = FeatureAppearanceProvider.supplement((app) => {
131
+ return app.extendAppearance(this.getAppearance(context.viewport, accepted));
132
+ });
133
+ this._graphicsProvider.addDecoration(context, GraphicType.Scene, offsetTrans, { appearanceProvider });
130
134
  }
131
135
  }
132
136
  /** @alpha Base class for tools that want to use the backend geometry cache. */
@@ -146,14 +150,22 @@ export class ElementGeometryCacheTool extends ElementSetTool {
146
150
  static callCommand(method, ...args) {
147
151
  return EditTools.callCommand(method, ...args);
148
152
  }
149
- // TODO: This looks nice as long as ViewFlags.transparency is on...
150
- get agendaAppearance() { return FeatureAppearance.fromTransparency(0.90); }
153
+ agendaAppearance(isDynamics) {
154
+ if (isDynamics) {
155
+ if (undefined === this._agendaAppearanceDynamic)
156
+ this._agendaAppearanceDynamic = FeatureAppearance.fromTransparency(0.0);
157
+ return this._agendaAppearanceDynamic;
158
+ }
159
+ if (undefined === this._agendaAppearanceDefault)
160
+ this._agendaAppearanceDefault = FeatureAppearance.fromTransparency(0.9);
161
+ return this._agendaAppearanceDefault;
162
+ }
151
163
  get wantAgendaAppearanceOverride() { return false; }
152
164
  addFeatureOverrides(overrides, _vp) {
153
165
  if (this.agenda.isEmpty)
154
166
  return;
155
- const app = this.agendaAppearance;
156
- this.agenda.elements.forEach((id) => { overrides.overrideElement(id, app); });
167
+ const appearance = this.agendaAppearance(false);
168
+ this.agenda.elements.forEach((elementId) => { overrides.override({ elementId, appearance }); });
157
169
  }
158
170
  updateAgendaAppearanceProvider(drop) {
159
171
  if (!this.wantAgendaAppearanceOverride)
@@ -168,18 +180,25 @@ export class ElementGeometryCacheTool extends ElementSetTool {
168
180
  }
169
181
  }
170
182
  get geometryCacheFilter() { return undefined; }
183
+ onGeometryCacheFilterChanged() { this._checkedIds.clear(); }
184
+ async createElementGeometryCache(id) {
185
+ // NOTE: Creates cache if it doesn't already exist then test new or existing cache against filter...
186
+ try {
187
+ this._startedCmd = await this.startCommand();
188
+ return await ElementGeometryCacheTool.callCommand("createElementGeometryCache", id, this.geometryCacheFilter);
189
+ }
190
+ catch (err) {
191
+ return false;
192
+ }
193
+ }
171
194
  async acceptElementForOperation(id) {
172
195
  if (Id64.isInvalid(id) || Id64.isTransient(id))
173
196
  return false;
174
197
  let accept = this._checkedIds.get(id);
175
198
  if (undefined === accept) {
176
- try {
177
- this._startedCmd = await this.startCommand();
178
- accept = await ElementGeometryCacheTool.callCommand("createElementGeometryCache", id, this.geometryCacheFilter);
179
- }
180
- catch (err) {
181
- accept = false;
182
- }
199
+ if (this.agenda.isEmpty && this._checkedIds.size > 1000)
200
+ this._checkedIds.clear(); // Limit auto-locate cache size to something reasonable...
201
+ accept = await this.createElementGeometryCache(id);
183
202
  this._checkedIds.set(id, accept);
184
203
  }
185
204
  return accept;
@@ -207,8 +226,14 @@ export class ElementGeometryCacheTool extends ElementSetTool {
207
226
  return this.postFilterIds(await super.getDragSelectCandidates(vp, origin, corner, method, overlap));
208
227
  }
209
228
  onDynamicFrame(_ev, context) {
210
- if (undefined !== this._graphicsProvider)
211
- this._graphicsProvider.addGraphic(context);
229
+ if (undefined === this._graphicsProvider)
230
+ return;
231
+ if (!this.wantAgendaAppearanceOverride)
232
+ return this._graphicsProvider.addGraphic(context);
233
+ const appearanceProvider = FeatureAppearanceProvider.supplement((app) => {
234
+ return app.extendAppearance(this.agendaAppearance(true));
235
+ });
236
+ this._graphicsProvider.addGraphic(context, undefined, { appearanceProvider });
212
237
  }
213
238
  async onMouseMotion(ev) {
214
239
  return this.updateGraphic(ev, IModelApp.viewManager.inDynamicsMode);
@@ -270,121 +295,126 @@ export class ElementGeometryCacheTool extends ElementSetTool {
270
295
  this.clearGraphic();
271
296
  }
272
297
  }
273
- /** @alpha Base class for tools that perform boolean operations on a set of elements. */
274
- export class BooleanOperationTool extends ElementGeometryCacheTool {
275
- get allowSelectionSet() { return BooleanMode.Subtract !== this.mode; }
276
- get allowDragSelect() { return BooleanMode.Subtract !== this.mode; }
277
- get controlKeyContinuesSelection() { return true; }
278
- get requiredElementCount() { return 2; }
279
- get geometryCacheFilter() {
280
- return { parts: true, curves: false, surfaces: BooleanMode.Subtract === this.mode && !this.agenda.isEmpty, solids: true, other: false };
281
- }
282
- async onAgendaModified() {
283
- // Filter changes to allow surfaces as tools, invalidate cached accept status...
284
- if (BooleanMode.Subtract === this.mode && (this.agenda.isEmpty || 1 === this.agenda.length))
285
- this._checkedIds.clear();
286
- }
287
- async applyAgendaOperation() {
288
- if (this.agenda.length < this.requiredElementCount)
289
- return undefined;
290
- try {
291
- this._startedCmd = await this.startCommand();
292
- const target = this.agenda.elements[0];
293
- const tools = this.agenda.elements.slice(1);
294
- const params = { mode: this.mode, tools };
295
- const opts = { writeChanges: true };
296
- return await ElementGeometryCacheTool.callCommand("booleanOperation", target, params, opts);
297
- }
298
- catch (err) {
299
- return undefined;
298
+ /** @alpha Base class for tools that need to locate faces, edges, and vertices. */
299
+ export class LocateSubEntityTool extends ElementGeometryCacheTool {
300
+ constructor() {
301
+ super(...arguments);
302
+ this._acceptedSubEntities = [];
303
+ this._summaryIds = new Map();
304
+ }
305
+ provideToolAssistance(mainInstrText, additionalInstr) {
306
+ if (this.wantAdditionalSubEntities) {
307
+ const faceKey = this.wantSubEntityType(SubEntityType.Face) ? "Face" : "";
308
+ const edgeKey = this.wantSubEntityType(SubEntityType.Edge) ? "Edge" : "";
309
+ const vertexKey = this.wantSubEntityType(SubEntityType.Vertex) ? "Vertex" : "";
310
+ const subEntityKey = `${faceKey}${edgeKey}${vertexKey}`;
311
+ if (0 === subEntityKey.length) {
312
+ super.provideToolAssistance(mainInstrText, additionalInstr);
313
+ return;
314
+ }
315
+ if (undefined === mainInstrText)
316
+ mainInstrText = EditTools.translate(`LocateSubEntities.Identify.${subEntityKey}`);
317
+ const leftMsg = EditTools.translate(`LocateSubEntities.Accept.${subEntityKey}`);
318
+ const rightMsg = this.haveAcceptedSubEntities && this.allowSubEntitySelectNext ? EditTools.translate(`LocateSubEntities.AcceptNext.${subEntityKey}`) : CoreTools.translate("ElementSet.Inputs.Cancel");
319
+ const mouseInstructions = [];
320
+ const touchInstructions = [];
321
+ if (!ToolAssistance.createTouchCursorInstructions(touchInstructions))
322
+ touchInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.OneTouchTap, leftMsg, false, ToolAssistanceInputMethod.Touch));
323
+ mouseInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.LeftClick, leftMsg, false, ToolAssistanceInputMethod.Mouse));
324
+ touchInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.TwoTouchTap, rightMsg, false, ToolAssistanceInputMethod.Touch));
325
+ mouseInstructions.push(ToolAssistance.createInstruction(ToolAssistanceImage.RightClick, rightMsg, false, ToolAssistanceInputMethod.Mouse));
326
+ if (this.allowSubEntityControlSelect)
327
+ mouseInstructions.push(ToolAssistance.createModifierKeyInstruction(ToolAssistance.ctrlKey, ToolAssistanceImage.LeftClickDrag, EditTools.translate(`LocateSubEntities.IdentifyAdditional.${subEntityKey}`), false, ToolAssistanceInputMethod.Mouse));
328
+ if (undefined !== additionalInstr) {
329
+ for (const instr of additionalInstr) {
330
+ if (ToolAssistanceInputMethod.Touch === instr.inputMethod)
331
+ touchInstructions.push(instr);
332
+ else
333
+ mouseInstructions.push(instr);
334
+ }
335
+ }
336
+ const sections = [];
337
+ sections.push(ToolAssistance.createSection(mouseInstructions, ToolAssistance.inputsLabel));
338
+ sections.push(ToolAssistance.createSection(touchInstructions, ToolAssistance.inputsLabel));
339
+ const mainInstruction = ToolAssistance.createInstruction(this.iconSpec, mainInstrText);
340
+ const instructions = ToolAssistance.createInstructions(mainInstruction, sections);
341
+ IModelApp.notifications.setToolAssistance(instructions);
342
+ return;
300
343
  }
344
+ super.provideToolAssistance(mainInstrText, additionalInstr);
301
345
  }
302
- async processAgenda(_ev) {
303
- const result = await this.applyAgendaOperation();
304
- if (result === null || result === void 0 ? void 0 : result.elementId)
305
- await this.saveChanges();
306
- }
307
- }
308
- /** @alpha Perform boolean union of solid geometry. */
309
- export class UniteSolidElementsTool extends BooleanOperationTool {
310
- get mode() { return BooleanMode.Unite; }
311
- async onRestartTool() {
312
- const tool = new UniteSolidElementsTool();
313
- if (!await tool.run())
314
- return this.exitTool();
315
- }
316
- }
317
- UniteSolidElementsTool.toolId = "UniteSolids";
318
- UniteSolidElementsTool.iconSpec = "icon-move"; // TODO: Need better icon...
319
- /** @alpha Perform boolean subtract of solid geometry. */
320
- export class SubtractSolidElementsTool extends BooleanOperationTool {
321
- get mode() { return BooleanMode.Subtract; }
322
- async onRestartTool() {
323
- const tool = new SubtractSolidElementsTool();
324
- if (!await tool.run())
325
- return this.exitTool();
346
+ get wantAgendaAppearanceOverride() { return true; }
347
+ get wantGeometrySummary() { return false; }
348
+ get wantSubEntitySnap() { return false; }
349
+ wantSubEntityType(type) { return SubEntityType.Face === type; }
350
+ getMaximumSubEntityHits(type) { return this.wantSubEntityType(type) ? 25 : 0; }
351
+ get requiredSubEntityCount() { return 1; }
352
+ get haveAcceptedSubEntities() { return (0 !== this._acceptedSubEntities.length); }
353
+ get inhibitSubEntityDisplay() { return this.isDynamicsStarted; }
354
+ get allowSubEntityControlSelect() { return true; }
355
+ get allowSubEntityControlDeselect() { return this.allowSubEntityControlSelect; }
356
+ get allowSubEntitySelectNext() { return !this.isDynamicsStarted; }
357
+ getBRepEntityTypeForSubEntity(id, subEntity) {
358
+ const summary = this._summaryIds.get(id);
359
+ if (undefined === summary)
360
+ return BRepEntityType.Invalid;
361
+ const index = (undefined !== subEntity.index ? subEntity.index : 0);
362
+ if (index >= summary.length)
363
+ return BRepEntityType.Invalid;
364
+ return summary[index];
365
+ }
366
+ async createElementGeometrySummary(id) {
367
+ let summary = this._summaryIds.get(id);
368
+ if (undefined === summary) {
369
+ if (this.agenda.isEmpty && this._summaryIds.size > 1000)
370
+ this._summaryIds.clear(); // Limit auto-locate cache size to something reasonable...
371
+ try {
372
+ this._startedCmd = await this.startCommand();
373
+ if (undefined === (summary = await ElementGeometryCacheTool.callCommand("summarizeElementGeometryCache", id)))
374
+ return false;
375
+ }
376
+ catch (err) {
377
+ return false;
378
+ }
379
+ this._summaryIds.set(id, summary);
380
+ }
381
+ return true;
326
382
  }
327
- }
328
- SubtractSolidElementsTool.toolId = "SubtractSolids";
329
- SubtractSolidElementsTool.iconSpec = "icon-move"; // TODO: Need better icon...
330
- /** @alpha Perform boolean intersection of solid geometry. */
331
- export class IntersectSolidElementsTool extends BooleanOperationTool {
332
- get mode() { return BooleanMode.Intersect; }
333
- async onRestartTool() {
334
- const tool = new IntersectSolidElementsTool();
335
- if (!await tool.run())
336
- return this.exitTool();
383
+ async createElementGeometryCache(id) {
384
+ const accept = await super.createElementGeometryCache(id);
385
+ return (accept && this.wantGeometrySummary ? this.createElementGeometrySummary(id) : accept);
337
386
  }
338
- }
339
- IntersectSolidElementsTool.toolId = "IntersectSolids";
340
- IntersectSolidElementsTool.iconSpec = "icon-move"; // TODO: Need better icon...
341
- /** @alpha Perform sew operation on surface geometry. */
342
- export class SewSheetElementsTool extends ElementGeometryCacheTool {
343
- get allowSelectionSet() { return true; }
344
- get allowDragSelect() { return true; }
345
- get controlKeyContinuesSelection() { return true; }
346
- get requiredElementCount() { return 2; }
347
- get geometryCacheFilter() {
348
- return { parts: true, curves: false, surfaces: true, solids: false, other: false };
349
- }
350
- async applyAgendaOperation() {
351
- if (this.agenda.length < this.requiredElementCount)
387
+ getAcceptedSubEntityData(index = -1) {
388
+ if (-1 === index)
389
+ index = this._acceptedSubEntities.length - 1;
390
+ if (index < 0 || index > this._acceptedSubEntities.length - 1)
352
391
  return undefined;
353
- try {
354
- this._startedCmd = await this.startCommand();
355
- const target = this.agenda.elements[0];
356
- const tools = this.agenda.elements.slice(1);
357
- const params = { tools };
358
- const opts = { writeChanges: true };
359
- return await ElementGeometryCacheTool.callCommand("sewSheets", target, params, opts);
360
- }
361
- catch (err) {
362
- return undefined;
363
- }
392
+ return this._acceptedSubEntities[index];
364
393
  }
365
- async processAgenda(_ev) {
366
- const result = await this.applyAgendaOperation();
367
- if (result === null || result === void 0 ? void 0 : result.elementId)
368
- await this.saveChanges();
394
+ getAcceptedSubEntities() {
395
+ const accepted = [];
396
+ this._acceptedSubEntities.forEach((entry) => { accepted.push(entry.props); });
397
+ return accepted;
369
398
  }
370
- async onRestartTool() {
371
- const tool = new SewSheetElementsTool();
372
- if (!await tool.run())
373
- return this.exitTool();
399
+ drawSubEntity(context, subEntity, accepted) {
400
+ subEntity.display(context, accepted);
401
+ }
402
+ drawAcceptedSubEntities(context) {
403
+ this._acceptedSubEntities.forEach((entry) => { this.drawSubEntity(context, entry, true); });
374
404
  }
375
- }
376
- SewSheetElementsTool.toolId = "SewSheets";
377
- SewSheetElementsTool.iconSpec = "icon-move"; // TODO: Need better icon...
378
- /** @alpha Base class for tools that need to locate faces, edges, and vertices. */
379
- export class LocateSubEntityTool extends ElementGeometryCacheTool {
380
- wantSubEntityType(type) { return SubEntityType.Face === type; }
381
- getMaximumSubEntityHits(type) { return this.wantSubEntityType(type) ? 25 : 0; }
382
- get wantAgendaAppearanceOverride() { return true; }
383
405
  decorate(context) {
384
- if (!this.allowView(context.viewport))
406
+ if (this.inhibitSubEntityDisplay || !this.allowView(context.viewport))
385
407
  return;
408
+ if (this.haveAcceptedSubEntities)
409
+ this.drawAcceptedSubEntities(context);
386
410
  if (undefined !== this._currentSubEntity)
387
- this._currentSubEntity.display(context, false);
411
+ this.drawSubEntity(context, this._currentSubEntity, false);
412
+ }
413
+ decorateSuspended(context) {
414
+ if (this.inhibitSubEntityDisplay || !this.allowView(context.viewport))
415
+ return;
416
+ if (this.haveAcceptedSubEntities)
417
+ this.drawAcceptedSubEntities(context);
388
418
  }
389
419
  getLocateAperture(ev) {
390
420
  if (undefined === ev.viewport)
@@ -404,6 +434,19 @@ export class LocateSubEntityTool extends ElementGeometryCacheTool {
404
434
  // The edge and vertex hits get post-filtered on xy distance, so this is fine for perspective views...
405
435
  return (aperture * vec[0].distance(vec[1]));
406
436
  }
437
+ getRayOrigin(ev) {
438
+ const spacePoint = ev.point.clone();
439
+ const vp = ev.viewport;
440
+ if (undefined === vp)
441
+ return spacePoint;
442
+ vp.worldToNpc(spacePoint, spacePoint);
443
+ spacePoint.z = 1.0;
444
+ vp.npcToWorld(spacePoint, spacePoint);
445
+ return spacePoint;
446
+ }
447
+ wantHiddenEdges(vp) {
448
+ return vp.viewFlags.hiddenEdgesVisible();
449
+ }
407
450
  getSubEntityFilter() { return undefined; }
408
451
  async pickSubEntities(id, boresite, maxFace, maxEdge, maxVertex, maxDistance, hiddenEdgesVisible, filter) {
409
452
  try {
@@ -433,8 +476,9 @@ export class LocateSubEntityTool extends ElementGeometryCacheTool {
433
476
  return undefined;
434
477
  const aperture = this.getLocateAperture(ev);
435
478
  const maxDistance = this.getMaxRayDistance(ev, aperture);
436
- const boresite = AccuDrawHintBuilder.getBoresite(ev.point, vp);
437
- const hiddenEdgesVisible = vp.viewFlags.hiddenEdgesVisible();
479
+ const spacePoint = this.getRayOrigin(ev);
480
+ const boresite = AccuDrawHintBuilder.getBoresite(spacePoint, vp);
481
+ const hiddenEdgesVisible = this.wantHiddenEdges(vp);
438
482
  const filter = this.getSubEntityFilter();
439
483
  let hits = await this.pickSubEntities(id, boresite, maxFace, maxEdge, maxVertex, maxDistance, hiddenEdgesVisible, filter);
440
484
  // NOTE: Remove erroneous edge/vertex hits in perspective views by checking real xy distance to hit point...
@@ -458,39 +502,103 @@ export class LocateSubEntityTool extends ElementGeometryCacheTool {
458
502
  }
459
503
  return hits;
460
504
  }
505
+ async createSubEntityData(id, hit) {
506
+ const data = new SubEntityData(hit.subEntity);
507
+ const chordTolerance = (this.targetView ? computeChordToleranceFromPoint(this.targetView, Point3d.fromJSON(hit.point)) : undefined);
508
+ await this.createSubEntityGraphic(id, data, chordTolerance);
509
+ return data;
510
+ }
511
+ /** Append specified sub-entity to accepted array. */
512
+ async addSubEntity(id, props) {
513
+ this._acceptedSubEntities.push(await this.createSubEntityData(id, props));
514
+ }
515
+ /** Remove specified sub-entity from accepted array, or pop last sub-entity if undefined. */
516
+ async removeSubEntity(_id, props) {
517
+ if (undefined !== props)
518
+ this._acceptedSubEntities = this._acceptedSubEntities.filter((entry) => !isSameSubEntity(entry.props, props.subEntity));
519
+ else
520
+ this._acceptedSubEntities.pop();
521
+ }
522
+ /** Locate sub-entities for the most recently added (last) agenda entry. Tool sub-classes that wish to identity
523
+ * sub-entities from multiple elements are responsible for maintaining the sub-entities per-element.
524
+ */
461
525
  async doLocateSubEntity(ev, newSearch) {
526
+ var _a;
462
527
  if (this.agenda.isEmpty || undefined === ev.viewport)
463
528
  return false;
529
+ const id = this.agenda.elements[this.agenda.length - 1];
464
530
  if (newSearch) {
465
- const info = await this.doPickSubEntities(this.agenda.elements[this.agenda.length - 1], ev);
466
- if (undefined === info)
531
+ this._locatedSubEntities = await this.doPickSubEntities(id, ev);
532
+ if (undefined === this._locatedSubEntities || 0 === this._locatedSubEntities.length)
467
533
  return false;
468
- this._acceptedSubEntity = info[0]; // TODO: Save array for reset cyling. Do we need to support multiple elements?
469
- this.clearSubEntityGraphic();
470
- return true;
534
+ /** NOTE: Set last button location to point on sub-entity when not snapping.
535
+ * If dynamics are enabled on this event, onDynamicFrame is called with this location.
536
+ */
537
+ if (CoordSource.ElemSnap !== ev.coordsFrom) {
538
+ ev.point.setFrom(Point3d.fromJSON(this._locatedSubEntities[0].point));
539
+ IModelApp.toolAdmin.setAdjustedDataPoint(ev);
540
+ }
541
+ }
542
+ else {
543
+ await this.removeSubEntity(id);
471
544
  }
472
- this._acceptedSubEntity = undefined;
545
+ const hit = (_a = this._locatedSubEntities) === null || _a === void 0 ? void 0 : _a.shift();
546
+ if (undefined !== hit) {
547
+ if (undefined === this._acceptedSubEntities.find((entry) => isSameSubEntity(entry.props, hit.subEntity)))
548
+ await this.addSubEntity(id, hit);
549
+ else if (this.allowSubEntityControlDeselect)
550
+ await this.removeSubEntity(id, hit);
551
+ }
552
+ IModelApp.viewManager.invalidateDecorationsAllViews();
473
553
  return true;
474
554
  }
475
555
  async chooseNextHit(ev) {
476
- if (undefined === this._acceptedSubEntity)
556
+ if (!this.haveAcceptedSubEntities)
477
557
  return super.chooseNextHit(ev);
478
- await this.doLocateSubEntity(ev, false);
479
- if (undefined === this._acceptedSubEntity)
480
- await this.onReinitialize();
558
+ if (!this.allowSubEntitySelectNext) {
559
+ await this.onReinitialize(); // Don't cycle through hits after starting dynamics...
560
+ }
561
+ else {
562
+ await this.doLocateSubEntity(ev, false);
563
+ if (!this.haveAcceptedSubEntities)
564
+ await this.onReinitialize();
565
+ }
481
566
  return EventHandled.No;
482
567
  }
568
+ get wantAdditionalSubEntities() {
569
+ return (this._acceptedSubEntities.length < this.requiredSubEntityCount || (this.allowSubEntityControlSelect && this.isControlDown));
570
+ }
483
571
  async gatherInput(ev) {
484
- if (undefined === this._acceptedSubEntity)
572
+ if (this.wantAdditionalSubEntities) {
485
573
  await this.doLocateSubEntity(ev, true);
574
+ if (this.wantAdditionalSubEntities) {
575
+ this.setupAndPromptForNextAction();
576
+ return EventHandled.No;
577
+ }
578
+ this.clearCurrentSubEntity();
579
+ }
486
580
  return super.gatherInput(ev);
487
581
  }
488
- get wantAdditionalInput() {
489
- if (undefined === this._acceptedSubEntity)
490
- return true;
491
- return super.wantAdditionalInput;
582
+ getCurrentElement() {
583
+ if (!this.agenda.isEmpty)
584
+ return this.agenda.elements[this.agenda.length - 1];
585
+ const hit = IModelApp.accuSnap.currHit;
586
+ return (undefined !== hit && hit.isElementHit ? hit.sourceId : undefined);
587
+ }
588
+ clearCurrentSubEntity() {
589
+ if (undefined === this._currentSubEntity)
590
+ return;
591
+ this._currentSubEntity.cleanupGraphic();
592
+ this._currentSubEntity = undefined;
492
593
  }
493
- async setCurrentSubEntity(id, current, chordTolerance) {
594
+ async setCurrentSubEntity(id, hit, chordTolerance) {
595
+ if (undefined === this._currentSubEntity)
596
+ this._currentSubEntity = new SubEntityData(hit.subEntity);
597
+ else
598
+ this._currentSubEntity.props = hit.subEntity;
599
+ return this.createSubEntityGraphic(id, this._currentSubEntity, chordTolerance);
600
+ }
601
+ async changeCurrentSubEntity(id, current, chordTolerance) {
494
602
  if (undefined === id || undefined === current) {
495
603
  if (undefined === this._currentSubEntity || !this._currentSubEntity.hasGraphic)
496
604
  return false;
@@ -499,159 +607,95 @@ export class LocateSubEntityTool extends ElementGeometryCacheTool {
499
607
  }
500
608
  if (undefined !== this._currentSubEntity && this._currentSubEntity.hasGraphic && this._currentSubEntity.isSame(current.subEntity))
501
609
  return false;
502
- if (undefined === this._currentSubEntity)
503
- this._currentSubEntity = new SubEntityData();
504
- else
505
- this._currentSubEntity.cleanupGraphic();
506
- const opts = {
507
- wantGraphic: true,
508
- wantRange: true,
509
- wantAppearance: true,
510
- chordTolerance,
511
- };
512
- this._currentSubEntity.info = current.subEntity;
513
- this._currentSubEntity.geom = await ElementGeometryCacheTool.callCommand("getSubEntityGeometry", id, current.subEntity, opts);
514
- return this._currentSubEntity.createGraphic(this.iModel);
515
- }
516
- clearSubEntityGraphic() {
517
- if (undefined === this._currentSubEntity)
518
- return;
519
- this._currentSubEntity.cleanupGraphic();
520
- this._currentSubEntity = undefined;
610
+ return this.setCurrentSubEntity(id, current, chordTolerance);
521
611
  }
522
- async updateSubEntityGraphic(ev) {
612
+ async updateCurrentSubEntity(ev) {
523
613
  if (undefined === ev.viewport)
524
614
  return false;
525
- const hit = IModelApp.accuSnap.currHit;
526
- if (undefined === hit || !hit.isElementHit)
527
- return this.setCurrentSubEntity();
528
- const current = await this.doPickSubEntities(hit.sourceId, ev);
615
+ const id = this.wantAdditionalSubEntities ? this.getCurrentElement() : undefined;
616
+ if (undefined === id)
617
+ return this.changeCurrentSubEntity();
618
+ if (this._subEntityGraphicPending)
619
+ return false;
620
+ this._subEntityGraphicPending = true;
621
+ const current = await this.doPickSubEntities(id, ev);
529
622
  const chordTolerance = current ? computeChordToleranceFromPoint(ev.viewport, Point3d.fromJSON(current[0].point)) : 0.0;
530
- if (!await this.setCurrentSubEntity(hit.sourceId, current ? current[0] : undefined, chordTolerance))
623
+ const status = await this.changeCurrentSubEntity(id, current ? current[0] : undefined, chordTolerance);
624
+ this._subEntityGraphicPending = undefined;
625
+ if (status)
626
+ IModelApp.viewManager.invalidateDecorationsAllViews();
627
+ return status;
628
+ }
629
+ async createSubEntityGraphic(id, data, chordTolerance) {
630
+ try {
631
+ const opts = {
632
+ wantGraphic: true,
633
+ wantRange: true,
634
+ wantAppearance: true,
635
+ chordTolerance,
636
+ };
637
+ data.chordTolerance = chordTolerance;
638
+ data.geometry = await ElementGeometryCacheTool.callCommand("getSubEntityGeometry", id, data.props, opts);
639
+ return await data.createGraphic(this.iModel);
640
+ }
641
+ catch (err) {
531
642
  return false;
532
- IModelApp.viewManager.invalidateDecorationsAllViews();
533
- return true;
643
+ }
534
644
  }
535
645
  async updateGraphic(ev, isDynamics) {
536
646
  if (isDynamics)
537
647
  return super.updateGraphic(ev, isDynamics);
538
- await this.updateSubEntityGraphic(ev);
648
+ await this.updateCurrentSubEntity(ev);
539
649
  }
540
- async applyAgendaOperation(_ev, _isAccept) { return undefined; }
541
650
  async getGraphicData(ev) {
542
651
  const result = await this.applyAgendaOperation(ev, false);
543
652
  return result === null || result === void 0 ? void 0 : result.graphic;
544
653
  }
654
+ async applyAgendaOperation(_ev, _isAccept) { return undefined; }
545
655
  async processAgenda(ev) {
546
- if (undefined === this._acceptedSubEntity)
547
- return;
548
656
  const result = await this.applyAgendaOperation(ev, true);
549
657
  if (result === null || result === void 0 ? void 0 : result.elementId)
550
658
  await this.saveChanges();
551
659
  }
660
+ async onModifierKeyTransition(wentDown, modifier, event) {
661
+ if (EventHandled.Yes === await super.onModifierKeyTransition(wentDown, modifier, event))
662
+ return EventHandled.Yes;
663
+ if (BeModifierKeys.Control !== modifier)
664
+ return EventHandled.No;
665
+ if (IModelApp.toolAdmin.isLocateCircleOn === this.wantAdditionalSubEntities)
666
+ return EventHandled.No;
667
+ this.setupAndPromptForNextAction();
668
+ return EventHandled.Yes;
669
+ }
670
+ changeLocateState(enableLocate, enableSnap, cursor, coordLockOvr) {
671
+ super.changeLocateState(enableLocate, enableSnap, cursor, coordLockOvr);
672
+ // Keep showing locate circle when identifing sub-entities even if done locating elements...
673
+ if (!IModelApp.toolAdmin.isLocateCircleOn && this.wantAdditionalSubEntities)
674
+ IModelApp.toolAdmin.setLocateCircleOn(true);
675
+ }
676
+ get shouldEnableSnap() {
677
+ if (this.isSelectByPoints || !this.wantAccuSnap)
678
+ return false;
679
+ if (this.isDynamicsStarted)
680
+ return true;
681
+ const isCtrlSelect = (this.isControlDown && (this.controlKeyContinuesSelection || this.allowSubEntityControlSelect));
682
+ if (isCtrlSelect || this.wantAdditionalElements || this.wantAdditionalSubEntities)
683
+ return this.wantSubEntitySnap;
684
+ return !this.wantSubEntitySnap;
685
+ }
552
686
  setupAccuDraw() { }
553
687
  setupAndPromptForNextAction() {
554
688
  this.setupAccuDraw();
555
689
  super.setupAndPromptForNextAction();
556
690
  }
691
+ clearSubEntityGraphics() {
692
+ if (undefined !== this._currentSubEntity)
693
+ this._currentSubEntity.cleanupGraphic();
694
+ this._acceptedSubEntities.forEach((entry) => { entry.cleanupGraphic(); });
695
+ }
557
696
  async onCleanup() {
558
- this.clearSubEntityGraphic();
697
+ this.clearSubEntityGraphics();
559
698
  return super.onCleanup();
560
699
  }
561
700
  }
562
- /** @alpha Identify faces of solids and surfaces to offset. */
563
- export class OffsetFacesTool extends LocateSubEntityTool {
564
- requireWriteableTarget() { return false; } // TODO: Testing...
565
- get wantDynamics() { return true; }
566
- get wantAccuSnap() { return undefined !== this._acceptedSubEntity; }
567
- get geometryCacheFilter() {
568
- return { minGeom: 1, maxGeom: 1, parts: true, curves: false, surfaces: true, solids: true, other: false };
569
- }
570
- async applyAgendaOperation(ev, isAccept) {
571
- var _a, _b;
572
- if (undefined === ev.viewport || this.agenda.isEmpty || undefined === ((_a = this._acceptedSubEntity) === null || _a === void 0 ? void 0 : _a.point) || undefined === ((_b = this._acceptedSubEntity) === null || _b === void 0 ? void 0 : _b.normal))
573
- return undefined;
574
- const facePt = Point3d.fromJSON(this._acceptedSubEntity.point);
575
- const faceNormal = Vector3d.fromJSON(this._acceptedSubEntity.normal);
576
- const projPt = AccuDrawHintBuilder.projectPointToLineInView(ev.point, facePt, faceNormal, ev.viewport);
577
- if (undefined === projPt)
578
- return undefined;
579
- const offsetDir = Vector3d.createStartEnd(facePt, projPt);
580
- let offset = offsetDir.magnitude();
581
- if (offset < Geometry.smallMetricDistance)
582
- return undefined;
583
- if (offsetDir.dotProduct(faceNormal) < 0.0)
584
- offset = -offset;
585
- try {
586
- this._startedCmd = await this.startCommand();
587
- const params = { faces: this._acceptedSubEntity.subEntity, distances: offset };
588
- const opts = {
589
- wantGraphic: isAccept ? undefined : true,
590
- chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
591
- requestId: `${this.toolId}:${this.agenda.elements[0]}`,
592
- writeChanges: isAccept ? true : undefined,
593
- };
594
- return await ElementGeometryCacheTool.callCommand("offsetFaces", this.agenda.elements[0], params, opts);
595
- }
596
- catch (err) {
597
- return undefined;
598
- }
599
- }
600
- setupAccuDraw() {
601
- var _a, _b;
602
- if (undefined === ((_a = this._acceptedSubEntity) === null || _a === void 0 ? void 0 : _a.point) || undefined === ((_b = this._acceptedSubEntity) === null || _b === void 0 ? void 0 : _b.normal))
603
- return;
604
- const facePt = Point3d.fromJSON(this._acceptedSubEntity.point);
605
- const faceNormal = Vector3d.fromJSON(this._acceptedSubEntity.normal);
606
- const hints = new AccuDrawHintBuilder();
607
- hints.setOriginFixed = true;
608
- hints.setLockY = true;
609
- hints.setLockZ = true;
610
- hints.setModeRectangular();
611
- hints.setOrigin(facePt);
612
- hints.setXAxis2(faceNormal);
613
- hints.sendHints();
614
- }
615
- async onRestartTool() {
616
- const tool = new OffsetFacesTool();
617
- if (!await tool.run())
618
- return this.exitTool();
619
- }
620
- }
621
- OffsetFacesTool.toolId = "OffsetFaces";
622
- OffsetFacesTool.iconSpec = "icon-move"; // TODO: Need better icon...
623
- /** @alpha Identify faces to offset to hollow solids. */
624
- export class HollowFacesTool extends LocateSubEntityTool {
625
- get geometryCacheFilter() {
626
- return { minGeom: 1, maxGeom: 1, parts: true, curves: false, surfaces: false, solids: true, other: false };
627
- }
628
- async applyAgendaOperation(ev, isAccept) {
629
- if (undefined === ev.viewport || this.agenda.isEmpty || undefined === this._acceptedSubEntity)
630
- return undefined;
631
- // TODO: Tool settings for shell thickness and face thickness...
632
- const shellThickness = 0.1;
633
- const faceThickness = 0.0;
634
- try {
635
- this._startedCmd = await this.startCommand();
636
- const params = { faces: this._acceptedSubEntity.subEntity, distances: faceThickness, defaultDistance: shellThickness };
637
- const opts = {
638
- wantGraphic: isAccept ? undefined : true,
639
- chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
640
- requestId: `${this.toolId}:${this.agenda.elements[0]}`,
641
- writeChanges: isAccept ? true : undefined,
642
- };
643
- return await ElementGeometryCacheTool.callCommand("hollowFaces", this.agenda.elements[0], params, opts);
644
- }
645
- catch (err) {
646
- return undefined;
647
- }
648
- }
649
- async onRestartTool() {
650
- const tool = new HollowFacesTool();
651
- if (!await tool.run())
652
- return this.exitTool();
653
- }
654
- }
655
- HollowFacesTool.toolId = "HollowFaces";
656
- HollowFacesTool.iconSpec = "icon-move"; // TODO: Need better icon...
657
701
  //# sourceMappingURL=ElementGeometryTool.js.map