@abi-software/scaffoldvuer 0.2.3-alpha-2 → 0.3.1

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 (49) hide show
  1. package/.eslintrc.js +12 -12
  2. package/CHANGELOG.md +344 -316
  3. package/LICENSE +201 -201
  4. package/README.md +164 -164
  5. package/babel.config.js +14 -14
  6. package/dist/scaffoldvuer-wc.common.js +277 -34
  7. package/dist/scaffoldvuer-wc.umd.js +277 -34
  8. package/dist/scaffoldvuer-wc.umd.min.js +277 -34
  9. package/dist/scaffoldvuer.common.js +1252 -529
  10. package/dist/scaffoldvuer.common.js.map +1 -1
  11. package/dist/scaffoldvuer.css +1 -1
  12. package/dist/scaffoldvuer.umd.js +1252 -529
  13. package/dist/scaffoldvuer.umd.js.map +1 -1
  14. package/dist/scaffoldvuer.umd.min.js +1 -1
  15. package/dist/scaffoldvuer.umd.min.js.map +1 -1
  16. package/package-lock.json +18121 -18119
  17. package/package.json +89 -89
  18. package/public/index.html +17 -17
  19. package/src/App.vue +669 -669
  20. package/src/ScaffoldVuer-wc.js +13 -13
  21. package/src/app/DropZone.vue +114 -114
  22. package/src/app/ModelsInformation.js +35 -35
  23. package/src/app/ModelsTable.vue +113 -113
  24. package/src/app/TextureDemos.js +114 -114
  25. package/src/assets/_variables.scss +43 -43
  26. package/src/assets/styles.scss +7 -7
  27. package/src/components/OpacityControls.vue +123 -222
  28. package/src/components/PrimitiveControls.vue +173 -0
  29. package/src/components/ScaffoldTooltip.vue +142 -142
  30. package/src/components/ScaffoldVuer.md +44 -44
  31. package/src/components/ScaffoldVuer.vue +2007 -2007
  32. package/src/components/TextureSlidesControls.vue +272 -0
  33. package/src/components/TreeControls.vue +707 -699
  34. package/src/components/index.js +7 -7
  35. package/src/credential.json +12 -0
  36. package/src/main.js +14 -14
  37. package/src/scripts/BaseModule.js +80 -80
  38. package/src/scripts/RendererModule.js +289 -289
  39. package/src/scripts/WebGL.js +94 -94
  40. package/src/scripts/annotation.js +5 -5
  41. package/src/scripts/eventNotifier.js +66 -66
  42. package/src/scripts/graphicsHighlight.js +134 -134
  43. package/src/scripts/organsRenderer.js +587 -587
  44. package/src/scripts/search.js +182 -182
  45. package/src/scripts/utilities.js +146 -146
  46. package/styleguide.config.js +22 -22
  47. package/vue.config.js +41 -41
  48. package/src/components/test.pdf +0 -0
  49. package/src/searchControls.vue +0 -122
package/src/App.vue CHANGED
@@ -1,669 +1,669 @@
1
- <template>
2
- <div id="app">
3
- <link
4
- rel="stylesheet"
5
- href="https://fonts.googleapis.com/css?family=Asap:400,400i,500,600,700&display=swap"
6
- />
7
- <drop-zone ref="dropzone" @files-drop="onFilesDrop">
8
- <ScaffoldVuer
9
- ref="scaffold"
10
- class="vuer"
11
- :display-u-i="displayUI"
12
- :url="url"
13
- :help-mode="helpMode"
14
- :display-latest-changes="true"
15
- :display-minimap="displayMinimap"
16
- :display-markers="displayMarkers"
17
- :enableOpenMapUI="true"
18
- :minimap-settings="minimapSettings"
19
- :show-colour-picker="showColourPicker"
20
- :render="render"
21
- :region="region"
22
- :view-u-r-l="viewURL"
23
- :format="format"
24
- :marker-labels="markerLabels"
25
- @open-map="openMap"
26
- @on-ready="onReady"
27
- @scaffold-selected="onSelected"
28
- @scaffold-navigated="onNavigated"
29
- @timeChanged="updateCurrentTime"
30
- @zinc-object-added="objectAdded"
31
- />
32
- </drop-zone>
33
-
34
- <el-popover
35
- placement="bottom"
36
- trigger="click"
37
- width="500"
38
- class="popover"
39
- :append-to-body="false"
40
- >
41
- <div class="options-container">
42
- <el-row :gutter="20">
43
- <p>{{ selectedCoordinates }}</p>
44
- </el-row>
45
- <el-row :gutter="20">
46
- <p v-if="currentTime !== 0">
47
- time emited is: {{ currentTime.toFixed(2) }}
48
- </p>
49
- </el-row>
50
- <el-row :gutter="20">
51
- <el-col :span="4" :offset="1">
52
- <el-switch v-model="displayUI" active-text="UI" />
53
- </el-col>
54
- <el-col :span="4" :offset="1">
55
- <el-switch
56
- v-model="displayMarkers"
57
- active-text="Markers"
58
- active-icon-class="el-icon-location"
59
- active-color="#8300bf"
60
- />
61
- </el-col>
62
- <el-col :span="4" :offset="1">
63
- <el-switch
64
- v-model="displayMinimap"
65
- active-text="Minimap"
66
- active-icon-class="el-icon-discover"
67
- active-color="#8300bf"
68
- />
69
- </el-col>
70
- </el-row>
71
- <el-row :gutter="20">
72
- <el-col :span="6">
73
- <el-switch
74
- v-model="tumbleOn"
75
- active-text="Tumble"
76
- active-color="#8300bf"
77
- />
78
- </el-col>
79
- <el-col :span="1"> x: </el-col>
80
- <el-col :span="3">
81
- <el-input-number
82
- class="tumble-direction"
83
- controls-position="right"
84
- v-model="tumbleDirection[0]"
85
- :min="-1.0"
86
- :max="1.0"
87
- :controls="false"
88
- placeholder="Please input"
89
- label="x"
90
- @change="autoTumble"
91
- />
92
- </el-col>
93
- <el-col :span="1" :offset="1"> y: </el-col>
94
- <el-col :span="3">
95
- <el-input-number
96
- class="tumble-direction"
97
- controls-position="right"
98
- v-model="tumbleDirection[1]"
99
- :min="-1.0"
100
- :max="1.0"
101
- :controls="false"
102
- placeholder="Please input"
103
- label="y"
104
- @change="autoTumble"
105
- />
106
- </el-col>
107
- </el-row>
108
- <el-row :gutter="20">
109
- <el-button size="mini" @click="helpMode = !helpMode">
110
- Help Mode
111
- </el-button>
112
- <el-button size="mini" @click="screenCapture()"> Capture </el-button>
113
- <el-button size="mini" @click="changeMarkers"> Change Markers </el-button>
114
- </el-row>
115
- <el-row :gutter="10">
116
- <el-button size="mini" @click="saveSettings()">
117
- Save Settings
118
- </el-button>
119
- <el-button size="mini" @click="restoreSettings()">
120
- Restore Settings
121
- </el-button>
122
- <el-button size="mini" @click="exportGLB()"> Export GLB </el-button>
123
- <el-button size="mini" @click="exportGLTF()"> Export GLTF </el-button>
124
- </el-row>
125
- <el-row :gutter="30">
126
- <el-col :span="7" :offset="2">
127
- <el-switch
128
- v-model="syncMode"
129
- active-text="Sync Mode"
130
- active-color="#8300bf"
131
- />
132
- <el-row v-if="syncMode">
133
- <el-input-number
134
- v-model="zoom"
135
- :min="1.0"
136
- :controls="false"
137
- placeholder="Please input"
138
- label="zoom"
139
- />
140
- <el-input-number
141
- v-model="pos[0]"
142
- :min="-1.0"
143
- :max="1.0"
144
- :controls="false"
145
- placeholder="Please input"
146
- label="x"
147
- />
148
- <el-input-number
149
- v-model="pos[1]"
150
- :min="-1.0"
151
- :max="1.0"
152
- :controls="false"
153
- label="y"
154
- />
155
- </el-row>
156
- </el-col>
157
- </el-row>
158
- <el-row :gutter="30">
159
- <el-col :span="7" :offset="4">
160
- <el-switch
161
- v-model="render"
162
- active-text="Rendering"
163
- active-color="#8300bf"
164
- />
165
- </el-col>
166
- <el-col :span="8" :offset="1">
167
- <el-switch
168
- v-model="renderInfoOn"
169
- active-text="Renderer Info"
170
- active-color="#8300bf"
171
- />
172
- </el-col>
173
- </el-row>
174
- <template v-if="renderInfoOn && rendererInfo">
175
- <el-row>
176
- <el-col
177
- v-for="(value, name) in rendererInfo.memory"
178
- :key="name"
179
- :offset="4"
180
- :span="6"
181
- >
182
- {{ name }} : {{ value }}
183
- </el-col>
184
- </el-row>
185
- <el-row>
186
- <el-col
187
- v-for="(value, name) in rendererInfo.render"
188
- :key="name"
189
- :offset="1"
190
- :span="6"
191
- >
192
- {{ name }} : {{ value }}
193
- </el-col>
194
- </el-row>
195
- </template>
196
- <el-row :gutter="20">
197
- Feature Demo:
198
- <el-button size="mini" @click="featureTextureVolume(false)">
199
- Texture volume
200
- </el-button>
201
- <el-button size="mini" @click="featureTextureSlides(false)">
202
- Texture slides
203
- </el-button>
204
- <el-button size="mini" @click="featureTextureVolume(true)">
205
- Body volume
206
- </el-button>
207
- <el-button size="mini" @click="featureTextureSlides(true)">
208
- Body slides
209
- </el-button>
210
- <el-switch
211
- v-model="onClickMarkers"
212
- active-text="Markers On Selection"
213
- active-color="#8300bf"
214
- />
215
- </el-row>
216
- <el-row :gutter="20">
217
- <el-input
218
- v-model="input"
219
- type="textarea"
220
- autosize
221
- placeholder="Please input"
222
- style="padding-left: 5%; width: 90%"
223
- />
224
- </el-row>
225
- </div>
226
- <el-button
227
- slot="reference"
228
- icon="el-icon-setting"
229
- @click="setSceneToWindo"
230
- >
231
- Options
232
- </el-button>
233
- </el-popover>
234
- <el-popover
235
- placement="bottom"
236
- trigger="click"
237
- width="800"
238
- class="models-popover"
239
- popper-class="table-popover"
240
- :append-to-body="false"
241
- >
242
- <ModelsTable @viewModelClicked="viewModelClicked" />
243
- <el-button slot="reference" icon="el-icon-folder-opened">
244
- Models
245
- </el-button>
246
- <el-autocomplete
247
- slot="reference"
248
- v-model="searchText"
249
- class="search-box"
250
- placeholder="Search"
251
- :fetch-suggestions="fetchSuggestions"
252
- :popper-append-to-body="false"
253
- popper-class="autocomplete-popper"
254
- @keyup.enter.native="search(searchText)"
255
- @select="search(searchText)"
256
- >
257
- <template slot-scope="{ item }">
258
- <div class="value">
259
- {{ item.value }}
260
- </div>
261
- </template>
262
- </el-autocomplete>
263
- </el-popover>
264
- </div>
265
- </template>
266
-
267
- <script>
268
- /* eslint-disable no-alert, no-console */
269
- import { ScaffoldVuer } from "./components/index.js";
270
- import DropZone from "./app/DropZone.vue";
271
- import ModelsTable from "./app/ModelsTable.vue";
272
- import {testSlides, testVolume} from "./app/TextureDemos.js";
273
- import Vue from "vue";
274
- import {
275
- Button,
276
- Col,
277
- Icon,
278
- Input,
279
- InputNumber,
280
- Popover,
281
- Row,
282
- Switch,
283
- Autocomplete,
284
- } from "element-ui";
285
- import lang from "element-ui/lib/locale/lang/en";
286
- import locale from "element-ui/lib/locale";
287
-
288
- locale.use(lang);
289
- Vue.use(Button);
290
- Vue.use(Col);
291
- Vue.use(Icon);
292
- Vue.use(Input);
293
- Vue.use(InputNumber);
294
- Vue.use(Popover);
295
- Vue.use(Row);
296
- Vue.use(Switch);
297
- Vue.use(Autocomplete);
298
-
299
- let texture_prefix = undefined;
300
-
301
- export default {
302
- name: "App",
303
- components: {
304
- DropZone,
305
- ScaffoldVuer,
306
- ModelsTable,
307
- },
308
- data: function () {
309
- return {
310
- url: undefined,
311
- input: undefined,
312
- displayUI: true,
313
- selectedCoordinates: undefined,
314
- helpMode: false,
315
- displayMarkers: false,
316
- onClickMarkers: false,
317
- syncMode: false,
318
- currentTime: 0,
319
- displayMinimap: false,
320
- tumbleOn: false,
321
- tumbleDirection: [1.0, 0.0],
322
- showColourPicker: true,
323
- minimapSettings: {
324
- x_offset: 16,
325
- y_offset: 50,
326
- width: 128,
327
- height: 128,
328
- align: "top-right",
329
- },
330
- markerLabels: ["left atrium", "epicardium"],
331
- render: true,
332
- region: "",
333
- viewURL: "",
334
- renderInfoOn: false,
335
- rendererInfo: undefined,
336
- zoom: 1,
337
- pos: [0, 0],
338
- format: "metadata",
339
- sceneSettings: [],
340
- searchInput: "",
341
- searchText: "",
342
- loadTextureVolumeOnReady: false,
343
- readyCallback: undefined,
344
- };
345
- },
346
- watch: {
347
- input: function () {
348
- this.parseInput();
349
- },
350
- tumbleOn: function () {
351
- this.autoTumble();
352
- },
353
- "$route.query": {
354
- handler: "parseQuery",
355
- deep: true,
356
- immediate: true,
357
- },
358
- syncMode: function (val) {
359
- this.$refs.scaffold.toggleSyncControl(val);
360
- },
361
- },
362
- mounted: function () {
363
- this._objects = [];
364
- this.selectedCoordinates =
365
- this.$refs.scaffold.getDynamicSelectedCoordinates();
366
- this.rendererInfo = this.$refs.scaffold.getRendererInfo();
367
- },
368
- created: function () {
369
- texture_prefix = process.env.VUE_APP_TEXTURE_FOOT_PREFIX;
370
- },
371
- methods: {
372
- exportGLTF: function () {
373
- this.$refs.scaffold.exportGLTF(false).then((data) => {
374
- let dataStr =
375
- "data:text/json;charset=utf-8," +
376
- encodeURIComponent(JSON.stringify(data));
377
- let hrefElement = document.createElement("a");
378
- document.body.append(hrefElement);
379
- hrefElement.download = `export.gltf`;
380
- hrefElement.href = dataStr;
381
- hrefElement.click();
382
- hrefElement.remove();
383
- });
384
- },
385
- exportGLB: function () {
386
- this.$refs.scaffold.exportGLTF(true).then((data) => {
387
- let blob = new Blob([data], { type: "octet/stream" });
388
- let url = window.URL.createObjectURL(blob);
389
- let hrefElement = document.createElement("a");
390
- document.body.append(hrefElement);
391
- hrefElement.download = `export.glb`;
392
- hrefElement.href = url;
393
- hrefElement.click();
394
- hrefElement.remove();
395
- });
396
- },
397
- objectAdded: function (zincObject) {
398
- if (this._objects.length === 0)
399
- zincObject.setMarkerMode("on");
400
- console.log(zincObject);
401
- this._objects.push(zincObject);
402
- },
403
- openMap: function (map) {
404
- console.log(map);
405
- },
406
- featureTextureVolume: async function (overlap) {
407
- //this.$refs.scaffold.clearScene();
408
- //volume matrix to fit the human body
409
- //[-100, 0, 0, 0, 0, -100, 0, 0, 0, 0, -100, 0, -60, -100, 30, 1]
410
- if (overlap) {
411
- const url =
412
- "https://mapcore-bucket1.s3.us-west-2.amazonaws.com/WholeBody/6-match-2023/human/nerve_metadata.json";
413
- if (this.$route.query.url !== encodeURI(url)) {
414
- this.$router.replace({ query: { url } });
415
- this.readyCallback = testVolume;
416
- return;
417
- } else {
418
- testVolume(this.$refs.scaffold, texture_prefix);
419
- return;
420
- }
421
- }
422
- this.$refs.scaffold.clearScene();
423
- testVolume(this.$refs.scaffold, texture_prefix);
424
- },
425
- featureTextureSlides: async function (overlap) {
426
- //Test texture
427
- if (overlap) {
428
- const url =
429
- "https://mapcore-bucket1.s3.us-west-2.amazonaws.com/WholeBody/6-match-2023/human/nerve_metadata.json";
430
- if (this.$route.query.url !== encodeURI(url)) {
431
- this.$router.replace({ query: { url } });
432
- this.readyCallback = testSlides;
433
- return;
434
- } else {
435
- testSlides(this.$refs.scaffold, texture_prefix);
436
- return;
437
- }
438
- }
439
- this.$refs.scaffold.clearScene();
440
- testSlides(this.$refs.scaffold, texture_prefix);
441
- },
442
- saveSettings: function () {
443
- this.sceneSettings.push(this.$refs.scaffold.getState());
444
- },
445
- restoreSettings: function () {
446
- if (this.sceneSettings.length > 0)
447
- this.$refs.scaffold.setState(this.sceneSettings.pop());
448
- },
449
- viewModelClicked: function (location) {
450
- this.input = location;
451
- },
452
- screenCapture: function () {
453
- this.$refs.scaffold.captureScreenshot("capture.png");
454
- },
455
- setSceneToWindo: function () {
456
- window.scene = this.$refs.scaffold.$module.scene;
457
- },
458
- search: function (term) {
459
- this.$refs.scaffold.search(term, true);
460
- },
461
- fetchSuggestions: function (term, cb) {
462
- if (term === "" || !this.$refs.scaffold) {
463
- cb([]);
464
- }
465
- cb(
466
- this.$refs.scaffold.fetchSuggestions(term).map((item) => {
467
- const value = item.terms.length > 1 ? item.terms[1] : item.terms[0];
468
- return {
469
- value: value,
470
- label: value
471
- };
472
- })
473
- );
474
- console.log(
475
- "found suggestions",
476
- this.$refs.scaffold.fetchSuggestions(term)
477
- );
478
- },
479
- autoTumble: function () {
480
- const flag = this.tumbleOn;
481
- let cameracontrol =
482
- this.$refs.scaffold.$module.scene.getZincCameraControls();
483
- if (flag) {
484
- this.displayUI = false;
485
- cameracontrol.enableAutoTumble();
486
- if (this.tumbleDirection[0] === 0 && this.tumbleDirection[1] === 0) {
487
- this.tumbleDirection[0] = 1;
488
- }
489
- cameracontrol.autoTumble(this.tumbleDirection, Math.PI / 2, true);
490
- } else {
491
- this.displayUI = true;
492
- cameracontrol.stopAutoTumble();
493
- }
494
- },
495
- onReady: function () {
496
- console.log("ready");
497
- //window.scaffoldvuer = this.$refs.scaffold;
498
- this.$refs.dropzone.revokeURLs();
499
- if (this.readyCallback) {
500
- this.readyCallback(this.$refs.scaffold, texture_prefix);
501
- this.readyCallback = undefined;
502
- }
503
- },
504
- onSelected: function (data) {
505
- if (data && data.length > 0 && data[0].data.group) {
506
- delete this.$route.query["viewURL"];
507
- this.$refs.scaffold.showRegionTooltipWithAnnotations(data, true, true);
508
- if (this.onClickMarkers) this.$refs.scaffold.setMarkerModeForObjectsWithName(data[0].data.group, "on");
509
- //this.$router.replace({
510
- // query: { ...this.$route.query, region: data[0].data.group }
511
- //});
512
- }
513
- },
514
- changeMarkers: function() {
515
- if (this.markerLabels[0] === "right ventricle"){
516
- this.markerLabels = ["left atrium", "epicardium", "stomach"]
517
- } else {
518
- this.markerLabels = ["right ventricle"]
519
- }
520
- },
521
- onNavigated: function (data) {
522
- this.zoom = data.zoom;
523
- this.pos[0] = data.target[0];
524
- this.pos[1] = data.target[1];
525
- },
526
- onFilesDrop: function (payload) {
527
- if (payload.format == "gltf") this.format = "gltf";
528
- else this.format = "metadata";
529
- this.input = payload.url;
530
- },
531
- parseInput: function () {
532
- if (this.$route.query.url !== this.input) {
533
- const queries = { ...this.$route.query };
534
- if (this.input && this.input !== "") queries.url = this.input;
535
- this.$router.replace({
536
- query: { ...this.$route.query, url: this.input },
537
- });
538
- }
539
- },
540
- updateCurrentTime: function (val) {
541
- this.currentTime = val;
542
- },
543
- parseQuery: function (query) {
544
- if (query.url != this.url) {
545
- this._objects = [];
546
- }
547
- if (query.url) {
548
- this.url = query.url;
549
- } else {
550
- this.url =
551
- "https://mapcore-bucket1.s3-us-west-2.amazonaws.com/others/29_Jan_2020/heartICN_metadata.json";
552
- }
553
- if (this.url.includes(".gltf") || this.url.includes(".glb")) {
554
- this.format = "gltf";
555
- } else if (this.url.includes(".json")) {
556
- this.format = "metadata";
557
- }
558
- this.input = this.url;
559
- if (query.region) {
560
- this.region = query.region;
561
- } else {
562
- this.region = "";
563
- }
564
- if (query.viewURL) {
565
- this.viewURL = query.viewURL;
566
- } else {
567
- this.viewURL = "";
568
- }
569
- },
570
- },
571
- };
572
- </script>
573
-
574
- <style lang="scss">
575
- @import "~element-ui/packages/theme-chalk/src/button";
576
- @import "~element-ui/packages/theme-chalk/src/col";
577
- @import "~element-ui/packages/theme-chalk/src/icon";
578
- @import "~element-ui/packages/theme-chalk/src/input";
579
- @import "~element-ui/packages/theme-chalk/src/input-number";
580
- @import "~element-ui/packages/theme-chalk/src/switch";
581
- @import "~element-ui/packages/theme-chalk/src/popover";
582
- @import "~element-ui/packages/theme-chalk/src/row";
583
-
584
- #app {
585
- font-family: "Asap", sans-serif;
586
- -webkit-font-smoothing: antialiased;
587
- -moz-osx-font-smoothing: grayscale;
588
- text-align: center;
589
- color: #2c3e50;
590
- height: 100%;
591
- width: 100%;
592
- position: absolute;
593
- overflow: hidden;
594
- }
595
-
596
- body {
597
- margin: 0px;
598
- }
599
-
600
- .options-container {
601
- text-align: center;
602
- .el-row {
603
- margin-bottom: 8px;
604
- &:last-child {
605
- margin-bottom: 0;
606
- }
607
- }
608
- }
609
-
610
- .vuer {
611
- position: absolute;
612
- width: 100%;
613
- height: 100%;
614
- }
615
-
616
- .popover {
617
- top: 5px;
618
- right: 10px;
619
- position: absolute;
620
- }
621
-
622
- .options-container {
623
- .el-row {
624
- margin-bottom: 10px;
625
- &:last-child {
626
- margin-bottom: 0;
627
- }
628
- }
629
- }
630
-
631
- .autocomplete-popper {
632
- li {
633
- line-height: normal;
634
- padding: 7px;
635
-
636
- .value {
637
- text-align: left;
638
- white-space: initial;
639
- }
640
- }
641
- }
642
-
643
- .models-popover {
644
- top: 5px;
645
- position: absolute;
646
- }
647
-
648
- .tumble-direction {
649
- height: 20px;
650
- .el-input {
651
- width: 80px;
652
- height: 20px;
653
- padding: 0;
654
- input {
655
- padding: 0px;
656
- height: 20px;
657
- vertical-align: top;
658
- }
659
- }
660
- }
661
-
662
- .table-popover {
663
- opacity: 0.9;
664
- }
665
-
666
- svg.map-icon {
667
- color: $app-primary-color;
668
- }
669
- </style>
1
+ <template>
2
+ <div id="app">
3
+ <link
4
+ rel="stylesheet"
5
+ href="https://fonts.googleapis.com/css?family=Asap:400,400i,500,600,700&display=swap"
6
+ />
7
+ <drop-zone ref="dropzone" @files-drop="onFilesDrop">
8
+ <ScaffoldVuer
9
+ ref="scaffold"
10
+ class="vuer"
11
+ :display-u-i="displayUI"
12
+ :url="url"
13
+ :help-mode="helpMode"
14
+ :display-latest-changes="true"
15
+ :display-minimap="displayMinimap"
16
+ :display-markers="displayMarkers"
17
+ :enableOpenMapUI="true"
18
+ :minimap-settings="minimapSettings"
19
+ :show-colour-picker="showColourPicker"
20
+ :render="render"
21
+ :region="region"
22
+ :view-u-r-l="viewURL"
23
+ :format="format"
24
+ :marker-labels="markerLabels"
25
+ @open-map="openMap"
26
+ @on-ready="onReady"
27
+ @scaffold-selected="onSelected"
28
+ @scaffold-navigated="onNavigated"
29
+ @timeChanged="updateCurrentTime"
30
+ @zinc-object-added="objectAdded"
31
+ />
32
+ </drop-zone>
33
+
34
+ <el-popover
35
+ placement="bottom"
36
+ trigger="click"
37
+ width="500"
38
+ class="popover"
39
+ :append-to-body="false"
40
+ >
41
+ <div class="options-container">
42
+ <el-row :gutter="20">
43
+ <p>{{ selectedCoordinates }}</p>
44
+ </el-row>
45
+ <el-row :gutter="20">
46
+ <p v-if="currentTime !== 0">
47
+ time emited is: {{ currentTime.toFixed(2) }}
48
+ </p>
49
+ </el-row>
50
+ <el-row :gutter="20">
51
+ <el-col :span="4" :offset="1">
52
+ <el-switch v-model="displayUI" active-text="UI" />
53
+ </el-col>
54
+ <el-col :span="4" :offset="1">
55
+ <el-switch
56
+ v-model="displayMarkers"
57
+ active-text="Markers"
58
+ active-icon-class="el-icon-location"
59
+ active-color="#8300bf"
60
+ />
61
+ </el-col>
62
+ <el-col :span="4" :offset="1">
63
+ <el-switch
64
+ v-model="displayMinimap"
65
+ active-text="Minimap"
66
+ active-icon-class="el-icon-discover"
67
+ active-color="#8300bf"
68
+ />
69
+ </el-col>
70
+ </el-row>
71
+ <el-row :gutter="20">
72
+ <el-col :span="6">
73
+ <el-switch
74
+ v-model="tumbleOn"
75
+ active-text="Tumble"
76
+ active-color="#8300bf"
77
+ />
78
+ </el-col>
79
+ <el-col :span="1"> x: </el-col>
80
+ <el-col :span="3">
81
+ <el-input-number
82
+ class="tumble-direction"
83
+ controls-position="right"
84
+ v-model="tumbleDirection[0]"
85
+ :min="-1.0"
86
+ :max="1.0"
87
+ :controls="false"
88
+ placeholder="Please input"
89
+ label="x"
90
+ @change="autoTumble"
91
+ />
92
+ </el-col>
93
+ <el-col :span="1" :offset="1"> y: </el-col>
94
+ <el-col :span="3">
95
+ <el-input-number
96
+ class="tumble-direction"
97
+ controls-position="right"
98
+ v-model="tumbleDirection[1]"
99
+ :min="-1.0"
100
+ :max="1.0"
101
+ :controls="false"
102
+ placeholder="Please input"
103
+ label="y"
104
+ @change="autoTumble"
105
+ />
106
+ </el-col>
107
+ </el-row>
108
+ <el-row :gutter="20">
109
+ <el-button size="mini" @click="helpMode = !helpMode">
110
+ Help Mode
111
+ </el-button>
112
+ <el-button size="mini" @click="screenCapture()"> Capture </el-button>
113
+ <el-button size="mini" @click="changeMarkers"> Change Markers </el-button>
114
+ </el-row>
115
+ <el-row :gutter="10">
116
+ <el-button size="mini" @click="saveSettings()">
117
+ Save Settings
118
+ </el-button>
119
+ <el-button size="mini" @click="restoreSettings()">
120
+ Restore Settings
121
+ </el-button>
122
+ <el-button size="mini" @click="exportGLB()"> Export GLB </el-button>
123
+ <el-button size="mini" @click="exportGLTF()"> Export GLTF </el-button>
124
+ </el-row>
125
+ <el-row :gutter="30">
126
+ <el-col :span="7" :offset="2">
127
+ <el-switch
128
+ v-model="syncMode"
129
+ active-text="Sync Mode"
130
+ active-color="#8300bf"
131
+ />
132
+ <el-row v-if="syncMode">
133
+ <el-input-number
134
+ v-model="zoom"
135
+ :min="1.0"
136
+ :controls="false"
137
+ placeholder="Please input"
138
+ label="zoom"
139
+ />
140
+ <el-input-number
141
+ v-model="pos[0]"
142
+ :min="-1.0"
143
+ :max="1.0"
144
+ :controls="false"
145
+ placeholder="Please input"
146
+ label="x"
147
+ />
148
+ <el-input-number
149
+ v-model="pos[1]"
150
+ :min="-1.0"
151
+ :max="1.0"
152
+ :controls="false"
153
+ label="y"
154
+ />
155
+ </el-row>
156
+ </el-col>
157
+ </el-row>
158
+ <el-row :gutter="30">
159
+ <el-col :span="7" :offset="4">
160
+ <el-switch
161
+ v-model="render"
162
+ active-text="Rendering"
163
+ active-color="#8300bf"
164
+ />
165
+ </el-col>
166
+ <el-col :span="8" :offset="1">
167
+ <el-switch
168
+ v-model="renderInfoOn"
169
+ active-text="Renderer Info"
170
+ active-color="#8300bf"
171
+ />
172
+ </el-col>
173
+ </el-row>
174
+ <template v-if="renderInfoOn && rendererInfo">
175
+ <el-row>
176
+ <el-col
177
+ v-for="(value, name) in rendererInfo.memory"
178
+ :key="name"
179
+ :offset="4"
180
+ :span="6"
181
+ >
182
+ {{ name }} : {{ value }}
183
+ </el-col>
184
+ </el-row>
185
+ <el-row>
186
+ <el-col
187
+ v-for="(value, name) in rendererInfo.render"
188
+ :key="name"
189
+ :offset="1"
190
+ :span="6"
191
+ >
192
+ {{ name }} : {{ value }}
193
+ </el-col>
194
+ </el-row>
195
+ </template>
196
+ <el-row :gutter="20">
197
+ Feature Demo:
198
+ <el-button size="mini" @click="featureTextureVolume(false)">
199
+ Texture volume
200
+ </el-button>
201
+ <el-button size="mini" @click="featureTextureSlides(false)">
202
+ Texture slides
203
+ </el-button>
204
+ <el-button size="mini" @click="featureTextureVolume(true)">
205
+ Body volume
206
+ </el-button>
207
+ <el-button size="mini" @click="featureTextureSlides(true)">
208
+ Body slides
209
+ </el-button>
210
+ <el-switch
211
+ v-model="onClickMarkers"
212
+ active-text="Markers On Selection"
213
+ active-color="#8300bf"
214
+ />
215
+ </el-row>
216
+ <el-row :gutter="20">
217
+ <el-input
218
+ v-model="input"
219
+ type="textarea"
220
+ autosize
221
+ placeholder="Please input"
222
+ style="padding-left: 5%; width: 90%"
223
+ />
224
+ </el-row>
225
+ </div>
226
+ <el-button
227
+ slot="reference"
228
+ icon="el-icon-setting"
229
+ @click="setSceneToWindo"
230
+ >
231
+ Options
232
+ </el-button>
233
+ </el-popover>
234
+ <el-popover
235
+ placement="bottom"
236
+ trigger="click"
237
+ width="800"
238
+ class="models-popover"
239
+ popper-class="table-popover"
240
+ :append-to-body="false"
241
+ >
242
+ <ModelsTable @viewModelClicked="viewModelClicked" />
243
+ <el-button slot="reference" icon="el-icon-folder-opened">
244
+ Models
245
+ </el-button>
246
+ <el-autocomplete
247
+ slot="reference"
248
+ v-model="searchText"
249
+ class="search-box"
250
+ placeholder="Search"
251
+ :fetch-suggestions="fetchSuggestions"
252
+ :popper-append-to-body="false"
253
+ popper-class="autocomplete-popper"
254
+ @keyup.enter.native="search(searchText)"
255
+ @select="search(searchText)"
256
+ >
257
+ <template slot-scope="{ item }">
258
+ <div class="value">
259
+ {{ item.value }}
260
+ </div>
261
+ </template>
262
+ </el-autocomplete>
263
+ </el-popover>
264
+ </div>
265
+ </template>
266
+
267
+ <script>
268
+ /* eslint-disable no-alert, no-console */
269
+ import { ScaffoldVuer } from "./components/index.js";
270
+ import DropZone from "./app/DropZone.vue";
271
+ import ModelsTable from "./app/ModelsTable.vue";
272
+ import {testSlides, testVolume} from "./app/TextureDemos.js";
273
+ import Vue from "vue";
274
+ import {
275
+ Button,
276
+ Col,
277
+ Icon,
278
+ Input,
279
+ InputNumber,
280
+ Popover,
281
+ Row,
282
+ Switch,
283
+ Autocomplete,
284
+ } from "element-ui";
285
+ import lang from "element-ui/lib/locale/lang/en";
286
+ import locale from "element-ui/lib/locale";
287
+
288
+ locale.use(lang);
289
+ Vue.use(Button);
290
+ Vue.use(Col);
291
+ Vue.use(Icon);
292
+ Vue.use(Input);
293
+ Vue.use(InputNumber);
294
+ Vue.use(Popover);
295
+ Vue.use(Row);
296
+ Vue.use(Switch);
297
+ Vue.use(Autocomplete);
298
+
299
+ let texture_prefix = undefined;
300
+
301
+ export default {
302
+ name: "App",
303
+ components: {
304
+ DropZone,
305
+ ScaffoldVuer,
306
+ ModelsTable,
307
+ },
308
+ data: function () {
309
+ return {
310
+ url: undefined,
311
+ input: undefined,
312
+ displayUI: true,
313
+ selectedCoordinates: undefined,
314
+ helpMode: false,
315
+ displayMarkers: false,
316
+ onClickMarkers: false,
317
+ syncMode: false,
318
+ currentTime: 0,
319
+ displayMinimap: false,
320
+ tumbleOn: false,
321
+ tumbleDirection: [1.0, 0.0],
322
+ showColourPicker: true,
323
+ minimapSettings: {
324
+ x_offset: 16,
325
+ y_offset: 50,
326
+ width: 128,
327
+ height: 128,
328
+ align: "top-right",
329
+ },
330
+ markerLabels: ["left atrium", "epicardium"],
331
+ render: true,
332
+ region: "",
333
+ viewURL: "",
334
+ renderInfoOn: false,
335
+ rendererInfo: undefined,
336
+ zoom: 1,
337
+ pos: [0, 0],
338
+ format: "metadata",
339
+ sceneSettings: [],
340
+ searchInput: "",
341
+ searchText: "",
342
+ loadTextureVolumeOnReady: false,
343
+ readyCallback: undefined,
344
+ };
345
+ },
346
+ watch: {
347
+ input: function () {
348
+ this.parseInput();
349
+ },
350
+ tumbleOn: function () {
351
+ this.autoTumble();
352
+ },
353
+ "$route.query": {
354
+ handler: "parseQuery",
355
+ deep: true,
356
+ immediate: true,
357
+ },
358
+ syncMode: function (val) {
359
+ this.$refs.scaffold.toggleSyncControl(val);
360
+ },
361
+ },
362
+ mounted: function () {
363
+ this._objects = [];
364
+ this.selectedCoordinates =
365
+ this.$refs.scaffold.getDynamicSelectedCoordinates();
366
+ this.rendererInfo = this.$refs.scaffold.getRendererInfo();
367
+ },
368
+ created: function () {
369
+ texture_prefix = process.env.VUE_APP_TEXTURE_FOOT_PREFIX;
370
+ },
371
+ methods: {
372
+ exportGLTF: function () {
373
+ this.$refs.scaffold.exportGLTF(false).then((data) => {
374
+ let dataStr =
375
+ "data:text/json;charset=utf-8," +
376
+ encodeURIComponent(JSON.stringify(data));
377
+ let hrefElement = document.createElement("a");
378
+ document.body.append(hrefElement);
379
+ hrefElement.download = `export.gltf`;
380
+ hrefElement.href = dataStr;
381
+ hrefElement.click();
382
+ hrefElement.remove();
383
+ });
384
+ },
385
+ exportGLB: function () {
386
+ this.$refs.scaffold.exportGLTF(true).then((data) => {
387
+ let blob = new Blob([data], { type: "octet/stream" });
388
+ let url = window.URL.createObjectURL(blob);
389
+ let hrefElement = document.createElement("a");
390
+ document.body.append(hrefElement);
391
+ hrefElement.download = `export.glb`;
392
+ hrefElement.href = url;
393
+ hrefElement.click();
394
+ hrefElement.remove();
395
+ });
396
+ },
397
+ objectAdded: function (zincObject) {
398
+ if (this._objects.length === 0)
399
+ zincObject.setMarkerMode("on");
400
+ console.log(zincObject);
401
+ this._objects.push(zincObject);
402
+ },
403
+ openMap: function (map) {
404
+ console.log(map);
405
+ },
406
+ featureTextureVolume: async function (overlap) {
407
+ //this.$refs.scaffold.clearScene();
408
+ //volume matrix to fit the human body
409
+ //[-100, 0, 0, 0, 0, -100, 0, 0, 0, 0, -100, 0, -60, -100, 30, 1]
410
+ if (overlap) {
411
+ const url =
412
+ "https://mapcore-bucket1.s3.us-west-2.amazonaws.com/WholeBody/6-match-2023/human/nerve_metadata.json";
413
+ if (this.$route.query.url !== encodeURI(url)) {
414
+ this.$router.replace({ query: { url } });
415
+ this.readyCallback = testVolume;
416
+ return;
417
+ } else {
418
+ testVolume(this.$refs.scaffold, texture_prefix);
419
+ return;
420
+ }
421
+ }
422
+ this.$refs.scaffold.clearScene();
423
+ testVolume(this.$refs.scaffold, texture_prefix);
424
+ },
425
+ featureTextureSlides: async function (overlap) {
426
+ //Test texture
427
+ if (overlap) {
428
+ const url =
429
+ "https://mapcore-bucket1.s3.us-west-2.amazonaws.com/WholeBody/6-match-2023/human/nerve_metadata.json";
430
+ if (this.$route.query.url !== encodeURI(url)) {
431
+ this.$router.replace({ query: { url } });
432
+ this.readyCallback = testSlides;
433
+ return;
434
+ } else {
435
+ testSlides(this.$refs.scaffold, texture_prefix);
436
+ return;
437
+ }
438
+ }
439
+ this.$refs.scaffold.clearScene();
440
+ testSlides(this.$refs.scaffold, texture_prefix);
441
+ },
442
+ saveSettings: function () {
443
+ this.sceneSettings.push(this.$refs.scaffold.getState());
444
+ },
445
+ restoreSettings: function () {
446
+ if (this.sceneSettings.length > 0)
447
+ this.$refs.scaffold.setState(this.sceneSettings.pop());
448
+ },
449
+ viewModelClicked: function (location) {
450
+ this.input = location;
451
+ },
452
+ screenCapture: function () {
453
+ this.$refs.scaffold.captureScreenshot("capture.png");
454
+ },
455
+ setSceneToWindo: function () {
456
+ window.scene = this.$refs.scaffold.$module.scene;
457
+ },
458
+ search: function (term) {
459
+ this.$refs.scaffold.search(term, true);
460
+ },
461
+ fetchSuggestions: function (term, cb) {
462
+ if (term === "" || !this.$refs.scaffold) {
463
+ cb([]);
464
+ }
465
+ cb(
466
+ this.$refs.scaffold.fetchSuggestions(term).map((item) => {
467
+ const value = item.terms.length > 1 ? item.terms[1] : item.terms[0];
468
+ return {
469
+ value: value,
470
+ label: value
471
+ };
472
+ })
473
+ );
474
+ console.log(
475
+ "found suggestions",
476
+ this.$refs.scaffold.fetchSuggestions(term)
477
+ );
478
+ },
479
+ autoTumble: function () {
480
+ const flag = this.tumbleOn;
481
+ let cameracontrol =
482
+ this.$refs.scaffold.$module.scene.getZincCameraControls();
483
+ if (flag) {
484
+ this.displayUI = false;
485
+ cameracontrol.enableAutoTumble();
486
+ if (this.tumbleDirection[0] === 0 && this.tumbleDirection[1] === 0) {
487
+ this.tumbleDirection[0] = 1;
488
+ }
489
+ cameracontrol.autoTumble(this.tumbleDirection, Math.PI / 2, true);
490
+ } else {
491
+ this.displayUI = true;
492
+ cameracontrol.stopAutoTumble();
493
+ }
494
+ },
495
+ onReady: function () {
496
+ console.log("ready");
497
+ //window.scaffoldvuer = this.$refs.scaffold;
498
+ this.$refs.dropzone.revokeURLs();
499
+ if (this.readyCallback) {
500
+ this.readyCallback(this.$refs.scaffold, texture_prefix);
501
+ this.readyCallback = undefined;
502
+ }
503
+ },
504
+ onSelected: function (data) {
505
+ if (data && data.length > 0 && data[0].data.group) {
506
+ delete this.$route.query["viewURL"];
507
+ this.$refs.scaffold.showRegionTooltipWithAnnotations(data, true, true);
508
+ if (this.onClickMarkers) this.$refs.scaffold.setMarkerModeForObjectsWithName(data[0].data.group, "on");
509
+ //this.$router.replace({
510
+ // query: { ...this.$route.query, region: data[0].data.group }
511
+ //});
512
+ }
513
+ },
514
+ changeMarkers: function() {
515
+ if (this.markerLabels[0] === "right ventricle"){
516
+ this.markerLabels = ["left atrium", "epicardium", "stomach"];
517
+ } else {
518
+ this.markerLabels = ["right ventricle"];
519
+ }
520
+ },
521
+ onNavigated: function (data) {
522
+ this.zoom = data.zoom;
523
+ this.pos[0] = data.target[0];
524
+ this.pos[1] = data.target[1];
525
+ },
526
+ onFilesDrop: function (payload) {
527
+ if (payload.format == "gltf") this.format = "gltf";
528
+ else this.format = "metadata";
529
+ this.input = payload.url;
530
+ },
531
+ parseInput: function () {
532
+ if (this.$route.query.url !== this.input) {
533
+ const queries = { ...this.$route.query };
534
+ if (this.input && this.input !== "") queries.url = this.input;
535
+ this.$router.replace({
536
+ query: { ...this.$route.query, url: this.input },
537
+ });
538
+ }
539
+ },
540
+ updateCurrentTime: function (val) {
541
+ this.currentTime = val;
542
+ },
543
+ parseQuery: function (query) {
544
+ if (query.url != this.url) {
545
+ this._objects = [];
546
+ }
547
+ if (query.url) {
548
+ this.url = query.url;
549
+ } else {
550
+ this.url =
551
+ "https://mapcore-bucket1.s3-us-west-2.amazonaws.com/others/29_Jan_2020/heartICN_metadata.json";
552
+ }
553
+ if (this.url.includes(".gltf") || this.url.includes(".glb")) {
554
+ this.format = "gltf";
555
+ } else if (this.url.includes(".json")) {
556
+ this.format = "metadata";
557
+ }
558
+ this.input = this.url;
559
+ if (query.region) {
560
+ this.region = query.region;
561
+ } else {
562
+ this.region = "";
563
+ }
564
+ if (query.viewURL) {
565
+ this.viewURL = query.viewURL;
566
+ } else {
567
+ this.viewURL = "";
568
+ }
569
+ },
570
+ },
571
+ };
572
+ </script>
573
+
574
+ <style lang="scss">
575
+ @import "~element-ui/packages/theme-chalk/src/button";
576
+ @import "~element-ui/packages/theme-chalk/src/col";
577
+ @import "~element-ui/packages/theme-chalk/src/icon";
578
+ @import "~element-ui/packages/theme-chalk/src/input";
579
+ @import "~element-ui/packages/theme-chalk/src/input-number";
580
+ @import "~element-ui/packages/theme-chalk/src/switch";
581
+ @import "~element-ui/packages/theme-chalk/src/popover";
582
+ @import "~element-ui/packages/theme-chalk/src/row";
583
+
584
+ #app {
585
+ font-family: "Asap", sans-serif;
586
+ -webkit-font-smoothing: antialiased;
587
+ -moz-osx-font-smoothing: grayscale;
588
+ text-align: center;
589
+ color: #2c3e50;
590
+ height: 100%;
591
+ width: 100%;
592
+ position: absolute;
593
+ overflow: hidden;
594
+ }
595
+
596
+ body {
597
+ margin: 0px;
598
+ }
599
+
600
+ .options-container {
601
+ text-align: center;
602
+ .el-row {
603
+ margin-bottom: 8px;
604
+ &:last-child {
605
+ margin-bottom: 0;
606
+ }
607
+ }
608
+ }
609
+
610
+ .vuer {
611
+ position: absolute;
612
+ width: 100%;
613
+ height: 100%;
614
+ }
615
+
616
+ .popover {
617
+ top: 5px;
618
+ right: 10px;
619
+ position: absolute;
620
+ }
621
+
622
+ .options-container {
623
+ .el-row {
624
+ margin-bottom: 10px;
625
+ &:last-child {
626
+ margin-bottom: 0;
627
+ }
628
+ }
629
+ }
630
+
631
+ .autocomplete-popper {
632
+ li {
633
+ line-height: normal;
634
+ padding: 7px;
635
+
636
+ .value {
637
+ text-align: left;
638
+ white-space: initial;
639
+ }
640
+ }
641
+ }
642
+
643
+ .models-popover {
644
+ top: 5px;
645
+ position: absolute;
646
+ }
647
+
648
+ .tumble-direction {
649
+ height: 20px;
650
+ .el-input {
651
+ width: 80px;
652
+ height: 20px;
653
+ padding: 0;
654
+ input {
655
+ padding: 0px;
656
+ height: 20px;
657
+ vertical-align: top;
658
+ }
659
+ }
660
+ }
661
+
662
+ .table-popover {
663
+ opacity: 0.9;
664
+ }
665
+
666
+ svg.map-icon {
667
+ color: $app-primary-color;
668
+ }
669
+ </style>