Skip to content

Commit 5109fcc

Browse files
committed
fix(RenderingModule): point 3D rendering controls to active image 3D view
1 parent f362fe7 commit 5109fcc

7 files changed

Lines changed: 115 additions & 59 deletions

File tree

‎src/components/RenderingModule.vue‎

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,85 @@ import LayerList from './LayerList.vue';
1010
export default defineComponent({
1111
components: { VolumeRendering, VolumePresets, VolumeProperties, LayerList },
1212
setup() {
13-
const { currentImageData, currentImage } = useCurrentImage();
13+
// Use global current image - this gets the first loaded image when no view is selected
14+
const { currentImageData, currentImage, currentLayers, currentImageID } =
15+
useCurrentImage('global');
16+
1417
const viewStore = useViewStore();
15-
const isActiveView3D = computed(() => {
16-
const view = viewStore.getView(viewStore.activeView);
17-
return view?.type === '3D';
18+
19+
// Get 3D view ID - prefer active view if it's 3D and shows current image,
20+
// otherwise find any 3D view showing current image
21+
const view3DId = computed(() => {
22+
const activeView = viewStore.getView(viewStore.activeView);
23+
if (
24+
activeView?.type === '3D' &&
25+
activeView.dataID === currentImageID.value
26+
) {
27+
return activeView.id;
28+
}
29+
30+
const any3DView = Object.values(viewStore.viewByID).find(
31+
(v) => v.type === '3D' && v.dataID === currentImageID.value
32+
);
33+
return any3DView?.id;
1834
});
35+
1936
const hasCurrentImage = computed(() => !!currentImageData.value);
2037
const isImageLoading = computed(() => !!unref(currentImage.value?.loading));
21-
const canShowPanel = computed(() => {
22-
return isActiveView3D.value && hasCurrentImage.value;
23-
});
2438
25-
const { currentLayers } = useCurrentImage();
39+
// Show 3D controls only if we have both an image AND a 3D view somewhere
40+
const canShow3DControls = computed(
41+
() => hasCurrentImage.value && !!view3DId.value
42+
);
43+
2644
const hasLayers = computed(() => !!currentLayers.value.length);
2745
2846
const panels = ref<string[]>(['properties', 'layers']);
2947
3048
return {
3149
panels,
32-
canShowPanel,
50+
hasCurrentImage,
3351
hasLayers,
3452
isImageLoading,
53+
canShow3DControls,
54+
view3DId,
3555
};
3656
},
3757
});
3858
</script>
3959

4060
<template>
4161
<div class="overflow-y-auto mx-2 mt-1 fill-height">
42-
<template v-if="canShowPanel">
43-
<v-skeleton-loader v-if="isImageLoading" type="image">
44-
</v-skeleton-loader>
45-
<volume-rendering v-else />
62+
<template v-if="hasCurrentImage">
63+
<template v-if="canShow3DControls">
64+
<v-skeleton-loader v-if="isImageLoading" type="image">
65+
</v-skeleton-loader>
66+
<volume-rendering v-else :view-id="view3DId" />
67+
</template>
68+
<template v-else>
69+
<div class="pt-4 text-body-2 text-center text-medium-emphasis">
70+
Create a 3D view to access volume rendering controls
71+
</div>
72+
</template>
73+
4674
<v-expansion-panels v-model="panels" multiple variant="accordion">
47-
<v-expansion-panel value="preset">
75+
<v-expansion-panel v-if="canShow3DControls" value="preset">
4876
<v-expansion-panel-title>
4977
<v-icon class="flex-grow-0 mr-4">mdi-palette</v-icon>
5078
Color Presets
5179
</v-expansion-panel-title>
5280
<v-expansion-panel-text>
53-
<volume-presets />
81+
<volume-presets :view-id="view3DId" />
5482
</v-expansion-panel-text>
5583
</v-expansion-panel>
5684

57-
<v-expansion-panel value="properties">
85+
<v-expansion-panel v-if="canShow3DControls" value="properties">
5886
<v-expansion-panel-title>
5987
<v-icon class="flex-grow-0 mr-4">mdi-cube-scan</v-icon>
6088
Cinematic Rendering
6189
</v-expansion-panel-title>
6290
<v-expansion-panel-text>
63-
<volume-properties />
91+
<volume-properties :view-id="view3DId" />
6492
</v-expansion-panel-text>
6593
</v-expansion-panel>
6694

@@ -78,7 +106,7 @@ export default defineComponent({
78106
<template v-else>
79107
<div class="pt-12 text-subtitle-1 d-flex flex-row justify-center">
80108
<div class="text-center" style="max-width: 75%">
81-
Select a 3D view containing an image to view 3D controls
109+
Load an image and add a 3D view to see rendering controls
82110
</div>
83111
</div>
84112
</template>

‎src/components/VolumePresets.vue‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { useCurrentImage } from '@/src/composables/useCurrentImage';
1010
import useVolumeColoringStore from '@/src/store/view-configs/volume-coloring';
1111
import { getColorFunctionRangeFromPreset } from '@/src/utils/vtk-helpers';
1212
import { useVolumeThumbnailing } from '@/src/composables/useVolumeThumbnailing';
13-
import { useViewStore } from '@/src/store/views';
1413
1514
const THUMBNAIL_SIZE = 80;
1615
@@ -21,14 +20,15 @@ export default defineComponent({
2120
GroupableItem,
2221
PersistentOverlay,
2322
},
24-
setup() {
23+
props: {
24+
viewId: {
25+
type: String,
26+
default: null,
27+
},
28+
},
29+
setup(props) {
2530
const volumeColoringStore = useVolumeColoringStore();
26-
const viewStore = useViewStore();
27-
const viewId = computed(() => {
28-
const view = viewStore.getView(viewStore.activeView);
29-
if (view?.type === '3D') return view.id;
30-
return null;
31-
});
31+
const viewId = computed(() => props.viewId);
3232
3333
const { currentImageID, currentImageData } = useCurrentImage();
3434

‎src/components/VolumeProperties.vue‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ import { useVolumeColoringInitializer } from '@/src/composables/useVolumeColorin
44
import { useCurrentImage } from '@/src/composables/useCurrentImage';
55
import { CVRConfig } from '@/src/types/views';
66
import useVolumeColoringStore from '@/src/store/view-configs/volume-coloring';
7-
import { useViewStore } from '@/src/store/views';
87
98
const LIGHTING_MODELS = {
109
standard: 'Standard',
1110
hybrid: 'Hybrid',
1211
};
1312
1413
export default defineComponent({
15-
name: 'VolumeRendering',
16-
setup() {
14+
name: 'VolumeProperties',
15+
props: {
16+
viewId: {
17+
type: String,
18+
default: null,
19+
},
20+
},
21+
setup(props) {
1722
const volumeColoringStore = useVolumeColoringStore();
18-
const viewStore = useViewStore();
19-
const viewId = computed(() => {
20-
const view = viewStore.getView(viewStore.activeView);
21-
if (view?.type === '3D') return view.id;
22-
return null;
23-
});
23+
const viewId = computed(() => props.viewId);
2424
2525
const { currentImageID } = useCurrentImage();
2626

‎src/components/VolumeRendering.vue‎

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,24 @@ import {
2626
getShiftedOpacityFromPreset,
2727
} from '@/src/utils/vtk-helpers';
2828
import { useVolumeThumbnailing } from '@/src/composables/useVolumeThumbnailing';
29-
import { useViewStore } from '@/src/store/views';
3029
3130
const WIDGET_WIDTH = 250;
3231
const WIDGET_HEIGHT = 150;
3332
const THUMBNAIL_SIZE = 80;
3433
3534
export default defineComponent({
3635
name: 'VolumeRendering',
37-
setup() {
36+
props: {
37+
viewId: {
38+
type: String,
39+
default: null,
40+
},
41+
},
42+
setup(props) {
3843
const volumeColoringStore = useVolumeColoringStore();
3944
const editorContainerRef = ref<HTMLElement | null>(null);
4045
const pwfEditorRef = ref<HTMLElement | null>(null);
41-
const viewStore = useViewStore();
42-
const viewId = computed(() => {
43-
const view = viewStore.getView(viewStore.activeView);
44-
if (view?.type === '3D') return view.id;
45-
return null;
46-
});
46+
const viewId = computed(() => props.viewId);
4747
4848
let recurseGuard = false;
4949
@@ -280,36 +280,56 @@ export default defineComponent({
280280
281281
// --- mapping range editing --- //
282282
283-
const rangeShift = ref(0);
284-
const rangeWidth = ref(0);
285-
286283
onKeyDown('Control', () => pwfWidget.setShiftOpacityValues(true));
287284
onKeyUp('Control', () => pwfWidget.setShiftOpacityValues(false));
288285
286+
const rangeShift = ref(0);
287+
const rangeWidth = ref(0);
288+
289289
const reset = () => {
290290
rangeShift.value = 0;
291291
rangeWidth.value = fullMappingRangeWidth.value;
292292
};
293-
// reset case
293+
294+
// Reset when preset or image changes
294295
watch([selectedPreset, currentImageID], reset, { immediate: true });
295296
296297
useResetViewsEvents().onClick(reset);
297298
299+
// Sync slider values from mappingRange when it changes (e.g., when switching views)
300+
watch(
301+
() => colorTransferFunctionRef.value?.mappingRange,
302+
(mappingRange) => {
303+
if (!mappingRange) return;
304+
305+
const fullRange = fullMappingRange.value;
306+
const fullCenter = (fullRange[0] + fullRange[1]) / 2;
307+
const currentCenter = (mappingRange[0] + mappingRange[1]) / 2;
308+
309+
rangeWidth.value = mappingRange[1] - mappingRange[0];
310+
rangeShift.value = currentCenter - fullCenter;
311+
}
312+
);
313+
314+
// Update store when sliders change (using center-based math to avoid drift)
298315
watch([rangeShift, rangeWidth], ([shift, width]) => {
299-
if (!viewId.value) return;
300-
const imageID = currentImageID.value;
301-
const config = volumeColoringStore.getConfig(viewId.value, imageID);
302-
// wait for config to be initialized with preset "all view default" for particular image
303-
if (!imageID || !config) return;
316+
if (!viewId.value || !currentImageID.value) return;
304317
305318
const fullRange = fullMappingRange.value;
306-
const fullWidth = fullMappingRangeWidth.value;
307-
const min = fullRange[0] + Math.floor((fullWidth - width) / 2) + shift;
308-
const max = fullRange[1] - Math.ceil((fullWidth - width) / 2) + shift;
319+
const fullCenter = (fullRange[0] + fullRange[1]) / 2;
320+
const newCenter = fullCenter + shift;
321+
const halfWidth = width / 2;
309322
310-
volumeColoringStore.updateColorTransferFunction(viewId.value, imageID, {
311-
mappingRange: [min, max],
312-
});
323+
const min = newCenter - halfWidth;
324+
const max = newCenter + halfWidth;
325+
326+
volumeColoringStore.updateColorTransferFunction(
327+
viewId.value,
328+
currentImageID.value,
329+
{
330+
mappingRange: [min, max],
331+
}
332+
);
313333
});
314334
315335
return {

‎src/store/view-configs.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useWindowingStore } from './view-configs/windowing';
55
import useLayerColoringStore from './view-configs/layers';
66
import useViewCameraStore from './view-configs/camera';
77
import useVolumeColoringStore from './view-configs/volume-coloring';
8+
import { useViewStore } from './views';
89
import { StateFile, ViewConfig } from '../io/state-file/schema';
910

1011
/**
@@ -17,6 +18,7 @@ export const useViewConfigStore = defineStore('viewConfig', () => {
1718
const layerColoringStore = useLayerColoringStore();
1819
const viewCameraStore = useViewCameraStore();
1920
const volumeColoringStore = useVolumeColoringStore();
21+
const viewStore = useViewStore();
2022

2123
const removeView = (viewID: string) => {
2224
viewSliceStore.removeView(viewID);
@@ -72,6 +74,10 @@ export const useViewConfigStore = defineStore('viewConfig', () => {
7274
});
7375
};
7476

77+
viewStore.LayoutViewReplacedEvent.on((oldViewID) => {
78+
removeView(oldViewID);
79+
});
80+
7581
return {
7682
removeView,
7783
removeData,

‎src/store/view-configs/windowing.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export const useWindowingStore = defineStore('windowing', () => {
133133

134134
// Ensure we always have required fields from defaults
135135
const baseConfig = currentInternalConfig || defaults;
136-
136+
137137
const newInternalConfig = {
138138
...baseConfig,
139139
...widthLevelPatchOnSwitchingFromAuto,

‎src/store/views.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ export const useViewStore = defineStore('view', () => {
141141
if (activeView.value === id) {
142142
setActiveView(newViewId);
143143
}
144+
145+
delete viewByID[id];
144146
}
145147

146148
function setLayout(newLayout: Layout) {

0 commit comments

Comments
 (0)