+
diff --git a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue
index 5786a163..3ec04bdd 100644
--- a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue
+++ b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue
@@ -179,46 +179,48 @@ function expandAll() {
-
-
-
+
+
+
+
+
diff --git a/app/composables/hover_highlight.js b/app/composables/hover_highlight.js
index 9dfe5c8d..2ab11f68 100644
--- a/app/composables/hover_highlight.js
+++ b/app/composables/hover_highlight.js
@@ -1,3 +1,4 @@
+import { useDataStore } from "@ogw_front/stores/data";
import { useViewerStore } from "@ogw_front/stores/viewer";
import vtk_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
@@ -5,6 +6,7 @@ const HOVER_DELAY = 200;
export function useHoverhighlight() {
const viewerStore = useViewerStore();
+ const dataStore = useDataStore();
let timer = undefined;
let currentId = undefined;
let currentType = undefined;
@@ -20,6 +22,11 @@ export function useHoverhighlight() {
currentId = id;
currentType = type;
+ const value = await dataStore.item(id);
+ if (value && !dataStore.isItemViewable(value)) {
+ return;
+ }
+
let block_ids = [];
if (typeof block_ids_provider === "function") {
block_ids = await block_ids_provider();
diff --git a/app/composables/use_overlapping_picker.js b/app/composables/use_overlapping_picker.js
index cf72075f..d92ec6fb 100644
--- a/app/composables/use_overlapping_picker.js
+++ b/app/composables/use_overlapping_picker.js
@@ -64,8 +64,22 @@ export function useOverlappingPicker() {
}
async function get_viewer_id({ x, y, containerWidth, containerHeight, containerRect }) {
- const activeIds = new Set(dataItems.value.map((item) => item.id));
- const ids = Object.keys(dataStyleStore.styles).filter((styleId) => activeIds.has(styleId));
+ const activeIds = new Set(dataItems.value.map((i) => i.id));
+ const visibleStyleIds = Object.keys(dataStyleStore.styles).filter(
+ (styleId) => activeIds.has(styleId) && dataStyleStore.objectVisibility(styleId),
+ );
+
+ const viewableChecks = await Promise.all(
+ visibleStyleIds.map(async (styleId) => {
+ try {
+ const dataItem = await dataStore.item(styleId);
+ return dataStore.isItemViewable(dataItem) ? styleId : undefined;
+ } catch {
+ return undefined;
+ }
+ }),
+ );
+ const ids = viewableChecks.filter(Boolean);
const result = { id: undefined, viewer_id: undefined };
let pickedResponse = undefined;
diff --git a/app/stores/app.js b/app/stores/app.js
index 4051efe6..f6e6c528 100644
--- a/app/stores/app.js
+++ b/app/stores/app.js
@@ -8,6 +8,23 @@ import { useInfraStore } from "@ogw_front/stores/infra";
// oxlint-disable-next-line max-lines-per-function, max-statements
export const useAppStore = defineStore("app", () => {
const stores = [];
+ const globalComponents = ref(new Map());
+
+ function registerGlobalComponent(extensionId, componentId, component) {
+ if (!globalComponents.value.has(extensionId)) {
+ globalComponents.value.set(extensionId, new Map());
+ }
+ globalComponents.value.get(extensionId).set(componentId, component);
+ console.log(
+ `[AppStore] Registered global component ${componentId} for extension ${extensionId}`,
+ );
+ }
+
+ function unregisterGlobalComponent(extensionId, componentId) {
+ if (globalComponents.value.has(extensionId)) {
+ globalComponents.value.get(extensionId).delete(componentId);
+ }
+ }
function registerStore(store) {
const isAlreadyRegistered = stores.some((registeredStore) => registeredStore.$id === store.$id);
@@ -285,5 +302,8 @@ export const useAppStore = defineStore("app", () => {
createProjectFolder,
start_request,
stop_request,
+ globalComponents,
+ registerGlobalComponent,
+ unregisterGlobalComponent,
};
});
diff --git a/app/stores/data.js b/app/stores/data.js
index c62e3897..fbcdf122 100644
--- a/app/stores/data.js
+++ b/app/stores/data.js
@@ -11,6 +11,19 @@ import { useViewerStore } from "@ogw_front/stores/viewer";
const viewer_generic_schemas = viewer_schemas.opengeodeweb_viewer.generic;
+function isItemViewable(item) {
+ if (!item || typeof item !== "object") {
+ return false;
+ }
+ if (item.is_viewable !== undefined) {
+ return Boolean(item.is_viewable);
+ }
+ if (item.binary_light_viewable !== undefined) {
+ return item.binary_light_viewable !== "not_viewable";
+ }
+ return true;
+}
+
// oxlint-disable-next-line max-lines-per-function, max-statements
export const useDataStore = defineStore("data", () => {
const viewerStore = useViewerStore();
@@ -93,8 +106,11 @@ export const useDataStore = defineStore("data", () => {
geode_object_type: new_item.geode_object_type,
visible: true,
created_at: new Date().toISOString(),
- binary_light_viewable: new_item.binary_light_viewable,
+ is_viewable: new_item.is_viewable,
};
+ if (new_item.binary_light_viewable !== undefined && new_item.binary_light_viewable !== null) {
+ itemData.binary_light_viewable = new_item.binary_light_viewable;
+ }
return data_db.put(itemData);
}
@@ -213,6 +229,7 @@ export const useDataStore = defineStore("data", () => {
item,
allItems,
refItem,
+ isItemViewable,
meshComponentType,
registerObject,
deregisterObject,
diff --git a/app/stores/data_style.js b/app/stores/data_style.js
index 5008f565..6f9d02e2 100644
--- a/app/stores/data_style.js
+++ b/app/stores/data_style.js
@@ -21,6 +21,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
async function setVisibility(id, visibility) {
const item = await dataStore.item(id);
+ if (!dataStore.isItemViewable(item)) {
+ return dataStyleState.mutateStyle(id, { visibility });
+ }
+
const { viewer_type } = item;
if (viewer_type === "mesh") {
@@ -34,6 +38,10 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
async function applyDefaultStyle(id) {
const item = await dataStore.item(id);
+ if (!dataStore.isItemViewable(item)) {
+ throw new Error(`applyDefaultStyle called for non-viewable item: ${id}`);
+ }
+
const { viewer_type } = item;
if (viewer_type === "mesh") {
@@ -81,6 +89,9 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
const ids = Object.keys(dataStyleState.styles.value);
const promises = ids.map(async (id) => {
const meta = await dataStore.item(id);
+ if (!dataStore.isItemViewable(meta)) {
+ return;
+ }
const viewerType = meta.viewer_type;
if (viewerType === "mesh") {
return meshStyleStore.applyMeshStyle(id);
diff --git a/app/stores/hybrid_viewer.js b/app/stores/hybrid_viewer.js
index eee1fa49..068045d7 100644
--- a/app/stores/hybrid_viewer.js
+++ b/app/stores/hybrid_viewer.js
@@ -12,7 +12,10 @@ import {
performSyncRemoteCamera,
} from "@ogw_internal/stores/hybrid_viewer_camera";
import {
- computeAverageBrightness,
+ createClearHoverData,
+ createHoverHighlight,
+} from "@ogw_internal/stores/hybrid_viewer_highlight";
+import {
performAddItem,
performClear,
performClearHoverHighlight,
@@ -22,10 +25,7 @@ import {
performSetVisibility,
performSetZScaling,
} from "@ogw_internal/stores/hybrid_viewer";
-import {
- createClearHoverData,
- createHoverHighlight,
-} from "@ogw_internal/stores/hybrid_viewer_highlight";
+import { computeAverageBrightness } from "@ogw_internal/stores/hybrid_viewer_brightness";
import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
import { newInstance as vtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
import { newInstance as vtkMapper } from "@kitware/vtk.js/Rendering/Core/Mapper";
diff --git a/app/utils/import_workflow.js b/app/utils/import_workflow.js
index 8631091b..9c5dc4e1 100644
--- a/app/utils/import_workflow.js
+++ b/app/utils/import_workflow.js
@@ -62,13 +62,18 @@ async function importItem(item) {
treeviewStore.addItem(item.geode_object_type, item.name, item.id, item.viewer_type);
const addDataStyleTask = dataStyleStore.addDataStyle(item.id, item.geode_object_type);
const addViewerTask = addDataTask.then(() => {
- if (item.nb_vertices === 0) {
+ if (!dataStore.isItemViewable(item)) {
return;
}
return hybridViewerStore.addItem(item.id);
});
const applyStyleTask = Promise.all([registerTask, addDataComponentsTask, addDataStyleTask]).then(
- () => dataStyleStore.applyDefaultStyle(item.id),
+ () => {
+ if (dataStore.isItemViewable(item)) {
+ return dataStyleStore.applyDefaultStyle(item.id);
+ }
+ return undefined;
+ },
);
await Promise.all([
registerTask,
diff --git a/internal/stores/hybrid_viewer.js b/internal/stores/hybrid_viewer.js
index 80b5a85e..d50ccfc7 100644
--- a/internal/stores/hybrid_viewer.js
+++ b/internal/stores/hybrid_viewer.js
@@ -1,65 +1,5 @@
-import { BACKGROUND_GREY_VALUE, RGB_MAX } from "./hybrid_viewer_constants";
import { centerCameraOnPosition } from "./hybrid_viewer_camera";
-const RGBA_CHANNELS = 4;
-const SAMPLE_SIZE = 10;
-const TOTAL_CHANNELS = 400;
-
-function mapRect(rect, latestImage, canvasRect) {
- const scaleX = latestImage.width / canvasRect.width;
- const scaleY = latestImage.height / canvasRect.height;
- return {
- relX: (rect.x - canvasRect.left) * scaleX,
- relY: (rect.y - canvasRect.top) * scaleY,
- relW: rect.width * scaleX,
- relH: rect.height * scaleY,
- };
-}
-
-function computeAverageBrightness(rect, options) {
- const { latestImage, offscreenCtx, offscreenCanvas, genericRenderWindow } = options;
- if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
- return BACKGROUND_GREY_VALUE / RGB_MAX;
- }
- const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
- if (!canvas) {
- return BACKGROUND_GREY_VALUE / RGB_MAX;
- }
- if (rect.width <= 0 || rect.height <= 0) {
- return BACKGROUND_GREY_VALUE / RGB_MAX;
- }
- const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
- if (relW <= 0 || relH <= 0) {
- return BACKGROUND_GREY_VALUE / RGB_MAX;
- }
- offscreenCanvas.width = SAMPLE_SIZE;
- offscreenCanvas.height = SAMPLE_SIZE;
- try {
- offscreenCtx.drawImage(
- latestImage,
- Math.max(0, relX),
- Math.max(0, relY),
- Math.min(latestImage.width, relW),
- Math.min(latestImage.height, relH),
- 0,
- 0,
- SAMPLE_SIZE,
- SAMPLE_SIZE,
- );
- const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
- let minBrightness = 1;
- for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
- const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
- if (brightness < minBrightness) {
- minBrightness = brightness;
- }
- }
- return minBrightness;
- } catch {
- return BACKGROUND_GREY_VALUE / RGB_MAX;
- }
-}
-
function performClickPicking(event, options) {
const { container, viewerStore, viewer_schemas, genericRenderWindow, syncRemoteCamera } = options;
const rect = container.getBoundingClientRect();
@@ -148,8 +88,11 @@ async function performAddItem(id, options) {
if (!genericRenderWindow) {
return;
}
- const reader = vtkXMLPolyDataReader(),
- value = await dataStore.item(id);
+ const value = await dataStore.item(id);
+ if (value && !dataStore.isItemViewable(value)) {
+ return;
+ }
+ const reader = vtkXMLPolyDataReader();
await reader.parseAsArrayBuffer(new TextEncoder().encode(value.binary_light_viewable));
const actor = vtkActor(),
mapper = vtkMapper(),
@@ -158,6 +101,9 @@ async function performAddItem(id, options) {
actor.getProperty().setColor(actorColor);
actor.setMapper(mapper);
const renderer = genericRenderWindow.getRenderer();
+ if (hybridDb[id] && hybridDb[id].actor) {
+ renderer.removeActor(hybridDb[id].actor);
+ }
const isFirst = renderer.getActors().length === 0;
renderer.addActor(actor);
if (isFirst) {
@@ -307,7 +253,6 @@ function performClear(options) {
}
export {
- computeAverageBrightness,
performAddItem,
performClearHoverHighlight,
performClickPicking,
diff --git a/internal/stores/hybrid_viewer_brightness.js b/internal/stores/hybrid_viewer_brightness.js
new file mode 100644
index 00000000..b4da1328
--- /dev/null
+++ b/internal/stores/hybrid_viewer_brightness.js
@@ -0,0 +1,62 @@
+import { BACKGROUND_GREY_VALUE, RGB_MAX } from "./hybrid_viewer_constants";
+
+const RGBA_CHANNELS = 4;
+const SAMPLE_SIZE = 10;
+const TOTAL_CHANNELS = 400;
+
+function mapRect(rect, latestImage, canvasRect) {
+ const scaleX = latestImage.width / canvasRect.width;
+ const scaleY = latestImage.height / canvasRect.height;
+ return {
+ relX: (rect.x - canvasRect.left) * scaleX,
+ relY: (rect.y - canvasRect.top) * scaleY,
+ relW: rect.width * scaleX,
+ relH: rect.height * scaleY,
+ };
+}
+
+function computeAverageBrightness(rect, options) {
+ const { latestImage, offscreenCtx, offscreenCanvas, genericRenderWindow } = options;
+ if (!latestImage || !offscreenCtx || !offscreenCanvas || !genericRenderWindow) {
+ return BACKGROUND_GREY_VALUE / RGB_MAX;
+ }
+ const canvas = genericRenderWindow.getApiSpecificRenderWindow().getCanvas();
+ if (!canvas) {
+ return BACKGROUND_GREY_VALUE / RGB_MAX;
+ }
+ if (rect.width <= 0 || rect.height <= 0) {
+ return BACKGROUND_GREY_VALUE / RGB_MAX;
+ }
+ const { relX, relY, relW, relH } = mapRect(rect, latestImage, canvas.getBoundingClientRect());
+ if (relW <= 0 || relH <= 0) {
+ return BACKGROUND_GREY_VALUE / RGB_MAX;
+ }
+ offscreenCanvas.width = SAMPLE_SIZE;
+ offscreenCanvas.height = SAMPLE_SIZE;
+ try {
+ offscreenCtx.drawImage(
+ latestImage,
+ Math.max(0, relX),
+ Math.max(0, relY),
+ Math.min(latestImage.width, relW),
+ Math.min(latestImage.height, relH),
+ 0,
+ 0,
+ SAMPLE_SIZE,
+ SAMPLE_SIZE,
+ );
+ const { data } = offscreenCtx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE);
+ let minBrightness = 1;
+ for (let i = 0; i < TOTAL_CHANNELS; i += RGBA_CHANNELS) {
+ const brightness = (data[i] + data[i + 1] + data[i + 2]) / (3 * RGB_MAX);
+ if (brightness < minBrightness) {
+ minBrightness = brightness;
+ }
+ }
+ return minBrightness;
+ } catch {
+ return BACKGROUND_GREY_VALUE / RGB_MAX;
+ }
+}
+
+export { computeAverageBrightness };
diff --git a/tests/unit/composables/project_manager.nuxt.test.js b/tests/unit/composables/project_manager.nuxt.test.js
index c35b5a7c..57f1736f 100644
--- a/tests/unit/composables/project_manager.nuxt.test.js
+++ b/tests/unit/composables/project_manager.nuxt.test.js
@@ -105,6 +105,7 @@ const dataStoreMock = {
registerObject: vi.fn().mockResolvedValue(),
addItem: vi.fn().mockResolvedValue(),
importStores: vi.fn().mockResolvedValue(),
+ isItemViewable: vi.fn().mockReturnValue(true),
};
const dataStyleStoreMock = {
importStores: vi.fn().mockResolvedValue(),