import * as macro from 'vtk.js/Sources/macro';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; import vtkCamera from 'vtk.js/Sources/Rendering/Core/Camera'; import vtkColorTransferFunction from 'vtk.js/Sources/Rendering/Core/ColorTransferFunction'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkPoints from 'vtk.js/Sources/Common/Core/Points'; import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper'; import vtkLight from 'vtk.js/Sources/Rendering/Core/Light'; import vtkLookupTable from 'vtk.js/Sources/Common/Core/LookupTable'; import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData'; import vtkProperty from 'vtk.js/Sources/Rendering/Core/Property'; import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer'; import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow'; import vtkTexture from 'vtk.js/Sources/Rendering/Core/Texture'; import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume'; import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper'; import vtkVolumeProperty from 'vtk.js/Sources/Rendering/Core/VolumeProperty'; import vtkImageSlice from 'vtk.js/Sources/Rendering/Core/ImageSlice'; import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper'; import vtkImageProperty from 'vtk.js/Sources/Rendering/Core/ImageProperty'; import vtkPiecewiseFunction from 'vtk.js/Sources/Common/DataModel/PiecewiseFunction';
const TYPE_HANDLERS = {}; const WRAPPED_ID_RE = /instance:\${([^}]+)}/; const WRAP_ID = (id) => `instance:$\{${id}}`; const ONE_TIME_INSTANCE_TRACKERS = {}; const SKIPPED_INSTANCE_IDS = []; const EXCLUDE_INSTANCE_MAP = {}; const DATA_ARRAY_MAPPER = { vtkPoints, vtkCellArray, vtkDataArray, };
function extractCallArgs(synchronizerContext, argList) { return argList.map((arg) => { const m = WRAPPED_ID_RE.exec(arg); if (m) { return synchronizerContext.getInstance(m[1]); } return arg; }); }
function extractInstanceIds(argList) { return argList .map((arg) => WRAPPED_ID_RE.exec(arg)) .filter((m) => m) .map((m) => m[1]); }
function extractDependencyIds(state, depList = []) { if (state.dependencies) { state.dependencies.forEach((childState) => { depList.push(childState.id); extractDependencyIds(childState, depList); }); } return depList; }
function bindArrays(arraysToBind) { while (arraysToBind.length) { const [fn, args] = arraysToBind.shift(); fn(...args); } }
function createNewArrayHandler(instance, arrayMetadata, arraysToBind) { return (values) => { const vtkClass = arrayMetadata.vtkClass ? arrayMetadata.vtkClass : 'vtkDataArray'; const array = DATA_ARRAY_MAPPER[vtkClass].newInstance({ ...arrayMetadata, values, }); const regMethod = arrayMetadata.registration ? arrayMetadata.registration : 'addArray'; const location = arrayMetadata.location ? instance.getReferenceByName(arrayMetadata.location) : instance; arraysToBind.push([location[regMethod], [array]]); return array; }; }
function update(type, instance, props, context) { if (!instance) { return; } const handler = TYPE_HANDLERS[type]; if (handler && handler.update) { handler.update(instance, props, context); } else { console.log('no updater for', type); } }
function build(type, initialProps = {}) { const handler = TYPE_HANDLERS[type];
if (handler && handler.build) { return handler.build(initialProps); }
console.log('No builder for', type); return null; }
function excludeInstance(type, propertyName, propertyValue) { EXCLUDE_INSTANCE_MAP[type] = { key: propertyName, value: propertyValue, }; }
function getSupportedTypes() { return Object.keys(TYPE_HANDLERS); }
function clearTypeMapping() { Object.keys(TYPE_HANDLERS).forEach((key) => { delete TYPE_HANDLERS[key]; }); }
function updateRenderWindow(instance, props, context) { return update('vtkRenderWindow', instance, props, context); }
function clearAllOneTimeUpdaters() { Object.keys(ONE_TIME_INSTANCE_TRACKERS).forEach((key) => { delete ONE_TIME_INSTANCE_TRACKERS[key]; }); }
function clearOneTimeUpdaters(...ids) { if (ids.length === 0) { return clearAllOneTimeUpdaters(); }
let array = ids; if (array.length === 1 && Array.isArray(array[0])) { array = array[0]; } array.forEach((instanceId) => { delete ONE_TIME_INSTANCE_TRACKERS[instanceId]; }); return array; }
function notSkippedInstance(call) { if (call[1].length === 1) { return SKIPPED_INSTANCE_IDS.indexOf(call[1][0]) === -1; } let keep = false; for (let i = 0; i < call[1].length; i++) { keep = keep || SKIPPED_INSTANCE_IDS.indexOf(call[1][i]) === -1; } return keep; }
function genericUpdater(instance, state, context) { context.start();
instance.set(state.properties);
if (state.dependencies) { state.dependencies.forEach((childState) => { const { id, type } = childState;
if (EXCLUDE_INSTANCE_MAP[type]) { const { key, value } = EXCLUDE_INSTANCE_MAP[type]; if (!key || childState.properties[key] === value) { SKIPPED_INSTANCE_IDS.push(WRAP_ID(id)); return; } }
let childInstance = context.getInstance(id); if (!childInstance) { childInstance = build(type, { managedInstanceId: id }); context.registerInstance(id, childInstance); } update(type, childInstance, childState, context); }); }
if (state.calls) { state.calls.filter(notSkippedInstance).forEach((call) => { instance[call[0]].apply(null, extractCallArgs(context, call[1])); }); }
if (state.arrays) { const arraysToBind = []; const promises = state.arrays.map((arrayMetadata) => { context.start(); return context .getArray(arrayMetadata.hash, arrayMetadata.dataType, context) .then(createNewArrayHandler(instance, arrayMetadata, arraysToBind)) .catch((error) => { console.log( 'Error fetching array', JSON.stringify(arrayMetadata), error ); }) .finally(context.end); }); context.start(); Promise.all(promises) .then(() => { bindArrays(arraysToBind); }) .catch((error) => { console.error( 'Error in array handling for state', JSON.stringify(state), error ); }) .finally(context.end); } context.end(); }
function oneTimeGenericUpdater(instance, state, context) { if (!ONE_TIME_INSTANCE_TRACKERS[state.id]) { genericUpdater(instance, state, context); }
ONE_TIME_INSTANCE_TRACKERS[state.id] = true; }
function rendererUpdater(instance, state, context) {
genericUpdater(instance, state, context);
const allActorsDeps = new Set();
if (state.dependencies) { state.dependencies.forEach((childState) => { const viewPropInstance = context.getInstance(childState.id); if (viewPropInstance) { const flattenedDepIds = extractDependencyIds(childState); viewPropInstance.set({ flattenedDepIds }, true); flattenedDepIds.forEach((depId) => allActorsDeps.add(depId)); } }); }
const unregisterCandidates = new Set();
if (state.calls) { state.calls .filter(notSkippedInstance) .filter((call) => call[0] === 'removeViewProp') .forEach((call) => { extractInstanceIds(call[1]).forEach((vpId) => { const deps = context.getInstance(vpId).get('flattenedDepIds') .flattenedDepIds; if (deps) { deps.forEach((depId) => unregisterCandidates.add(depId)); } unregisterCandidates.add(vpId); }); }); }
const idsToUnregister = [...unregisterCandidates].filter( (depId) => !allActorsDeps.has(depId) ); idsToUnregister.forEach((depId) => context.unregisterInstance(depId)); }
function vtkRenderWindowUpdater(instance, state, context) { if (state.calls) { state.calls .filter(notSkippedInstance) .filter((call) => call[0] === 'removeRenderer') .forEach((call) => { extractInstanceIds(call[1]).forEach((renId) => { const renderer = context.getInstance(renId); const viewProps = renderer.getViewProps(); viewProps.forEach((viewProp) => { const deps = viewProp.get('flattenedDepIds').flattenedDepIds; if (deps) { deps.forEach((depId) => context.unregisterInstance(depId)); } context.unregisterInstance(context.getInstanceId(viewProp)); }); renderer.removeAllViewProps(); }); }); }
instance.render();
genericUpdater(instance, state, context); }
function colorTransferFunctionUpdater(instance, state, context) { context.start(); const nodes = state.properties.nodes.map( ([x, r, g, b, midpoint, sharpness]) => ({ x, r, g, b, midpoint, sharpness }) ); instance.set({ ...state.properties, nodes }, true); instance.sortAndUpdateRange(); instance.modified(); context.end(); }
function piecewiseFunctionUpdater(instance, state, context) { context.start(); const nodes = state.properties.nodes.map(([x, y, midpoint, sharpness]) => ({ x, y, midpoint, sharpness, })); instance.set({ ...state.properties, nodes }, true); instance.sortAndUpdateRange(); instance.modified(); context.end(); }
function createDataSetUpdate(piecesToFetch = []) { return (instance, state, context) => { context.start();
if (!state.arrays) { state.arrays = []; }
piecesToFetch.forEach((key) => { if (state.properties[key]) { const arrayMeta = state.properties[key]; arrayMeta.registration = `set${macro.capitalize(key)}`; state.arrays.push(arrayMeta); delete state.properties[key]; } });
const fieldsArrays = state.properties.fields || []; state.arrays.push(...fieldsArrays); delete state.properties.fields;
instance.getPointData().removeAllArrays(); instance.getCellData().removeAllArrays();
genericUpdater(instance, state, context);
context.end(); }; }
const polydataUpdater = createDataSetUpdate([ 'points', 'polys', 'verts', 'lines', 'strips', ]); const imageDataUpdater = createDataSetUpdate([]);
function setTypeMapping(type, buildFn = null, updateFn = genericUpdater) { if (!build && !update) { delete TYPE_HANDLERS[type]; return; }
TYPE_HANDLERS[type] = { build: buildFn, update: updateFn }; }
const DEFAULT_ALIASES = { vtkMapper: [ 'vtkOpenGLPolyDataMapper', 'vtkCompositePolyDataMapper2', 'vtkDataSetMapper', ], vtkProperty: ['vtkOpenGLProperty'], vtkRenderer: ['vtkOpenGLRenderer'], vtkCamera: ['vtkOpenGLCamera'], vtkColorTransferFunction: ['vtkPVDiscretizableColorTransferFunction'], vtkActor: ['vtkOpenGLActor', 'vtkPVLODActor'], vtkLight: ['vtkOpenGLLight', 'vtkPVLight'], vtkTexture: ['vtkOpenGLTexture'], vtkImageMapper: ['vtkOpenGLImageSliceMapper'], vtkVolumeMapper: ['vtkFixedPointVolumeRayCastMapper'], };
const DEFAULT_MAPPING = { vtkRenderWindow: { build: vtkRenderWindow.newInstance, update: vtkRenderWindowUpdater, }, vtkRenderer: { build: vtkRenderer.newInstance, update: rendererUpdater, }, vtkLookupTable: { build: vtkLookupTable.newInstance, update: genericUpdater, }, vtkCamera: { build: vtkCamera.newInstance, update: oneTimeGenericUpdater, }, vtkPolyData: { build: vtkPolyData.newInstance, update: polydataUpdater, }, vtkImageData: { build: vtkImageData.newInstance, update: imageDataUpdater, }, vtkMapper: { build: vtkMapper.newInstance, update: genericUpdater, }, vtkGlyph3DMapper: { build: vtkGlyph3DMapper.newInstance, update: genericUpdater, }, vtkProperty: { build: vtkProperty.newInstance, update: genericUpdater, }, vtkActor: { build: vtkActor.newInstance, update: genericUpdater, }, vtkLight: { build: vtkLight.newInstance, update: genericUpdater, }, vtkColorTransferFunction: { build: vtkColorTransferFunction.newInstance, update: colorTransferFunctionUpdater, }, vtkTexture: { build: vtkTexture.newInstance, update: genericUpdater, }, vtkVolume: { build: vtkVolume.newInstance, update: genericUpdater, }, vtkVolumeMapper: { build: vtkVolumeMapper.newInstance, update: genericUpdater, }, vtkVolumeProperty: { build: vtkVolumeProperty.newInstance, update: genericUpdater, }, vtkImageSlice: { build: vtkImageSlice.newInstance, update: genericUpdater, }, vtkImageMapper: { build: vtkImageMapper.newInstance, update: genericUpdater, }, vtkImageProperty: { build: vtkImageProperty.newInstance, update: genericUpdater, }, vtkPiecewiseFunction: { build: vtkPiecewiseFunction.newInstance, update: piecewiseFunctionUpdater, }, };
function setDefaultMapping(reset = true) { if (reset) { clearTypeMapping(); }
Object.keys(DEFAULT_MAPPING).forEach((type) => { const mapping = DEFAULT_MAPPING[type]; setTypeMapping(type, mapping.build, mapping.update); }); }
function applyDefaultAliases() { Object.keys(DEFAULT_ALIASES).forEach((name) => { const aliases = DEFAULT_ALIASES[name]; aliases.forEach((alias) => { TYPE_HANDLERS[alias] = TYPE_HANDLERS[name]; }); }); }
function alwaysUpdateCamera() { setTypeMapping('vtkCamera', vtkCamera.newInstance); applyDefaultAliases(); }
setDefaultMapping(); applyDefaultAliases();
EXCLUDE_INSTANCE_MAP.vtkOpenGLLight = {}; EXCLUDE_INSTANCE_MAP.vtkPVLight = {}; EXCLUDE_INSTANCE_MAP.vtkLight = {};
export default { build, update, genericUpdater, oneTimeGenericUpdater, setTypeMapping, clearTypeMapping, getSupportedTypes, clearOneTimeUpdaters, updateRenderWindow, excludeInstance, setDefaultMapping, applyDefaultAliases, alwaysUpdateCamera, };
|