/
InspectElementTool.ts
236 lines (196 loc) · 8.81 KB
/
InspectElementTool.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Tools
*/
import { BentleyError, Id64, Id64Array, Id64String } from "@itwin/core-bentley";
import { GeometrySummaryOptions, GeometrySummaryVerbosity, IModelReadRpcInterface } from "@itwin/core-common";
import {
BeButtonEvent, CoreTools, EventHandled, HitDetail, IModelApp, LocateFilterStatus, LocateResponse, MessageBoxIconType, MessageBoxType,
NotifyMessageDetails, OutputMessagePriority, PrimitiveTool,
} from "@itwin/core-frontend";
import { copyStringToClipboard } from "../ClipboardUtilities";
import { parseArgs } from "./parseArgs";
/** Creates a readable text summary of a geometric element or geometry part. The keyin takes the following arguments, all of which are optional:
* - `id=elementId,elementId,elementId` comma-separated list of element Ids where each `elementId` is a hexadecimal element Id such as "0x12cb";
* - `symbology=0|1` where 1 indicates detailed symbology information should be included in the output;
* - `placement=0|1` where 1 indicates detailed geometric element placement should be included; and
* - `verbosity=0|1|2` controlling the verbosity of the output for each geometric primitive in the geometry stream. Higher values = more detailed information. Note verbosity=2 can produce megabytes of data for certain types of geometric primitives like large meshes.
* - `modal=0|1` where 1 indicates the output should appear in a modal dialog.
* - `copy=0|1` where 1 indicates the output should be copied to the clipboard. Defaults to true.
* - `refs=0|1` where 1 indicates that for geometry parts a list of all elements referencing that part should be included in the output. This is extremely computationally expensive.
* If no id is specified, the tool runs in interactive mode: first operating upon the selection set (if any), then allowing the user to select additional elements.
* @beta
*/
export class InspectElementTool extends PrimitiveTool {
public static override toolId = "InspectElement";
public static override get minArgs() { return 0; }
public static override get maxArgs() { return 6; }
private _options: GeometrySummaryOptions = {};
private _elementIds?: Id64String[];
private _modal = false;
private _useSelection = false;
private _doCopy = false;
private _explodeParts = false;
constructor(options?: GeometrySummaryOptions, elementIds?: Id64String[]) {
super();
if (undefined !== options)
this._options = { ...options };
this._elementIds = elementIds;
}
private setupAndPromptForNextAction(): void {
this._useSelection = (undefined !== this.targetView && this.targetView.iModel.selectionSet.isActive);
if (!this._useSelection)
IModelApp.accuSnap.enableLocate(true);
this.showPrompt();
}
private showPrompt(): void {
CoreTools.outputPromptByKey(this._useSelection ? "ElementSet.Prompts.ConfirmSelection" : "ElementSet.Prompts.IdentifyElement");
}
public override autoLockTarget(): void { }
public override requireWriteableTarget(): boolean { return false; }
public override async onUnsuspend() {
this.showPrompt();
}
public override async onPostInstall() {
await super.onPostInstall();
if (undefined !== this._elementIds)
this.process(this._elementIds).then(async () => {
await this.onReinitialize();
}).catch((err) => {
IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, err.toString()));
});
else {
this.setupAndPromptForNextAction();
}
}
public override async onDataButtonDown(ev: BeButtonEvent): Promise<EventHandled> {
if (this._useSelection) {
if (undefined !== ev.viewport) {
const ids: Id64Array = [];
ev.viewport.iModel.selectionSet.elements.forEach((id) => {
if (!Id64.isInvalid(id) && !Id64.isTransient(id))
ids.push(id);
});
if (0 === ids.length)
IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, CoreTools.translate("ElementSet.Error.NotSupportedElmType")));
else
await this.process(ids);
await this.onReinitialize();
return EventHandled.Yes;
}
}
const hit = await IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource);
if (undefined === hit || !hit.isElementHit)
return EventHandled.No;
await this.process([hit.sourceId]);
this.setupAndPromptForNextAction();
return EventHandled.No;
}
public override async onResetButtonUp(_ev: BeButtonEvent): Promise<EventHandled> {
await this.onReinitialize();
return EventHandled.No;
}
public override async onReinitialize() {
if (this._useSelection || undefined !== this._elementIds) {
await this.exitTool();
} else {
await this.onRestartTool();
}
}
public async onRestartTool() {
const tool = new InspectElementTool();
if (!await tool.run())
return this.exitTool();
}
public override async filterHit(hit: HitDetail, _out: LocateResponse): Promise<LocateFilterStatus> {
return hit.isElementHit ? LocateFilterStatus.Accept : LocateFilterStatus.Reject;
}
private async process(elementIds: Id64String[]) {
const request = {
elementIds,
options: this._options,
};
let messageDetails: NotifyMessageDetails;
try {
let str = await IModelReadRpcInterface.getClientForRouting(this.iModel.routingContext.token).getGeometrySummary(this.iModel.getRpcProps(), request);
if (this._explodeParts) {
const regex = /^part id: (0x[a-f0-9]+)/gm;
const partIds = new Set<string>();
let match;
while (null !== (match = regex.exec(str)))
partIds.add(match[1]);
if (partIds.size > 0) {
request.elementIds = Array.from(partIds);
str += `\npart ids: ${JSON.stringify(request.elementIds)}\n`;
str += await IModelReadRpcInterface.getClientForRouting(this.iModel.routingContext.token).getGeometrySummary(this.iModel.getRpcProps(), request);
}
}
if (this._doCopy)
copyStringToClipboard(str);
const brief = `Summary ${this._doCopy ? "copied to clipboard." : "complete."}`;
messageDetails = new NotifyMessageDetails(OutputMessagePriority.Info, brief, str);
if (this._modal) {
const div = document.createElement("div");
const appendText = (toAppend: string) => {
const txt = document.createElement("div");
txt.innerText = toAppend;
div.append(txt);
};
const lines = str.split("\n");
const maxLines = 30;
let curLine = 0;
for (const line of lines) {
appendText(line);
if (++curLine > maxLines) {
appendText("...");
break;
}
}
await IModelApp.notifications.openMessageBox(MessageBoxType.Ok, div, MessageBoxIconType.Information);
}
} catch (err) {
messageDetails = new NotifyMessageDetails(OutputMessagePriority.Error, "Error occurred while generating summary", BentleyError.getErrorMessage(err));
}
IModelApp.notifications.outputMessage(messageDetails);
}
public override async parseAndRun(...inputArgs: string[]): Promise<boolean> {
const args = parseArgs(inputArgs);
const ids = args.get("i");
if (undefined !== ids)
this._elementIds = ids.split(",");
const verbosity = args.getInteger("v");
if (undefined !== verbosity) {
switch (verbosity) {
case 0:
this._options.geometryVerbosity = GeometrySummaryVerbosity.Basic;
break;
case 1:
this._options.geometryVerbosity = GeometrySummaryVerbosity.Detailed;
break;
case 2:
this._options.geometryVerbosity = GeometrySummaryVerbosity.Full;
break;
}
}
const symbology = args.getBoolean("s");
if (undefined !== symbology)
this._options.verboseSymbology = symbology;
const placement = args.getBoolean("p");
if (undefined !== placement)
this._options.includePlacement = placement;
const parts = args.getBoolean("r");
if (true === parts && undefined !== IModelApp.viewManager.selectedView)
this._options.includePartReferences = IModelApp.viewManager.selectedView.view.is3d() ? "3d" : "2d";
const modal = args.getBoolean("m");
if (undefined !== modal)
this._modal = modal;
const doCopy = args.getBoolean("c");
if (undefined !== doCopy)
this._doCopy = doCopy;
this._explodeParts = true === args.getBoolean("e");
return this.run();
}
}