@geode/opengeodeweb-front 10.30.0-rc.3 → 10.30.0-rc.4
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.
- package/app/components/Viewer/ObjectTree/Base/TreeRow.vue +58 -20
- package/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +42 -40
- package/app/composables/hover_highlight.js +6 -0
- package/app/composables/use_overlapping_picker.js +15 -2
- package/app/stores/app.js +20 -2
- package/app/stores/data.js +32 -1
- package/app/stores/data_style.js +11 -0
- package/app/stores/hybrid_viewer.js +5 -5
- package/app/utils/import_workflow.js +8 -3
- package/internal/stores/hybrid_viewer.js +8 -63
- package/internal/stores/hybrid_viewer_brightness.js +62 -0
- package/package.json +1 -1
- package/tests/unit/composables/project_manager.nuxt.test.js +1 -0
- package/tests/unit/stores/data.nuxt.test.js +35 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { useDataStore } from "@ogw_front/stores/data";
|
|
3
|
+
|
|
4
|
+
const dataStore = useDataStore();
|
|
5
|
+
|
|
2
6
|
const { item, itemProps, selection, isSelected, getIndeterminate } = defineProps({
|
|
3
7
|
item: { type: Object, required: true },
|
|
4
8
|
itemProps: { type: Object, required: true },
|
|
@@ -7,13 +11,34 @@ const { item, itemProps, selection, isSelected, getIndeterminate } = defineProps
|
|
|
7
11
|
getIndeterminate: { type: Function, required: true },
|
|
8
12
|
});
|
|
9
13
|
|
|
10
|
-
defineEmits(["toggle-open", "toggle-select", "hover-eye-enter", "hover-eye-leave"]);
|
|
14
|
+
const emit = defineEmits(["toggle-open", "toggle-select", "hover-eye-enter", "hover-eye-leave"]);
|
|
11
15
|
|
|
12
16
|
const INDENT_STEP = 10;
|
|
17
|
+
|
|
18
|
+
function triggerHorizonStackModal(rawItem) {
|
|
19
|
+
globalThis.dispatchEvent(new CustomEvent("open-horizon-stack-modal", { detail: rawItem }));
|
|
20
|
+
}
|
|
21
|
+
const isHorizonStack = computed(() => item.raw.geode_object_type === "HorizonStack3D");
|
|
22
|
+
const isViewable = computed(() => dataStore.isItemViewable(item.raw));
|
|
23
|
+
const showEyeButton = computed(
|
|
24
|
+
() => !isHorizonStack.value && item.raw.title !== "HorizonStack3D" && isViewable.value,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
function handleRowClick(event) {
|
|
28
|
+
if (isHorizonStack.value) {
|
|
29
|
+
if (!item.isLeaf) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
triggerHorizonStackModal(item.raw);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
13
38
|
</script>
|
|
14
39
|
|
|
15
40
|
<template>
|
|
16
|
-
<div class="tree-row-content d-flex align-center px-2 ps-2 w-100">
|
|
41
|
+
<div class="tree-row-content d-flex align-center px-2 ps-2 w-100" @click="handleRowClick">
|
|
17
42
|
<div
|
|
18
43
|
v-if="item.depth > 0"
|
|
19
44
|
class="flex-shrink-0"
|
|
@@ -30,24 +55,37 @@ const INDENT_STEP = 10;
|
|
|
30
55
|
/>
|
|
31
56
|
<div v-else class="icon-placeholder" />
|
|
32
57
|
|
|
33
|
-
<v-
|
|
34
|
-
v-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
<template v-if="selection.selectable">
|
|
59
|
+
<v-btn
|
|
60
|
+
v-if="isHorizonStack && item.isLeaf"
|
|
61
|
+
icon="mdi-layers-triple"
|
|
62
|
+
variant="text"
|
|
63
|
+
density="compact"
|
|
64
|
+
color="black"
|
|
65
|
+
class="flex-shrink-0"
|
|
66
|
+
style="z-index: 4"
|
|
67
|
+
@click.stop="triggerHorizonStackModal(item.raw)"
|
|
68
|
+
@mousedown.stop
|
|
69
|
+
/>
|
|
70
|
+
<v-btn
|
|
71
|
+
v-else-if="showEyeButton"
|
|
72
|
+
:icon="
|
|
73
|
+
getIndeterminate(item.raw)
|
|
74
|
+
? 'mdi-eye-minus-outline'
|
|
75
|
+
: isSelected(item.raw)
|
|
76
|
+
? 'mdi-eye'
|
|
77
|
+
: 'mdi-eye-off-outline'
|
|
78
|
+
"
|
|
79
|
+
variant="text"
|
|
80
|
+
density="compact"
|
|
81
|
+
color="black"
|
|
82
|
+
class="flex-shrink-0"
|
|
83
|
+
@click.stop="$emit('toggle-select', item.raw)"
|
|
84
|
+
@mousedown.stop
|
|
85
|
+
@mouseenter="$emit('hover-eye-enter', item.raw)"
|
|
86
|
+
@mouseleave="$emit('hover-eye-leave', item.raw)"
|
|
87
|
+
/>
|
|
88
|
+
</template>
|
|
51
89
|
</div>
|
|
52
90
|
|
|
53
91
|
<div class="tree-title flex-grow-1 overflow-hidden d-flex align-center ms-1 pt-1">
|
|
@@ -179,46 +179,48 @@ function expandAll() {
|
|
|
179
179
|
</template>
|
|
180
180
|
|
|
181
181
|
<template #append="{ item }">
|
|
182
|
-
<v-
|
|
183
|
-
v-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
v-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
v-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
182
|
+
<template v-if="item.geode_object_type !== 'HorizonStack3D'">
|
|
183
|
+
<v-btn
|
|
184
|
+
v-if="item.viewer_type"
|
|
185
|
+
icon="mdi-target"
|
|
186
|
+
size="medium"
|
|
187
|
+
variant="text"
|
|
188
|
+
v-tooltip="'Focus camera on object'"
|
|
189
|
+
@click.stop="hybridViewerStore.focusCameraOnObject(item.id)"
|
|
190
|
+
/>
|
|
191
|
+
<v-btn
|
|
192
|
+
v-if="isModel(item)"
|
|
193
|
+
icon="mdi-magnify-expand"
|
|
194
|
+
size="medium"
|
|
195
|
+
class="ml-2"
|
|
196
|
+
variant="text"
|
|
197
|
+
v-tooltip="'Model\'s mesh components'"
|
|
198
|
+
@click.stop="
|
|
199
|
+
treeviewStore.displayAdditionalTree(
|
|
200
|
+
item.id,
|
|
201
|
+
item.title,
|
|
202
|
+
item.geode_object_type,
|
|
203
|
+
'model_components',
|
|
204
|
+
)
|
|
205
|
+
"
|
|
206
|
+
/>
|
|
207
|
+
<v-btn
|
|
208
|
+
v-if="isModel(item) && hasCollectionsMap[item.id]"
|
|
209
|
+
icon="mdi-format-list-group"
|
|
210
|
+
size="medium"
|
|
211
|
+
class="ml-2"
|
|
212
|
+
variant="text"
|
|
213
|
+
v-tooltip="'Model\'s collections'"
|
|
214
|
+
@click.stop="
|
|
215
|
+
treeviewStore.displayAdditionalTree(
|
|
216
|
+
item.id,
|
|
217
|
+
item.title,
|
|
218
|
+
item.geode_object_type,
|
|
219
|
+
'model_collections',
|
|
220
|
+
)
|
|
221
|
+
"
|
|
222
|
+
/>
|
|
223
|
+
</template>
|
|
222
224
|
</template>
|
|
223
225
|
</CommonTreeView>
|
|
224
226
|
</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useDataStore } from "@ogw_front/stores/data";
|
|
1
2
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
2
3
|
import vtk_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
3
4
|
|
|
@@ -5,6 +6,7 @@ const HOVER_DELAY = 200;
|
|
|
5
6
|
|
|
6
7
|
export function useHoverhighlight() {
|
|
7
8
|
const viewerStore = useViewerStore();
|
|
9
|
+
const dataStore = useDataStore();
|
|
8
10
|
let timer = undefined;
|
|
9
11
|
let currentId = undefined;
|
|
10
12
|
let currentType = undefined;
|
|
@@ -20,6 +22,10 @@ export function useHoverhighlight() {
|
|
|
20
22
|
currentId = id;
|
|
21
23
|
currentType = type;
|
|
22
24
|
|
|
25
|
+
if (!(await dataStore.isItemViewable(id))) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
let block_ids = [];
|
|
24
30
|
if (typeof block_ids_provider === "function") {
|
|
25
31
|
block_ids = await block_ids_provider();
|
|
@@ -64,8 +64,21 @@ export function useOverlappingPicker() {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async function get_viewer_id({ x, y, containerWidth, containerHeight, containerRect }) {
|
|
67
|
-
const activeIds = new Set(dataItems.value.map((
|
|
68
|
-
const
|
|
67
|
+
const activeIds = new Set(dataItems.value.map((i) => i.id));
|
|
68
|
+
const visibleStyleIds = Object.keys(dataStyleStore.styles).filter(
|
|
69
|
+
(styleId) => activeIds.has(styleId) && dataStyleStore.objectVisibility(styleId),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const viewableChecks = await Promise.all(
|
|
73
|
+
visibleStyleIds.map(async (styleId) => {
|
|
74
|
+
try {
|
|
75
|
+
return (await dataStore.isItemViewable(styleId)) ? styleId : undefined;
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
const ids = viewableChecks.filter(Boolean);
|
|
69
82
|
|
|
70
83
|
const result = { id: undefined, viewer_id: undefined };
|
|
71
84
|
let pickedResponse = undefined;
|
package/app/stores/app.js
CHANGED
|
@@ -9,11 +9,10 @@ import { useInfraStore } from "@ogw_front/stores/infra";
|
|
|
9
9
|
// oxlint-disable-next-line max-lines-per-function, max-statements
|
|
10
10
|
export const useAppStore = defineStore("app", () => {
|
|
11
11
|
const stores = [];
|
|
12
|
+
const globalComponents = ref(new Map());
|
|
12
13
|
const default_local_port = ref(globalThis.location.port);
|
|
13
14
|
const status = ref(Status.NOT_CONNECTED);
|
|
14
|
-
|
|
15
15
|
const protocol = computed(() => getRestApiProtocol());
|
|
16
|
-
|
|
17
16
|
const port = computed(() => getRestApiPort(default_local_port.value));
|
|
18
17
|
|
|
19
18
|
const base_url = computed(() => {
|
|
@@ -25,6 +24,22 @@ export const useAppStore = defineStore("app", () => {
|
|
|
25
24
|
return app_url;
|
|
26
25
|
});
|
|
27
26
|
|
|
27
|
+
function registerGlobalComponent(extensionId, componentId, component) {
|
|
28
|
+
if (!globalComponents.value.has(extensionId)) {
|
|
29
|
+
globalComponents.value.set(extensionId, new Map());
|
|
30
|
+
}
|
|
31
|
+
globalComponents.value.get(extensionId).set(componentId, component);
|
|
32
|
+
console.log(
|
|
33
|
+
`[AppStore] Registered global component ${componentId} for extension ${extensionId}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function unregisterGlobalComponent(extensionId, componentId) {
|
|
38
|
+
if (globalComponents.value.has(extensionId)) {
|
|
39
|
+
globalComponents.value.get(extensionId).delete(componentId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
function registerStore(store) {
|
|
29
44
|
const isAlreadyRegistered = stores.some((registeredStore) => registeredStore.$id === store.$id);
|
|
30
45
|
if (isAlreadyRegistered) {
|
|
@@ -311,5 +326,8 @@ export const useAppStore = defineStore("app", () => {
|
|
|
311
326
|
createProjectFolder,
|
|
312
327
|
start_request,
|
|
313
328
|
stop_request,
|
|
329
|
+
globalComponents,
|
|
330
|
+
registerGlobalComponent,
|
|
331
|
+
unregisterGlobalComponent,
|
|
314
332
|
};
|
|
315
333
|
});
|
package/app/stores/data.js
CHANGED
|
@@ -11,6 +11,33 @@ import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
|
11
11
|
|
|
12
12
|
const viewer_generic_schemas = viewer_schemas.opengeodeweb_viewer.generic;
|
|
13
13
|
|
|
14
|
+
function checkItemViewable(item) {
|
|
15
|
+
if (!item || typeof item !== "object") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (item.is_viewable !== undefined) {
|
|
19
|
+
return Boolean(item.is_viewable);
|
|
20
|
+
}
|
|
21
|
+
if (item.binary_light_viewable !== undefined) {
|
|
22
|
+
return item.binary_light_viewable !== "not_viewable";
|
|
23
|
+
}
|
|
24
|
+
if (
|
|
25
|
+
item.geode_object_type === "HorizonStack3D" ||
|
|
26
|
+
item.id === "HorizonStack3D" ||
|
|
27
|
+
item.title === "HorizonStack3D"
|
|
28
|
+
) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isItemViewable(itemOrId) {
|
|
35
|
+
if (typeof itemOrId === "string") {
|
|
36
|
+
return database.data.get(itemOrId).then(checkItemViewable);
|
|
37
|
+
}
|
|
38
|
+
return checkItemViewable(itemOrId);
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
// oxlint-disable-next-line max-lines-per-function, max-statements
|
|
15
42
|
export const useDataStore = defineStore("data", () => {
|
|
16
43
|
const viewerStore = useViewerStore();
|
|
@@ -93,8 +120,11 @@ export const useDataStore = defineStore("data", () => {
|
|
|
93
120
|
geode_object_type: new_item.geode_object_type,
|
|
94
121
|
visible: true,
|
|
95
122
|
created_at: new Date().toISOString(),
|
|
96
|
-
|
|
123
|
+
is_viewable: new_item.is_viewable,
|
|
97
124
|
};
|
|
125
|
+
if (new_item.binary_light_viewable !== undefined && new_item.binary_light_viewable !== null) {
|
|
126
|
+
itemData.binary_light_viewable = new_item.binary_light_viewable;
|
|
127
|
+
}
|
|
98
128
|
return data_db.put(itemData);
|
|
99
129
|
}
|
|
100
130
|
|
|
@@ -213,6 +243,7 @@ export const useDataStore = defineStore("data", () => {
|
|
|
213
243
|
item,
|
|
214
244
|
allItems,
|
|
215
245
|
refItem,
|
|
246
|
+
isItemViewable,
|
|
216
247
|
meshComponentType,
|
|
217
248
|
registerObject,
|
|
218
249
|
deregisterObject,
|
package/app/stores/data_style.js
CHANGED
|
@@ -21,6 +21,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
21
21
|
|
|
22
22
|
async function setVisibility(id, visibility) {
|
|
23
23
|
const item = await dataStore.item(id);
|
|
24
|
+
if (!(await dataStore.isItemViewable(item))) {
|
|
25
|
+
return dataStyleState.mutateStyle(id, { visibility });
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
const { viewer_type } = item;
|
|
25
29
|
|
|
26
30
|
if (viewer_type === "mesh") {
|
|
@@ -34,6 +38,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
34
38
|
|
|
35
39
|
async function applyDefaultStyle(id) {
|
|
36
40
|
const item = await dataStore.item(id);
|
|
41
|
+
if (!(await dataStore.isItemViewable(item))) {
|
|
42
|
+
throw new Error(`applyDefaultStyle called for non-viewable item: ${id}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
const { viewer_type } = item;
|
|
38
46
|
|
|
39
47
|
if (viewer_type === "mesh") {
|
|
@@ -81,6 +89,9 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
|
|
|
81
89
|
const ids = Object.keys(dataStyleState.styles.value);
|
|
82
90
|
const promises = ids.map(async (id) => {
|
|
83
91
|
const meta = await dataStore.item(id);
|
|
92
|
+
if (!(await dataStore.isItemViewable(meta))) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
84
95
|
const viewerType = meta.viewer_type;
|
|
85
96
|
if (viewerType === "mesh") {
|
|
86
97
|
return meshStyleStore.applyMeshStyle(id);
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
performSyncRemoteCamera,
|
|
13
13
|
} from "@ogw_internal/stores/hybrid_viewer_camera";
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
createClearHoverData,
|
|
16
|
+
createHoverHighlight,
|
|
17
|
+
} from "@ogw_internal/stores/hybrid_viewer_highlight";
|
|
18
|
+
import {
|
|
16
19
|
performAddItem,
|
|
17
20
|
performClear,
|
|
18
21
|
performClearHoverHighlight,
|
|
@@ -22,10 +25,7 @@ import {
|
|
|
22
25
|
performSetVisibility,
|
|
23
26
|
performSetZScaling,
|
|
24
27
|
} from "@ogw_internal/stores/hybrid_viewer";
|
|
25
|
-
import {
|
|
26
|
-
createClearHoverData,
|
|
27
|
-
createHoverHighlight,
|
|
28
|
-
} from "@ogw_internal/stores/hybrid_viewer_highlight";
|
|
28
|
+
import { computeAverageBrightness } from "@ogw_internal/stores/hybrid_viewer_brightness";
|
|
29
29
|
import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
|
|
30
30
|
import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
|
|
31
31
|
import { newInstance as vtkMapper } from "@kitware/vtk.js/Rendering/Core/Mapper";
|
|
@@ -61,14 +61,19 @@ async function importItem(item) {
|
|
|
61
61
|
item.viewer_type === "model" ? dataStore.addComponentRelations(item) : Promise.resolve();
|
|
62
62
|
treeviewStore.addItem(item.geode_object_type, item.name, item.id, item.viewer_type);
|
|
63
63
|
const addDataStyleTask = dataStyleStore.addDataStyle(item.id, item.geode_object_type);
|
|
64
|
-
const addViewerTask = addDataTask.then(() => {
|
|
65
|
-
if (item
|
|
64
|
+
const addViewerTask = addDataTask.then(async () => {
|
|
65
|
+
if (!(await dataStore.isItemViewable(item))) {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
return hybridViewerStore.addItem(item.id);
|
|
69
69
|
});
|
|
70
70
|
const applyStyleTask = Promise.all([registerTask, addDataComponentsTask, addDataStyleTask]).then(
|
|
71
|
-
() =>
|
|
71
|
+
async () => {
|
|
72
|
+
if (await dataStore.isItemViewable(item)) {
|
|
73
|
+
return dataStyleStore.applyDefaultStyle(item.id);
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
},
|
|
72
77
|
);
|
|
73
78
|
await Promise.all([
|
|
74
79
|
registerTask,
|
|
@@ -1,65 +1,5 @@
|
|
|
1
|
-
import { BACKGROUND_GREY_VALUE, RGB_MAX } from "./hybrid_viewer_constants";
|
|
2
1
|
import { centerCameraOnPosition } from "./hybrid_viewer_camera";
|
|
3
2
|
|
|
4
|
-
const RGBA_CHANNELS = 4;
|
|
5
|
-
const SAMPLE_SIZE = 10;
|
|
6
|
-
const TOTAL_CHANNELS = 400;
|
|
7
|
-
|
|
8
|
-
function mapRect(rect, latestImage, canvasRect) {
|
|
9
|
-
const scaleX = latestImage.width / canvasRect.width;
|
|
10
|
-
const scaleY = latestImage.height / canvasRect.height;
|
|
11
|
-
return {
|
|
12
|
-
relX: (rect.x - canvasRect.left) * scaleX,
|
|
13
|
-
relY: (rect.y - canvasRect.top) * scaleY,
|
|
14
|
-
relW: rect.width * scaleX,
|
|
15
|
-
relH: rect.height * scaleY,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function computeAverageBrightness(rect, options) {
|
|
20
|
-
const { latestImage, offscreenCtx, offscreenCanvas, genericRenderWindow } = options;
|
|
21
|
-
if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
|
|
22
|
-
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
23
|
-
}
|
|
24
|
-
const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
|
|
25
|
-
if (!canvas) {
|
|
26
|
-
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
27
|
-
}
|
|
28
|
-
if (rect.width <= 0 || rect.height <= 0) {
|
|
29
|
-
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
30
|
-
}
|
|
31
|
-
const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
|
|
32
|
-
if (relW <= 0 || relH <= 0) {
|
|
33
|
-
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
34
|
-
}
|
|
35
|
-
offscreenCanvas.width = SAMPLE_SIZE;
|
|
36
|
-
offscreenCanvas.height = SAMPLE_SIZE;
|
|
37
|
-
try {
|
|
38
|
-
offscreenCtx.drawImage(
|
|
39
|
-
latestImage,
|
|
40
|
-
Math.max(0, relX),
|
|
41
|
-
Math.max(0, relY),
|
|
42
|
-
Math.min(latestImage.width, relW),
|
|
43
|
-
Math.min(latestImage.height, relH),
|
|
44
|
-
0,
|
|
45
|
-
0,
|
|
46
|
-
SAMPLE_SIZE,
|
|
47
|
-
SAMPLE_SIZE,
|
|
48
|
-
);
|
|
49
|
-
const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
|
|
50
|
-
let minBrightness = 1;
|
|
51
|
-
for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
|
|
52
|
-
const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
|
|
53
|
-
if (brightness < minBrightness) {
|
|
54
|
-
minBrightness = brightness;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return minBrightness;
|
|
58
|
-
} catch {
|
|
59
|
-
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
3
|
function performClickPicking(event, options) {
|
|
64
4
|
const { container, viewerStore, viewer_schemas, genericRenderWindow, syncRemoteCamera } = options;
|
|
65
5
|
const rect = container.getBoundingClientRect();
|
|
@@ -148,8 +88,11 @@ async function performAddItem(id, options) {
|
|
|
148
88
|
if (!genericRenderWindow) {
|
|
149
89
|
return;
|
|
150
90
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
91
|
+
const value = await dataStore.item(id);
|
|
92
|
+
if (value && !dataStore.isItemViewable(value)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const reader = vtkXMLPolyDataReader();
|
|
153
96
|
await reader.parseAsArrayBuffer(new TextEncoder().encode(value.binary_light_viewable));
|
|
154
97
|
const actor = vtkActor(),
|
|
155
98
|
mapper = vtkMapper(),
|
|
@@ -158,6 +101,9 @@ async function performAddItem(id, options) {
|
|
|
158
101
|
actor.getProperty().setColor(actorColor);
|
|
159
102
|
actor.setMapper(mapper);
|
|
160
103
|
const renderer = genericRenderWindow.getRenderer();
|
|
104
|
+
if (hybridDb[id] && hybridDb[id].actor) {
|
|
105
|
+
renderer.removeActor(hybridDb[id].actor);
|
|
106
|
+
}
|
|
161
107
|
const isFirst = renderer.getActors().length === 0;
|
|
162
108
|
renderer.addActor(actor);
|
|
163
109
|
if (isFirst) {
|
|
@@ -307,7 +253,6 @@ function performClear(options) {
|
|
|
307
253
|
}
|
|
308
254
|
|
|
309
255
|
export {
|
|
310
|
-
computeAverageBrightness,
|
|
311
256
|
performAddItem,
|
|
312
257
|
performClearHoverHighlight,
|
|
313
258
|
performClickPicking,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { BACKGROUND_GREY_VALUE, RGB_MAX } from "./hybrid_viewer_constants";
|
|
2
|
+
|
|
3
|
+
const RGBA_CHANNELS = 4;
|
|
4
|
+
const SAMPLE_SIZE = 10;
|
|
5
|
+
const TOTAL_CHANNELS = 400;
|
|
6
|
+
|
|
7
|
+
function mapRect(rect, latestImage, canvasRect) {
|
|
8
|
+
const scaleX = latestImage.width / canvasRect.width;
|
|
9
|
+
const scaleY = latestImage.height / canvasRect.height;
|
|
10
|
+
return {
|
|
11
|
+
relX: (rect.x - canvasRect.left) * scaleX,
|
|
12
|
+
relY: (rect.y - canvasRect.top) * scaleY,
|
|
13
|
+
relW: rect.width * scaleX,
|
|
14
|
+
relH: rect.height * scaleY,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function computeAverageBrightness(rect, options) {
|
|
19
|
+
const { latestImage, offscreenCtx, offscreenCanvas, genericRenderWindow } = options;
|
|
20
|
+
if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
|
|
21
|
+
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
22
|
+
}
|
|
23
|
+
const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
|
|
24
|
+
if (!canvas) {
|
|
25
|
+
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
26
|
+
}
|
|
27
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
28
|
+
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
29
|
+
}
|
|
30
|
+
const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
|
|
31
|
+
if (relW <= 0 || relH <= 0) {
|
|
32
|
+
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
33
|
+
}
|
|
34
|
+
offscreenCanvas.width = SAMPLE_SIZE;
|
|
35
|
+
offscreenCanvas.height = SAMPLE_SIZE;
|
|
36
|
+
try {
|
|
37
|
+
offscreenCtx.drawImage(
|
|
38
|
+
latestImage,
|
|
39
|
+
Math.max(0, relX),
|
|
40
|
+
Math.max(0, relY),
|
|
41
|
+
Math.min(latestImage.width, relW),
|
|
42
|
+
Math.min(latestImage.height, relH),
|
|
43
|
+
0,
|
|
44
|
+
0,
|
|
45
|
+
SAMPLE_SIZE,
|
|
46
|
+
SAMPLE_SIZE,
|
|
47
|
+
);
|
|
48
|
+
const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
|
|
49
|
+
let minBrightness = 1;
|
|
50
|
+
for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
|
|
51
|
+
const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
|
|
52
|
+
if (brightness < minBrightness) {
|
|
53
|
+
minBrightness = brightness;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return minBrightness;
|
|
57
|
+
} catch {
|
|
58
|
+
return BACKGROUND_GREY_VALUE / RGB_MAX;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { computeAverageBrightness };
|
package/package.json
CHANGED
|
@@ -105,6 +105,7 @@ const dataStoreMock = {
|
|
|
105
105
|
registerObject: vi.fn().mockResolvedValue(),
|
|
106
106
|
addItem: vi.fn().mockResolvedValue(),
|
|
107
107
|
importStores: vi.fn().mockResolvedValue(),
|
|
108
|
+
isItemViewable: vi.fn().mockReturnValue(true),
|
|
108
109
|
};
|
|
109
110
|
const dataStyleStoreMock = {
|
|
110
111
|
importStores: vi.fn().mockResolvedValue(),
|
|
@@ -124,4 +124,39 @@ describe("useDataStore - collections", () => {
|
|
|
124
124
|
},
|
|
125
125
|
]);
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
describe("isItemViewable", () => {
|
|
129
|
+
test("correctly identifies viewable and HorizonStack3D items", () => {
|
|
130
|
+
const dataStore = useDataStore();
|
|
131
|
+
|
|
132
|
+
// Standard viewable item (BRep)
|
|
133
|
+
expect(
|
|
134
|
+
dataStore.isItemViewable({ id: "1", title: "BRep 1", geode_object_type: "BRep" }),
|
|
135
|
+
).toBe(true);
|
|
136
|
+
|
|
137
|
+
// Group node for viewable type
|
|
138
|
+
expect(dataStore.isItemViewable({ id: "BRep", title: "BRep" })).toBe(true);
|
|
139
|
+
|
|
140
|
+
// HorizonStack3D leaf item
|
|
141
|
+
expect(
|
|
142
|
+
dataStore.isItemViewable({
|
|
143
|
+
id: "2",
|
|
144
|
+
title: "HS 1",
|
|
145
|
+
geode_object_type: "HorizonStack3D",
|
|
146
|
+
}),
|
|
147
|
+
).toBe(false);
|
|
148
|
+
|
|
149
|
+
// HorizonStack3D group node
|
|
150
|
+
expect(dataStore.isItemViewable({ id: "HorizonStack3D", title: "HorizonStack3D" })).toBe(
|
|
151
|
+
false,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("respects explicit is_viewable overrides", () => {
|
|
156
|
+
const dataStore = useDataStore();
|
|
157
|
+
|
|
158
|
+
expect(dataStore.isItemViewable({ is_viewable: false })).toBe(false);
|
|
159
|
+
expect(dataStore.isItemViewable({ is_viewable: true })).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
127
162
|
});
|