ProxyManager

Introduction

vtkProxyManager is the central manager for managing proxy resources
in vtk.js.

Methods

extend

Decorates a given publicAPI + model with vtkProxyManager characteristics.

Argument Type Required Description
publicAPI Yes
model Yes
initialValues IProxyManagerInitialValues No

newInstance

Creates a vtkProxyManager.

Argument Type Required Description
initialValues IProxyManagerInitialValues No

Source

core.js
import macro from 'vtk.js/Sources/macros';

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// Proxy Registration Handling
// ----------------------------------------------------------------------------

export default function addRegistrationAPI(publicAPI, model) {
function registerProxy(proxy) {
if (!proxy) {
return;
}
model.proxyIdMapping[proxy.getProxyId()] = proxy;
const group = proxy.getProxyGroup();
if (!model.proxyByGroup[group]) {
model.proxyByGroup[group] = [];
}
if (model.proxyByGroup[group].indexOf(proxy) === -1) {
model.proxyByGroup[group].push(proxy);
}
proxy.setProxyManager(publicAPI);

// Make sure we invoke event after the current execution path
macro.setImmediate(() => {
publicAPI.invokeProxyRegistrationChange({
action: 'register',
proxyId: proxy.getProxyId(),
proxyName: proxy.getProxyName(),
proxyGroup: proxy.getProxyGroup(),
proxy,
});
});
}

// --------------------------------------------------------------------------

function unRegisterProxy(proxyOrId) {
const id = proxyOrId.getProxyId ? proxyOrId.getProxyId() : proxyOrId;
const proxy = model.proxyIdMapping[id];

// Unregister proxy in any group
Object.keys(model.proxyByGroup).forEach((groupName) => {
const proxyList = model.proxyByGroup[groupName];
const index = proxyList.indexOf(proxy);
if (index !== -1) {
proxyList.splice(index, 1);
}
});

delete model.proxyIdMapping[id];
proxy.gcPropertyLinks('application');
proxy.gcPropertyLinks('source');
proxy.setProxyManager(null);
publicAPI.invokeProxyRegistrationChange({
action: 'unregister',
proxyId: id,
proxyName: proxy.getProxyName(),
proxyGroup: proxy.getProxyGroup(),
proxy,
});
return proxy;
}

// --------------------------------------------------------------------------

publicAPI.setActiveSource = (source) => {
if (model.activeSource !== source) {
if (model.activeSourceSubscription) {
model.activeSourceSubscription.unsubscribe();
model.activeSourceSubscription = null;
}
model.activeSource = source;
if (source) {
model.activeSourceSubscription = source.onModified(publicAPI.modified);
}
publicAPI.modified();
publicAPI.invokeActiveSourceChange(source);
}
};

publicAPI.setActiveView = (view) => {
if (model.activeView !== view) {
if (model.activeViewSubscription) {
model.activeViewSubscription.unsubscribe();
model.activeViewSubscription = null;
}
model.activeView = view;
if (view) {
model.activeViewSubscription = view.onModified(publicAPI.modified);
}
publicAPI.modified();
publicAPI.invokeActiveViewChange(view);
}
};

// --------------------------------------------------------------------------

publicAPI.getProxyById = (id) => model.proxyIdMapping[id];

// --------------------------------------------------------------------------

publicAPI.getProxyGroups = () => Object.keys(model.proxyByGroup);

// --------------------------------------------------------------------------

publicAPI.getProxyInGroup = (name) =>
[].concat(model.proxyByGroup[name] || []);

// --------------------------------------------------------------------------

publicAPI.getSources = () => [].concat(model.proxyByGroup.Sources || []);
publicAPI.getRepresentations = () =>
[].concat(model.proxyByGroup.Representations || []);
publicAPI.getViews = () => [].concat(model.proxyByGroup.Views || []);

// --------------------------------------------------------------------------

publicAPI.createProxy = (group, name, options) => {
const { definitions } = model.proxyConfiguration;
if (!definitions[group] || !definitions[group][name]) {
return null;
}
const definition = definitions[group][name];
const definitionOptions = { ...definition.options, ...options };
const proxy = definition.class.newInstance({
...definitionOptions,
proxyGroup: group,
proxyName: name,
proxyManager: publicAPI,
});

// Handle proxy property settings
if (definition.proxyProps) {
const proxyMap = {};
Object.keys(definition.proxyProps).forEach((key) => {
const newProxyDef = definition.proxyProps[key];
proxyMap[key] = publicAPI.createProxy(
newProxyDef.group,
newProxyDef.name,
newProxyDef.options
);
});
proxy.set(proxyMap);
}

// Handle property setting
if (definition.props) {
proxy.set(definition.props);
}

registerProxy(proxy);

if (definitionOptions.activateOnCreate) {
proxy.activate();
}

return proxy;
};

// --------------------------------------------------------------------------

publicAPI.getRepresentation = (source, view) => {
const sourceToUse = source || publicAPI.getActiveSource();
const viewToUse = view || publicAPI.getActiveView();

// Can only get a representation for a source and a view
if (!sourceToUse || !viewToUse || !sourceToUse.getType()) {
return null;
}

const sourceId = sourceToUse.getProxyId();
const viewId = viewToUse.getProxyId();

let viewRepMap = model.sv2rMapping[sourceId];
if (!viewRepMap) {
viewRepMap = {};
model.sv2rMapping[sourceId] = viewRepMap;
}
let rep = viewRepMap[viewId];
if (!rep) {
const viewName = viewToUse.getProxyName();
const sourceType = sourceToUse.getType();
const definition =
model.proxyConfiguration.representations[viewName][sourceType];
if (!definition) {
vtkErrorMacro(
`No definition for representation of ${sourceType} in view ${viewName}`
);
return null;
}
rep = publicAPI.createProxy(
'Representations',
definition.name,
definition.options
);

model.r2svMapping[rep.getProxyId()] = { sourceId, viewId };
viewRepMap[viewId] = rep;

rep.setInput(sourceToUse);
viewToUse.addRepresentation(rep);
}
return rep;
};

// --------------------------------------------------------------------------

publicAPI.deleteProxy = (proxy) => {
const group = proxy.getProxyGroup().toLowerCase();

if (group === 'views') {
proxy.getRepresentations().forEach((repProxy) => {
publicAPI.deleteProxy(repProxy);
});
proxy.setContainer(null);
unRegisterProxy(proxy);
if (publicAPI.getActiveView() === proxy) {
publicAPI.setActiveView(publicAPI.getViews()[0]);
}
} else if (group === 'representations') {
const { sourceId, viewId } = model.r2svMapping[proxy.getProxyId()];
const view = publicAPI.getProxyById(viewId);
view.removeRepresentation(proxy);
delete model.r2svMapping[proxy.getProxyId()];
delete model.sv2rMapping[sourceId][viewId];
unRegisterProxy(proxy);
} else if (group === 'sources') {
const viewToRep = model.sv2rMapping[proxy.getProxyId()];
Object.keys(viewToRep).forEach((viewId) => {
publicAPI.deleteProxy(viewToRep[viewId]);
});
unRegisterProxy(proxy);
if (publicAPI.getActiveSource() === proxy) {
publicAPI.setActiveSource(publicAPI.getSources()[0]);
}
} else {
unRegisterProxy(proxy);
}

// Delete the object itself
proxy.delete();
};
}
index.d.ts
import { vtkObject } from '../../../interfaces';
import vtkSourceProxy from '../SourceProxy';
import vtkViewProxy from '../ViewProxy';
import vtkAbstractRepresentationProxy from '../AbstractRepresentationProxy';
import vtkLookupTableProxy from '../LookupTableProxy';
import vtkPiecewiseFunctionProxy from '../PiecewiseFunctionProxy';
import { VtkProxy } from '../../../macros';

export type ProxyConfiguration = object;

export interface ProxyRegistrationChangeInfo {
action: 'register' | 'unregister';
proxyId: string;
proxyName: string;
proxyGroup: string;
proxy: VtkProxy;
}

export interface IProxyManagerInitialValues {
proxyConfiguration?: ProxyConfiguration;
}

export interface vtkProxyManager extends vtkObject {
// core //

setProxyConfiguration(config: ProxyConfiguration): boolean;
getProxyConfiguration(): ProxyConfiguration;

setActiveSource<T>(sourceProxy: vtkSourceProxy<T>): boolean;
getActiveSource<T>(): vtkSourceProxy<T>;

setActiveView(viewProxy: vtkViewProxy): boolean;
getActiveView(): vtkViewProxy;

onProxyRegistrationChange(
callback: (changeInfo: ProxyRegistrationChangeInfo) => void
);

getProxyById<T extends VtkProxy>(id: string): T | undefined;
getProxyGroups(): string[];
getProxyInGroup(groupName: string): VtkProxy[];

getSources(): vtkSourceProxy<any>[];
getRepresentations(): vtkAbstractRepresentationProxy[];
getViews(): vtkViewProxy[];

createProxy<T extends VtkProxy>(
group: string,
name: string,
options?: Object
): T;

getRepresentation<T extends vtkAbstractRepresentationProxy>(
source: vtkSourceProxy<any>,
view: vtkViewProxy
): T | null;

deleteProxy(proxy: VtkProxy): void;

// view //

render(view?: vtkViewProxy): void;
renderAllViews(): void;
setAnimationOnAllViews(): void;
autoAnimateViews(debounceTimeout: number): void;
resizeAllViews(): void;
resetCamera(view?: vtkViewProxy): void;
createRepresentationInAllViews(source: vtkSourceProxy<any>): void;
resetCameraInAllViews(): void;

// properties //

// these are specific to the proxy configuration...
getLookupTable(arrayName: string, options?: any): vtkLookupTableProxy;
getPiecewiseFunction(
arrayName: string,
options?: any
): vtkPiecewiseFunctionProxy;
rescaleTransferFunctionToDataRange(
arrayName: string,
dataRange: [number, number]
): void;
}

/**
* Decorates a given publicAPI + model with vtkProxyManager characteristics.
*
* @param publicAPI
* @param model
* @param {IProxyManagerInitialValues} [initialValues]
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: IProxyManagerInitialValues
): void;

/**
* Creates a vtkProxyManager.
* @param {IProxyManagerInitialValues} [initialValues]
*/
export function newInstance(
initialValues?: IProxyManagerInitialValues
): vtkProxyManager;

/**
* vtkProxyManager is the central manager for managing proxy resources
* in vtk.js.
*/
export declare const vtkProxyManager: {
newInstance: typeof newInstance;
extend: typeof extend;
};

export default vtkProxyManager;
index.js
import macro from 'vtk.js/Sources/macros';

import core from './core';
import state from './state';
import view from './view';
import properties from './properties';

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(
model,
{
proxyIdMapping: {},
proxyByGroup: {},
proxyConfiguration: {}, // { definitions: {}, representations: { viewName: { sourceType: representationName } } }
sv2rMapping: {}, // sv2rMapping[sourceId][viewId] = rep
r2svMapping: {}, // r2svMapping[representationId] = { sourceId, viewId }
collapseState: {},
lookupTables: {},
piecewiseFunctions: {},
animating: false,
},
initialValues
);

// Object methods
macro.obj(publicAPI, model);
macro.setGet(publicAPI, model, [
'proxyConfiguration',
'activeSource',
'activeView',
]);
macro.event(publicAPI, model, 'ActiveSourceChange');
macro.event(publicAPI, model, 'ActiveViewChange');
macro.event(publicAPI, model, 'ProxyRegistrationChange');

core(publicAPI, model);
state(publicAPI, model);
view(publicAPI, model);
properties(publicAPI, model);

// Add proxy API
macro.proxy(publicAPI, model);

model.classHierarchy.push('vtkProxyManager');
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkProxyManager');

// ----------------------------------------------------------------------------

export default { newInstance, extend };
properties.js
export default function addVPropertyHandlingAPI(publicAPI, model) {
// --------------------------------------------------------------------------
// Property management
// --------------------------------------------------------------------------

publicAPI.getSections = () => {
const sections = [];
const source = publicAPI.getActiveSource();
if (!source) {
return [];
}
const view = publicAPI.getActiveView();
if (source) {
const section = source.getProxySection();
if (section.ui.length) {
sections.push(
Object.assign(section, {
collapsed: model.collapseState[section.name],
})
);
}
}
if (source && view) {
const representation = publicAPI.getRepresentation(source, view);
if (representation) {
const section = representation.getProxySection();
if (section.ui.length) {
sections.push(
Object.assign(section, {
collapsed: model.collapseState[section.name],
})
);
}
}
}
if (view) {
const section = view.getProxySection();
if (section.ui.length) {
sections.push(
Object.assign(section, {
collapsed: model.collapseState[section.name],
})
);
}
}
return sections;
};

// --------------------------------------------------------------------------

publicAPI.updateCollapseState = (name, state) => {
model.collapseState[name] = state;
publicAPI.modified();
};

// --------------------------------------------------------------------------

publicAPI.applyChanges = (changeSet) => {
const groupBy = {};
const keys = Object.keys(changeSet);
let count = keys.length;
while (count--) {
const key = keys[count];
const [id, prop] = key.split(':');
if (!groupBy[id]) {
groupBy[id] = {};
}
if (changeSet[key] === '__command_execute__') {
const obj = publicAPI.getProxyById(id);
if (obj) {
obj[prop]();
}
} else {
groupBy[id][prop] = changeSet[key];
}
}

// Apply changes
const objIds = Object.keys(groupBy);
count = objIds.length;
while (count--) {
const id = objIds[count];
const obj = publicAPI.getProxyById(id);
if (obj) {
obj.set(groupBy[id]);
}
}
publicAPI.modified();
publicAPI.renderAllViews();
};

// --------------------------------------------------------------------------
// Color Management
// --------------------------------------------------------------------------

publicAPI.getLookupTable = (arrayName, options) => {
if (!model.lookupTables[arrayName]) {
model.lookupTables[arrayName] = publicAPI.createProxy(
'Proxy',
'LookupTable',
{ arrayName, ...options }
);
}
return model.lookupTables[arrayName];
};

// --------------------------------------------------------------------------

publicAPI.getPiecewiseFunction = (arrayName, options) => {
if (!model.piecewiseFunctions[arrayName]) {
model.piecewiseFunctions[arrayName] = publicAPI.createProxy(
'Proxy',
'PiecewiseFunction',
{ arrayName, ...options }
);
}
return model.piecewiseFunctions[arrayName];
};

// --------------------------------------------------------------------------

publicAPI.rescaleTransferFunctionToDataRange = (arrayName, dataRange) => {
const lut = publicAPI.getLookupTable(arrayName);
const pwf = publicAPI.getPiecewiseFunction(arrayName);
lut.setDataRange(dataRange[0], dataRange[1]);
pwf.setDataRange(dataRange[0], dataRange[1]);
};
}
state.js
import vtk from 'vtk.js/Sources/vtk';
import vtkPiecewiseFunctionProxy from 'vtk.js/Sources/Proxy/Core/PiecewiseFunctionProxy';

function getProperties(proxy) {
const props = {};
proxy.listPropertyNames().forEach((name) => {
props[name] = proxy.getPropertyByName(name).value;
});
return props;
}

// ----------------------------------------------------------------------------
// Proxy State Handling
// ----------------------------------------------------------------------------

export default function addStateAPI(publicAPI, model) {
publicAPI.loadState = (state, options = {}) =>
new Promise((resolve, reject) => {
const proxyMapping = {};
const $oldToNewIdMapping = {};
const cameras = {};
const datasetHandler = options.datasetHandler || vtk;
const sourcePromises = [];

state.sources.forEach(({ id, group, name, props }) => {
sourcePromises.push(
Promise.resolve(datasetHandler(props.dataset)).then((dataset) => {
if (dataset) {
const proxy = publicAPI.createProxy(group, name);
proxy.setName(props.name);
proxy.setInputData(dataset, props.type);
proxyMapping[id] = proxy;
return proxy;
}
return null;
})
);
});

Promise.all(sourcePromises)
.then(() => {
const views = publicAPI.getViews();
state.views.forEach(({ id, group, name, props, camera }) => {
let proxy = null;
if (state.options.recycleViews) {
proxy = views.find(
(v) =>
v.getProxyGroup() === group &&
v.getProxyName() === name &&
v.getName() === props.name
);
}
if (!proxy) {
proxy = publicAPI.createProxy(group, name, {
disableAnimation: true,
});
} else {
proxy.setDisableAnimation(true);
}

proxy.set(props, true);
proxyMapping[id] = proxy;
cameras[id] = camera;
});

function updateView(view) {
if (!proxyMapping[view] || !cameras[view]) {
return;
}
proxyMapping[view].resetOrientation().then(() => {
proxyMapping[view].getCamera().set(cameras[view]);
proxyMapping[view]
.getRenderer()
.updateLightsGeometryToFollowCamera();
proxyMapping[view].renderLater();
});
}

state.representations.forEach(({ source, view, props }) => {
const proxy = publicAPI.getRepresentation(
proxyMapping[source],
proxyMapping[view]
);
proxy.set(props, true);
updateView(view);
});

// restore luts and pwfs after restoring reps to avoid
// rep initialization from resetting restored luts/pwfs
Object.keys(state.fields).forEach((fieldName) => {
const { lookupTable, piecewiseFunction } = state.fields[fieldName];
const lutProxy = publicAPI.getLookupTable(fieldName, lookupTable);
lutProxy.setPresetName(lookupTable.presetName);
lutProxy.setDataRange(...lookupTable.dataRange);
const pwfProxy = publicAPI.getPiecewiseFunction(
fieldName,
piecewiseFunction
);
switch (piecewiseFunction.mode) {
case vtkPiecewiseFunctionProxy.Mode.Gaussians:
pwfProxy.setGaussians(piecewiseFunction.gaussians);
break;
case vtkPiecewiseFunctionProxy.Mode.Points:
pwfProxy.setPoints(piecewiseFunction.points);
break;
case vtkPiecewiseFunctionProxy.Mode.Nodes:
pwfProxy.setNodes(piecewiseFunction.nodes);
break;
default:
// nothing that we can do
break;
}
pwfProxy.setMode(piecewiseFunction.mode);
pwfProxy.setDataRange(...piecewiseFunction.dataRange);
});

// Apply camera no matter what
Object.keys(cameras).forEach(updateView);

// Create id mapping
Object.keys(proxyMapping).forEach((originalId) => {
const newId = proxyMapping[originalId].getProxyId();
$oldToNewIdMapping[originalId] = newId;
});

// Re-enable animation on views
state.views.forEach(({ id }) => {
proxyMapping[id].setDisableAnimation(false);
});

resolve({ ...state.userData, $oldToNewIdMapping });
})
.catch(reject);
});

publicAPI.saveState = (options = {}, userData = {}) =>
new Promise((resolve, reject) => {
const sources = publicAPI.getSources();
// const representations = publicAPI.getRepresentations();
const views = publicAPI.getViews();

// Extract handlers
const datasetHandler = options.datasetHandler || ((d) => d.getState());
delete options.datasetHandler;
const datasets = [];

const fieldNames = new Set();
const state = {
userData,
options,
sources: [],
views: [],
representations: [],
fields: {},
};
sources.forEach((source) => {
const dataset = Promise.resolve(
datasetHandler(source.getDataset(), source)
);
datasets.push(dataset);
state.sources.push({
id: source.getProxyId(),
group: source.getProxyGroup(),
name: source.getProxyName(),
props: {
name: source.getName(),
type: source.getType(),
dataset,
},
});
});
views.forEach((view) => {
const camera = view.getCamera().get('position', 'viewUp', 'focalPoint');
state.views.push({
id: view.getProxyId(),
group: view.getProxyGroup(),
name: view.getProxyName(),
props: Object.assign(
getProperties(view),
view.get('axis', 'orientation', 'viewUp')
),
camera,
});

// Loop over view representations
const representations = view.getRepresentations();
representations.forEach((representation) => {
state.representations.push({
source: representation.getInput().getProxyId(),
view: view.getProxyId(),
props: getProperties(representation),
});
fieldNames.add(representation.getColorBy()[0]);
});
});

fieldNames.forEach((fieldName) => {
state.fields[fieldName] = {
lookupTable: publicAPI
.getLookupTable(fieldName)
.get(
'mode',
'presetName',
'rgbPoints',
'hsvPoints',
'nodes',
'arrayName',
'arrayLocation',
'dataRange'
),
piecewiseFunction: publicAPI
.getPiecewiseFunction(fieldName)
.get(
'mode',
'gaussians',
'points',
'nodes',
'arrayName',
'arrayLocation',
'dataRange'
),
};
});

Promise.all(datasets)
.then(() => {
// Patch datasets in state to the result of the promises
for (let i = 0; i < state.sources.length; i++) {
state.sources[i].props.dataset.then((value) => {
state.sources[i].props.dataset = value;
});
}

// provide valide state
resolve(state);
})
.catch(reject);
});
}
view.js
import macro from 'vtk.js/Sources/macros';

export default function addViewHandlingAPI(publicAPI, model) {
publicAPI.create3DView = (options) =>
publicAPI.createProxy('Views', 'View3D', options);

// --------------------------------------------------------------------------

publicAPI.create2DView = (options) =>
publicAPI.createProxy('Views', 'View2D', options);

// --------------------------------------------------------------------------

publicAPI.render = (view) => {
const viewToRender = view || publicAPI.getActiveView();
if (viewToRender) {
viewToRender.renderLater();
}
};

// --------------------------------------------------------------------------

publicAPI.renderAllViews = (blocking = false) => {
const allViews = publicAPI.getViews();
for (let i = 0; i < allViews.length; i++) {
allViews[i].render(blocking);
}
};

// --------------------------------------------------------------------------

publicAPI.setAnimationOnAllViews = (enable = false) => {
const allViews = publicAPI
.getViews()
.filter((v) => !enable || v.getContainer());
for (let i = 0; i < allViews.length; i++) {
allViews[i].setAnimation(enable, publicAPI);
}
};

// --------------------------------------------------------------------------

function clearAnimations() {
model.animating = false;
const allViews = publicAPI.getViews();
for (let i = 0; i < allViews.length; i++) {
allViews[i].setAnimation(false, publicAPI);
}
}

// --------------------------------------------------------------------------

publicAPI.autoAnimateViews = (debouceTimout = 250) => {
if (!model.animating) {
model.animating = true;
const allViews = publicAPI.getViews().filter((v) => v.getContainer());
for (let i = 0; i < allViews.length; i++) {
allViews[i].setAnimation(true, publicAPI);
}
model.clearAnimations = macro.debounce(clearAnimations, debouceTimout);
}
model.clearAnimations();
};

// --------------------------------------------------------------------------

publicAPI.resizeAllViews = () => {
const allViews = publicAPI.getViews();
for (let i = 0; i < allViews.length; i++) {
allViews[i].resize();
}
};

// --------------------------------------------------------------------------

publicAPI.resetCamera = (view) => {
const viewToRender = view || publicAPI.getActiveView();
if (viewToRender && viewToRender.resetCamera) {
viewToRender.resetCamera();
}
};

// --------------------------------------------------------------------------

publicAPI.createRepresentationInAllViews = (source) => {
const allViews = publicAPI.getViews();
for (let i = 0; i < allViews.length; i++) {
publicAPI.getRepresentation(source, allViews[i]);
}
};

// --------------------------------------------------------------------------

publicAPI.resetCameraInAllViews = () => {
const allViews = publicAPI.getViews();
for (let i = 0; i < allViews.length; i++) {
allViews[i].resetCamera();
}
};
}