@acransac/vtk.js
Version:
Visualization Toolkit for the Web
256 lines (222 loc) • 8.4 kB
JavaScript
/* eslint-disable import/prefer-default-export */
/* eslint-disable import/no-extraneous-dependencies */
import 'vtk.js/Sources/favicon';
import JSZip from 'jszip';
import macro from 'vtk.js/Sources/macro';
import HttpDataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper';
import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
import vtkURLExtract from 'vtk.js/Sources/Common/Core/URLExtract';
import vtkOBJReader from 'vtk.js/Sources/IO/Misc/OBJReader';
import vtkMTLReader from 'vtk.js/Sources/IO/Misc/MTLReader';
import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper';
import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor';
import style from './OBJViewer.module.css';
const iOS = /iPad|iPhone|iPod/.test(window.navigator.platform);
let autoInit = true;
// Look at URL an see if we should load a file
// ?fileURL=https://data.kitware.com/api/v1/item/59cdbb588d777f31ac63de08/download
// &noInterpolation
const userParams = vtkURLExtract.extractURLParameters();
// Add class to body if iOS device --------------------------------------------
if (iOS) {
document.querySelector('body').classList.add('is-ios-device');
}
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function emptyContainer(container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
function loadZipContent(zipContent, renderWindow, renderer) {
const fileContents = { obj: {}, mtl: {}, img: {} };
const zip = new JSZip();
zip.loadAsync(zipContent).then(() => {
let workLoad = 0;
function done() {
if (workLoad !== 0) {
return;
}
// Attach images to MTLs
const promises = [];
Object.keys(fileContents.mtl).forEach((mtlFilePath) => {
const mtlReader = fileContents.mtl[mtlFilePath];
const basePath = mtlFilePath
.split('/')
.filter((v, i, a) => i < a.length - 1)
.join('/');
mtlReader.listImages().forEach((relPath) => {
const key = basePath.length ? `${basePath}/${relPath}` : relPath;
const imgSRC = fileContents.img[key];
if (imgSRC) {
promises.push(mtlReader.setImageSrc(relPath, imgSRC));
console.log('register promise');
}
});
});
Promise.all(promises).then(() => {
console.log('load obj...');
// Create pipeline from obj
Object.keys(fileContents.obj).forEach((objFilePath) => {
const mtlFilePath = objFilePath.replace(/\.obj$/, '.mtl');
const objReader = fileContents.obj[objFilePath];
const mtlReader = fileContents.mtl[mtlFilePath];
const size = objReader.getNumberOfOutputPorts();
for (let i = 0; i < size; i++) {
const source = objReader.getOutputData(i);
const mapper = vtkMapper.newInstance();
const actor = vtkActor.newInstance();
const name = source.get('name').name;
actor.setMapper(mapper);
mapper.setInputData(source);
renderer.addActor(actor);
if (mtlReader && name) {
mtlReader.applyMaterialToActor(name, actor);
}
}
});
renderer.resetCamera();
renderWindow.render();
});
}
zip.forEach((relativePath, zipEntry) => {
if (relativePath.match(/\.obj$/i)) {
workLoad++;
zipEntry.async('string').then((txt) => {
const reader = vtkOBJReader.newInstance({ splitMode: 'usemtl' });
reader.parseAsText(txt);
fileContents.obj[relativePath] = reader;
workLoad--;
done();
});
}
if (relativePath.match(/\.mtl$/i)) {
workLoad++;
zipEntry.async('string').then((txt) => {
const reader = vtkMTLReader.newInstance({
interpolateTextures: !userParams.noInterpolation,
});
reader.parseAsText(txt);
fileContents.mtl[relativePath] = reader;
workLoad--;
done();
});
}
if (relativePath.match(/\.jpg$/i) || relativePath.match(/\.png$/i)) {
workLoad++;
zipEntry.async('base64').then((txt) => {
const ext = relativePath.slice(-3).toLowerCase();
fileContents.img[relativePath] = `data:image/${ext};base64,${txt}`;
workLoad--;
done();
});
}
});
});
}
export function load(container, options) {
autoInit = false;
emptyContainer(container);
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
background: [0, 0, 0],
rootContainer: container,
containerStyle: { height: '100%', width: '100%', position: 'absolute' },
});
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = fullScreenRenderer.getRenderWindow();
if (options.file) {
if (options.ext === 'obj') {
const reader = new FileReader();
reader.onload = function onLoad(e) {
const objReader = vtkOBJReader.newInstance();
objReader.parseAsText(reader.result);
const nbOutputs = objReader.getNumberOfOutputPorts();
for (let idx = 0; idx < nbOutputs; idx++) {
const source = objReader.getOutputData(idx);
const mapper = vtkMapper.newInstance();
const actor = vtkActor.newInstance();
actor.setMapper(mapper);
mapper.setInputData(source);
renderer.addActor(actor);
}
renderer.resetCamera();
renderWindow.render();
};
reader.readAsText(options.file);
} else {
loadZipContent(options.file, renderWindow, renderer);
}
} else if (options.fileURL) {
const progressContainer = document.createElement('div');
progressContainer.setAttribute('class', style.progress);
container.appendChild(progressContainer);
const progressCallback = (progressEvent) => {
if (progressEvent.lengthComputable) {
const percent = Math.floor(
(100 * progressEvent.loaded) / progressEvent.total
);
progressContainer.innerHTML = `Loading ${percent}%`;
} else {
progressContainer.innerHTML = macro.formatBytesToProperUnit(
progressEvent.loaded
);
}
};
HttpDataAccessHelper.fetchBinary(options.fileURL, {
progressCallback,
}).then((content) => {
container.removeChild(progressContainer);
loadZipContent(content, renderWindow, renderer);
});
}
}
export function initLocalFileLoader(container) {
const exampleContainer = document.querySelector('.content');
const rootBody = document.querySelector('body');
const myContainer = container || exampleContainer || rootBody;
if (myContainer !== container) {
myContainer.classList.add(style.fullScreen);
rootBody.style.margin = '0';
rootBody.style.padding = '0';
} else {
rootBody.style.margin = '0';
rootBody.style.padding = '0';
}
const fileContainer = document.createElement('div');
fileContainer.innerHTML = `<div class="${style.bigFileDrop}"/><input type="file" accept=".zip,.obj" style="display: none;"/>`;
myContainer.appendChild(fileContainer);
const fileInput = fileContainer.querySelector('input');
function handleFile(e) {
preventDefaults(e);
const dataTransfer = e.dataTransfer;
const files = e.target.files || dataTransfer.files;
if (files.length === 1) {
myContainer.removeChild(fileContainer);
const ext = files[0].name.split('.').slice(-1)[0];
load(myContainer, { file: files[0], ext });
}
}
fileInput.addEventListener('change', handleFile);
fileContainer.addEventListener('drop', handleFile);
fileContainer.addEventListener('click', (e) => fileInput.click());
fileContainer.addEventListener('dragover', preventDefaults);
}
if (userParams.url || userParams.fileURL) {
const exampleContainer = document.querySelector('.content');
const rootBody = document.querySelector('body');
const myContainer = exampleContainer || rootBody;
if (myContainer) {
myContainer.classList.add(style.fullScreen);
rootBody.style.margin = '0';
rootBody.style.padding = '0';
}
load(myContainer, userParams);
}
// Auto setup if no method get called within 100ms
setTimeout(() => {
if (autoInit) {
initLocalFileLoader();
}
}, 100);