@cornerstonejs/core
Version:
Cornerstone3D Core
394 lines (393 loc) • 13.1 kB
JavaScript
import Events from '../../enums/Events.js';
import ViewportStatus from '../../enums/ViewportStatus.js';
import triggerEvent from '../../utilities/triggerEvent.js';
import renderingEngineCache from '../renderingEngineCache.js';
import { isGenericViewportReferenceViewable, } from './genericViewportReferenceCompatibility.js';
class GenericViewport {
constructor(args) {
this.viewportStatus = ViewportStatus.NO_DATA;
this.bindings = new Map();
this.dataPresentation = new Map();
this.isDestroyed = false;
this._debug = {
renderModes: {},
};
this.id = args.id;
this.element = args.element;
}
async setDisplaySets(...entries) {
this.removeAllData();
for (const [index, { displaySetId, options }] of entries.entries()) {
if (!options) {
throw new Error(`[${this.type}] setDisplaySets requires per-entry options when the viewport family does not override it.`);
}
const dataOptions = options;
await this.addDisplaySet(displaySetId, {
...dataOptions,
role: dataOptions.role ?? (index === 0 ? 'source' : 'overlay'),
});
}
}
async addDisplaySet(displaySetId, options) {
if (this.isDestroyed) {
throw new Error('Viewport has been destroyed');
}
const data = await this.dataProvider.load(displaySetId, options);
await this.addLoadedData(displaySetId, data, options);
}
getDisplaySets() {
return Array.from(this.bindings.entries()).map(([displaySetId, binding]) => ({
displaySetId,
options: { role: binding.role },
}));
}
removeData(displaySetId) {
const binding = this.bindings.get(displaySetId);
if (!binding) {
return;
}
binding.removeData();
this.bindings.delete(displaySetId);
this.dataPresentation.delete(displaySetId);
delete this._debug.renderModes[displaySetId];
if (!this.isDestroyed) {
this.render();
}
}
setDisplaySetPresentation(displaySetIdOrProps, maybeProps) {
if (typeof displaySetIdOrProps === 'string') {
this.mergeDataPresentation(displaySetIdOrProps, maybeProps ?? {});
return;
}
const defaultId = this.getCurrentBinding()?.data.id;
if (!defaultId) {
return;
}
this.mergeDataPresentation(defaultId, displaySetIdOrProps);
}
getDisplaySetPresentation(displaySetId) {
return this.getDataPresentationState(displaySetId);
}
getCurrentMode() {
return this.getSourceBinding() ? 'unknown' : 'empty';
}
getViewReference(_specifier = {}) {
return {
FrameOfReferenceUID: this.getFrameOfReferenceUID(),
dataId: this.getCurrentBinding()?.data.id,
};
}
getViewReferenceId(_specifier = {}) {
return `frameOfReference:${this.getFrameOfReferenceUID()}`;
}
setViewReference(_viewReference) {
}
isReferenceViewable(viewReference, options = {}) {
return isGenericViewportReferenceViewable(viewReference, this.getReferenceViewContexts(viewReference), options);
}
getFrameOfReferenceUID() {
return (this.getResolvedView()?.getFrameOfReferenceUID() ??
`${this.type}-viewport-${this.id}`);
}
getRenderingEngine() {
return renderingEngineCache.get(this.renderingEngineId);
}
canvasToWorld(canvasPos) {
const cc = this.getResolvedView();
if (!cc) {
throw new Error(`[${this.type}] Cannot convert canvas to world for viewport ${this.id} because no data is mounted.`);
}
return cc.canvasToWorld(canvasPos);
}
worldToCanvas(worldPos) {
const cc = this.getResolvedView();
if (!cc) {
throw new Error(`[${this.type}] Cannot convert world to canvas for viewport ${this.id} because no data is mounted.`);
}
return cc.worldToCanvas(worldPos);
}
getAspectRatio() {
return [1, 1];
}
setViewState(viewStatePatch) {
if (this.isDestroyed) {
return;
}
const previousCamera = this.getCameraForEvent();
const next = {
...this.viewState,
...viewStatePatch,
};
this.viewState = this.normalizeViewState(next);
this.modified(previousCamera);
}
getViewState() {
return this.viewState;
}
updateViewState(updater) {
const patch = typeof updater === 'function' ? updater(this.getViewState()) : updater;
if (!patch) {
return;
}
this.setViewState(patch);
}
removeWidgets() {
}
destroy() {
if (this.isDestroyed) {
return;
}
this.isDestroyed = true;
this.destroyBindings();
this.onDestroy();
this.bindings.clear();
this.dataPresentation.clear();
for (const key of Object.keys(this._debug.renderModes)) {
delete this._debug.renderModes[key];
}
this.element.removeAttribute('data-viewport-uid');
this.element.removeAttribute('data-rendering-engine-uid');
}
dispose() {
this.destroy();
}
setRendered() {
if (this.viewportStatus === ViewportStatus.NO_DATA ||
this.viewportStatus === ViewportStatus.LOADING) {
return;
}
this.viewportStatus = ViewportStatus.RENDERED;
}
setNeedsRender() {
this.viewportStatus = ViewportStatus.NEEDS_RENDER;
}
updateRenderingPipeline() {
}
resize() {
if (this.isDestroyed) {
return;
}
this.resizeBindings();
this.modified();
}
resetViewState(_options) {
return false;
}
resizeForRenderingEngine({ keepCamera = true, } = {}) {
if (this.isDestroyed) {
return;
}
this.resize();
if (!keepCamera) {
this.resetViewState();
}
}
async addLoadedData(displaySetId, data, options, shouldIgnore) {
if (this.isDestroyed) {
throw new Error('Viewport has been destroyed');
}
if (shouldIgnore?.()) {
return false;
}
const path = this.renderPathResolver.resolve(this.type, data, options);
const renderPath = path.createRenderPath();
const ctx = path.selectContext?.(this.renderContext) ?? this.renderContext;
const attachment = await renderPath.addData(ctx, data, options);
if (shouldIgnore?.()) {
attachment.removeData();
return false;
}
if (this.isDestroyed) {
attachment.removeData();
throw new Error('Viewport has been destroyed');
}
const current = this.bindings.get(displaySetId);
if (current) {
current.removeData();
}
const role = options.role ?? 'overlay';
if (role === 'source') {
for (const [bindingDisplaySetId, binding] of this.bindings.entries()) {
if (bindingDisplaySetId !== displaySetId) {
binding.role = 'overlay';
}
}
}
this.bindings.set(displaySetId, {
data,
role,
...attachment,
});
const binding = this.bindings.get(displaySetId);
if (!binding) {
throw new Error(`Failed to bind rendering for ${displaySetId}`);
}
const props = this.dataPresentation.get(displaySetId);
if (props !== undefined) {
binding.updateDataPresentation(props);
}
binding.applyViewState(this.viewState);
this._debug.renderModes[displaySetId] = attachment.rendering.renderMode;
this.viewportStatus = ViewportStatus.PRE_RENDER;
this.render();
return true;
}
removeAllData() {
for (const displaySetId of Array.from(this.bindings.keys())) {
this.removeData(displaySetId);
}
}
setDataPresentationState(displaySetId, props) {
if (this.isDestroyed) {
return;
}
this.dataPresentation.set(displaySetId, props);
const binding = this.bindings.get(displaySetId);
if (!binding) {
return;
}
binding.updateDataPresentation(props);
this.render();
}
getDataPresentationState(displaySetId) {
return this.dataPresentation.get(displaySetId);
}
setDefaultDataPresentation(displaySetId, defaults) {
const nextPresentation = {
...defaults,
...(this.getDataPresentationState(displaySetId) || {}),
};
this.setDataPresentationState(displaySetId, nextPresentation);
return nextPresentation;
}
mergeDataPresentation(displaySetId, props) {
const nextPresentation = {
...(this.getDataPresentationState(displaySetId) || {}),
...props,
};
this.setDataPresentationState(displaySetId, nextPresentation);
if (this.bindings.has(displaySetId)) {
this.notifyDataPresentationModified(displaySetId, props);
}
return nextPresentation;
}
notifyDataPresentationModified(_displaySetId, _props) {
}
normalizeViewState(viewState) {
return viewState;
}
modified(previousCamera) {
if (this.isDestroyed) {
return;
}
this.forEachBinding((binding) => {
binding.applyViewState(this.viewState);
});
this.render();
if (previousCamera) {
this.triggerCameraModifiedEvent(previousCamera);
}
}
getCameraForEvent() {
return (this.getResolvedView()?.toICamera() ??
this.getViewState());
}
triggerCameraModifiedEvent(previousCamera) {
const eventDetail = {
previousCamera,
camera: this.getCameraForEvent(),
element: this.element,
viewportId: this.id,
renderingEngineId: this.renderingEngineId,
};
triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
}
triggerCameraResetEvent() {
const eventDetail = {
element: this.element,
viewportId: this.id,
camera: this.getCameraForEvent(),
renderingEngineId: this.renderingEngineId,
};
triggerEvent(this.element, Events.CAMERA_RESET, eventDetail);
}
getBinding(displaySetId) {
return this.bindings.get(displaySetId);
}
getDisplaySetRenderMode(displaySetId) {
return this.getBinding(displaySetId)?.rendering.renderMode;
}
getDisplaySetRole(displaySetId) {
return this.getBinding(displaySetId)?.role;
}
getFirstBinding() {
return this.bindings.values().next().value;
}
getSourceBinding() {
for (const binding of this.bindings.values()) {
if (binding.role === 'source') {
return binding;
}
}
return this.getFirstBinding();
}
getCurrentBinding() {
return this.getFirstBinding();
}
forEachBinding(visitor) {
for (const binding of this.bindings.values()) {
visitor(binding);
}
}
getReferenceViewContexts(_viewReference) {
const contexts = [];
const resolvedView = this.getResolvedView();
const camera = resolvedView?.toICamera();
for (const [dataId, binding] of this.bindings.entries()) {
contexts.push({
dataId,
dataIds: [binding.data.id],
frameOfReferenceUID: binding.getFrameOfReferenceUID() ??
resolvedView?.getFrameOfReferenceUID() ??
this.getFrameOfReferenceUID(),
cameraFocalPoint: camera?.focalPoint,
viewPlaneNormal: camera?.viewPlaneNormal,
});
}
if (!contexts.length) {
contexts.push({
frameOfReferenceUID: this.getFrameOfReferenceUID(),
cameraFocalPoint: camera?.focalPoint,
viewPlaneNormal: camera?.viewPlaneNormal,
});
}
return contexts;
}
renderBindings() {
if (this.isDestroyed) {
return false;
}
let renderedByAdapter = false;
this.forEachBinding((binding) => {
binding.render?.();
renderedByAdapter = renderedByAdapter || Boolean(binding.render);
});
return renderedByAdapter;
}
resizeBindings() {
if (this.isDestroyed) {
return;
}
this.forEachBinding((binding) => {
binding.resize?.();
});
}
destroyBindings() {
for (const dataId of Array.from(this.bindings.keys())) {
this.removeData(dataId);
}
}
onDestroy() {
}
}
export default GenericViewport;