UNPKG

ftn-stl-viewer

Version:

A component for viewing an STL object from a given URL by utilizing Three.js

258 lines (220 loc) 6.76 kB
import THREE from './Three'; import ReactDOM from 'react-dom'; let OrbitControls = require('three-orbit-controls')(THREE); const DIRECTIONAL_LIGHT = 'directionalLight'; class Paint { constructor() { this.loader = new THREE.STLLoader(); this.scene = new THREE.Scene(); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.reqNumber = 0; } init(context) { this.component = context; this.width = context.props.width; this.height = context.props.height; this.modelColor = context.props.modelColor; this.backgroundColor = context.props.backgroundColor; this.orbitControls = context.props.orbitControls; this.rotate = context.props.rotate; this.cameraX = context.props.cameraX; this.cameraY = context.props.cameraY; this.cameraZ = context.props.cameraZ; this.rotationSpeeds = context.props.rotationSpeeds; this.lights = context.props.lights; this.lightColor = context.props.lightColor; this.model = context.props.model; if (this.mesh !== undefined) { this.scene.remove(this.mesh); this.mesh.geometry.dispose(); this.mesh.material.dispose(); this.scene.remove(this.grid); } const directionalLightObj = this.scene.getObjectByName(DIRECTIONAL_LIGHT); if (directionalLightObj) { this.scene.remove(directionalLightObj); } if (this.animationRequestId) { cancelAnimationFrame(this.animationRequestId); } //Detector.addGetWebGLMessage(); this.distance = 10000; // lights processing const hasMultipleLights = this.lights.reduce( (acc, item) => acc && Array.isArray(item), true ); if (hasMultipleLights) { this.lights.forEach(this.addLight.bind(this)); } else { this.addLight(this.lights); } this.reqNumber += 1; this.addSTLToScene(this.reqNumber); } addLight(lights, index = 0) { const directionalLight = new THREE.DirectionalLight(this.lightColor); directionalLight.position.set(...lights); directionalLight.name = DIRECTIONAL_LIGHT + index; directionalLight.position.normalize(); this.scene.add(directionalLight); } loadSTLFromUrl(url, reqId) { return new Promise(resolve => { this.loader.crossOrigin = ''; this.loader.loadFromUrl(url, geometry => { if (this.reqNumber !== reqId) { return; } resolve(geometry); }); }); } loadFromFile(file) { return new Promise(resolve => { this.loader.loadFromFile(file, geometry => { resolve(geometry); }); }); } addSTLToScene(reqId) { let loadPromise; if (typeof this.model === 'string') { loadPromise = this.loadSTLFromUrl(this.model, reqId); } else if (this.model instanceof ArrayBuffer) { loadPromise = this.loadFromFile(this.model); } else { return Promise.resolve(null); } return loadPromise.then(geometry => { // Calculate mesh noramls for MeshLambertMaterial. geometry.computeFaceNormals(); geometry.computeVertexNormals(); // Center the object geometry.center(); let material = new THREE.MeshLambertMaterial({ overdraw: true, color: this.modelColor }); if (geometry.hasColors) { material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); } this.mesh = new THREE.Mesh(geometry, material); // Set the object's dimensions geometry.computeBoundingBox(); this.xDims = geometry.boundingBox.max.x - geometry.boundingBox.min.x; this.yDims = geometry.boundingBox.max.y - geometry.boundingBox.min.y; this.zDims = geometry.boundingBox.max.z - geometry.boundingBox.min.z; if (this.rotate) { this.mesh.rotation.x = this.rotationSpeeds[0]; this.mesh.rotation.y = this.rotationSpeeds[1]; this.mesh.rotation.z = this.rotationSpeeds[2]; } this.scene.add(this.mesh); this.addCamera(); this.addInteractionControls(); this.addToReactComponent(); // Start the animation this.animate(); }); } addCamera() { // Add the camera this.camera = new THREE.PerspectiveCamera( 30, this.width / this.height, 1, this.distance ); if (this.cameraZ === null) { this.cameraZ = Math.max(this.xDims * 3, this.yDims * 3, this.zDims * 3); } this.camera.position.set(this.cameraX, this.cameraY, this.cameraZ); this.scene.add(this.camera); this.camera.lookAt(this.mesh); this.renderer.set; this.renderer.setSize(this.width, this.height); this.renderer.setClearColor(this.backgroundColor, 1); } addInteractionControls() { // Add controls for mouse interaction if (this.orbitControls) { this.controls = new OrbitControls( this.camera, ReactDOM.findDOMNode(this.component) ); this.controls.enableKeys = false; this.controls.addEventListener('change', this.orbitRender.bind(this)); } } addToReactComponent() { // Add to the React Component ReactDOM.findDOMNode(this.component).replaceChild( this.renderer.domElement, ReactDOM.findDOMNode(this.component).firstChild ); } /** * Animate the scene * @returns {void} */ animate() { // note: three.js includes requestAnimationFrame shim if (this.rotate) { this.animationRequestId = requestAnimationFrame(this.animate.bind(this)); } if (this.orbitControls) { this.controls.update(); } this.render(); } /** * Render the scene after turning off the rotation * @returns {void} */ orbitRender() { if (this.rotate) { this.rotate = false; } this.render(); } /** * Deallocate Mesh, renderer context. * @returns {void} */ clean() { if (this.mesh !== undefined) { this.mesh.geometry.dispose(); this.mesh.material.dispose(); this.scene.remove(this.mesh); delete this.mesh; } const directionalLightObj = this.scene.getObjectByName(DIRECTIONAL_LIGHT); if (directionalLightObj) { this.scene.remove(directionalLightObj); } if (this.animationRequestId) { cancelAnimationFrame(this.animationRequestId); } this.renderer.dispose(); this.renderer.forceContextLoss(); } /** * Render the scene * @returns {void} */ render() { if (this.mesh && this.rotate) { this.mesh.rotation.x += this.rotationSpeeds[0]; this.mesh.rotation.y += this.rotationSpeeds[1]; this.mesh.rotation.z += this.rotationSpeeds[2]; } this.renderer.render(this.scene, this.camera); } } export default Paint;