openscad-remote/tools/js/utils.js

198 lines
6.3 KiB
JavaScript

import * as THREE from "https://cdn.jsdelivr.net/npm/three@v0.120.0/build/three.module.js";
import { STLLoader } from "https://cdn.jsdelivr.net/npm/three@v0.120.0/examples/jsm/loaders/STLLoader.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@v0.120.0/examples/jsm/controls/OrbitControls.js";
class STLViewer {
constructor(container) {
this.mesh = null;
this.loader = new STLLoader();
this.container = container;
this.camera = new THREE.PerspectiveCamera(
60,
this.container.clientWidth / this.container.clientHeight,
1,
1000
);
this.camera.position.set(100, 100, 100);
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.setRenderer();
// Controls
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.setControls();
// World
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xefefef);
const axes = new THREE.AxesHelper(100);
this.scene.add(axes);
const gridXZ = new THREE.GridHelper(100, 10, new THREE.Color(0x006600));
gridXZ.position.set(0, 0, 0);
this.scene.add(gridXZ);
const gridXY = new THREE.GridHelper(100, 10, new THREE.Color(0x000066));
gridXY.position.set(0, 0, 0);
gridXY.rotation.x = Math.PI/2;
this.scene.add(gridXY);
const gridYZ = new THREE.GridHelper(100, 10, new THREE.Color(0x660000));
gridYZ.position.set(0, 0, 0);
gridYZ.rotation.z = Math.PI/2;
this.scene.add(gridYZ);
// Lights
this.scene.add(new THREE.HemisphereLight(0x443333, 0x111122));
this.addShadowedLight(1, 1, 1, 0xffffff, 1.35);
this.addShadowedLight(0.5, 1, - 1, 0xffffff, 1);
this.container.appendChild(this.renderer.domElement);
// Events listeners
window.addEventListener('resize', this.onWindowResize);
this.animate();
}
loadSTLBlob(blob) {
if (this.mesh != null) {
this.mesh.geometry.dispose();
this.mesh.material.dispose();
this.scene.remove(this.mesh);
this.mesh = null;
}
blob.arrayBuffer().then(buffer => {
const geometry = this.loader.parse(buffer);
const material = new THREE.MeshPhongMaterial( { color: 0xff5533, specular: 0x111111, shininess: 200 } );
this.mesh = new THREE.Mesh( geometry, material );
this.mesh.position.set(0, 0, 0);
this.mesh.rotation.set(0, 0, 0);
this.mesh.scale.set(1, 1, 1 );
this.mesh.castShadow = true;
this.mesh.receiveShadow = true;
this.scene.add(this.mesh);
});
}
setControls() {
this.controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
this.controls.dampingFactor = 0.05;
this.controls.screenSpacePanning = false;
this.controls.enablePan = false;
this.controls.minDistance = 1;
this.controls.maxDistance = 500;
this.controls.maxPolarAngle = Math.PI;
}
setRenderer() {
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.shadowMap.enabled = true;
}
addShadowedLight(x, y, z, color, intensity) {
const directionalLight = new THREE.DirectionalLight(color, intensity);
directionalLight.position.set(x, y, z);
this.scene.add(directionalLight);
directionalLight.castShadow = false;
const d = 1;
directionalLight.shadow.camera.left = - d;
directionalLight.shadow.camera.right = d;
directionalLight.shadow.camera.top = d;
directionalLight.shadow.camera.bottom = - d;
directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 4;
directionalLight.shadow.bias = - 0.002;
}
onWindowResize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
animate() {
requestAnimationFrame(this.animate.bind(this));
this.render(this.renderer, this.scene, this.camera);
}
render() {
this.renderer.render(this.scene, this.camera);
}
}
function addFileInput(parent, id) {
const newLine = document.createElement("div");
const newInput = document.createElement("input");
const newLabel = document.createElement("label");
const newButton = document.createElement("button");
const uploadIcon = document.createElement("i");
const removeIcon = document.createElement("i");
const uploadText = document.createElement("span");
newLine.classList.add(..."w-100 mt2 mb2 flex".split(" "));
newInput.classList = ["clip"];
newLabel.classList.add(..."f6 pointer dim blue bl bt bb b--dark-blue w-90 ph3 pv2 link mr0 ml0 dib".split(" "));
newButton.classList.add(..."f6 pointer dim white bg-red ba b--dark-red ph3 pv2 w-10".split(" "));
uploadIcon.classList.add("fa", "fa-upload");
removeIcon.classList.add("fa", "fa-trash");
uploadIcon.setAttribute("aria-hidden", "true");
removeIcon.setAttribute("aria-hidden", "true");
uploadText.textContent = " Choose a file to upload";
newInput.type = "file";
newInput.name = id;
newInput.id = id;
newLabel.htmlFor = id;
newLabel.appendChild(uploadIcon);
newLabel.appendChild(uploadText);
newButton.appendChild(removeIcon);
newLine.appendChild(newInput);
newLine.appendChild(newLabel);
newLine.appendChild(newButton);
newInput.addEventListener('change', (event) => {
const fileName = event.target.value.split('\\').pop();
uploadText.textContent = fileName ? ` ${fileName}` : " Choose a file to upload";
});
newButton.addEventListener('click', (event) => {
newLine.remove();
});
parent.appendChild(newLine);
}
export {
STLViewer,
addFileInput
};