import 'vtk.js/Sources/favicon';
import 'vtk.js/Sources/Rendering/Profiles/Geometry';
import JSZip from 'jszip';
import macro from 'vtk.js/Sources/macros';
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 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper';
import style from './OBJViewer.module.css';
const iOS = /iPad|iPhone|iPod/.test(window.navigator.platform); let autoInit = true;
const userParams = vtkURLExtract.extractURLParameters();
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; }
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...'); 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); }
setTimeout(() => { if (autoInit) { initLocalFileLoader(); } }, 100);
|