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