271 lines
9.9 KiB
HTML
271 lines
9.9 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head lang="en">
|
|
<title>Generate a model with OpenSCAD Remote</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="css/custom.css">
|
|
<link rel="stylesheet" href="css/tachyons-4.12.0.min.css">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous">
|
|
</head>
|
|
|
|
<body>
|
|
<div class="mw5 mw7-ns center bg-light-gray pa3 ph5-ns shadow-2 avenir">
|
|
<h1 class="f1">OpenSCAD Remote</h1>
|
|
|
|
<form id="files-form" enctype="multipart/form-data">
|
|
<h2 class="f4">Main SCAD file</h2>
|
|
|
|
<div class="w-100 mt2 mb2 flex">
|
|
<input class="clip" type=file name="main-file" id="fileForUpload" accept=".scad">
|
|
<label class="f6 pointer dim blue ba b--dark-blue w-100 ph3 pv2 link mr0 ml0 dib" for="fileForUpload"><i class="fa fa-upload" aria-hidden="true"></i> Choose a file to upload (.scad)</label>
|
|
</div>
|
|
|
|
<div class="w-100">
|
|
<div class="w-100 bb b--black-30 pb3"></div>
|
|
<div class="pt2"></div>
|
|
</div>
|
|
|
|
<h2 class="f4">Parameters JSON file</h2>
|
|
|
|
<div class="w-100 mt2 mb2 flex">
|
|
<input class="clip" type=file name="parameters-file" id="paramFile" accept=".json">
|
|
<label class="f6 pointer dim blue ba b--dark-blue w-100 ph3 pv2 link mr0 ml0 dib" for="paramFile"><i class="fa fa-upload" aria-hidden="true"></i> Choose a file to upload (.json)</label>
|
|
</div>
|
|
|
|
<div class="w-100">
|
|
<div class="w-100 bb b--black-30 pb3"></div>
|
|
<div class="pt2"></div>
|
|
</div>
|
|
|
|
<div class="flex items-center">
|
|
<h3 class="f4 w-100">Extra files</h3>
|
|
<button type="button" id="add-file-button" class="f6 w5 h2 white bg-blue ba b--dark-blue ph3 pv2 db br-pill shadow-2 grow"><i class="fa fa-plus" aria-hidden="true"></i> Add a file</button>
|
|
</div>
|
|
|
|
|
|
<div class="flex flex-column" id="extra-files-list"></div>
|
|
|
|
<div class="w-100">
|
|
<div class="w-100 bb b--black-30 pb3"></div>
|
|
<div class="pt2"></div>
|
|
</div>
|
|
|
|
<button type="submit" class="f4 white bg-green ba b--dark-green mt2 ph4 pv3 center mw5 db shadow-2 grow" id="send-buttonbak"><i class="fa fa-paper-plane" aria-hidden="true"></i> Send</button>
|
|
</form>
|
|
</div>
|
|
<div id="result-viewer" class="mw5 mw7-ns center bg-light-gray shadow-2 avenir pt3 mt3"></div>
|
|
<script type="module">
|
|
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";
|
|
|
|
let inc = 0;
|
|
let camera, controls, cameraTarget, scene, renderer;
|
|
|
|
const add_button = document.getElementById("add-file-button")
|
|
add_button.addEventListener("click", (event) => {
|
|
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 = `extra-file-${inc}`;
|
|
newInput.id = `extra-file-${inc}`;
|
|
|
|
newLabel.htmlFor = `extra-file-${inc}`;
|
|
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();
|
|
});
|
|
|
|
document.getElementById("extra-files-list").appendChild(newLine);
|
|
inc++;
|
|
});
|
|
|
|
const send_form = document.getElementById("files-form");
|
|
send_form.addEventListener("submit", (event) => {
|
|
const url = "https://cad.interstellai.re/upload";
|
|
fetch(url, {
|
|
method: "POST",
|
|
body: new FormData(event.target)
|
|
})
|
|
.then( res => res.blob() )
|
|
.then( blob => {
|
|
addResult(blob);
|
|
});
|
|
event.preventDefault();
|
|
});
|
|
|
|
function addResult(blob) {
|
|
const container = document.getElementById("result-viewer");
|
|
|
|
const title = document.createElement("h3");
|
|
title.classList.add(..."f3 mr3 ml3 mr5-ns ml5-ns".split(" "));
|
|
title.textContent = "Result";
|
|
|
|
const dl_icon = document.createElement("i");
|
|
dl_icon.classList.add("fa", "fa-download");
|
|
dl_icon.setAttribute("aria-hidden", "true");
|
|
|
|
const dl_button = document.createElement("button");
|
|
dl_button.appendChild(dl_icon);
|
|
dl_button.appendChild(document.createTextNode(" Download"));
|
|
dl_button.classList.add(..."f4 white bg-green ba b--dark-green mt2 ph4 pv3 center mw5 db shadow-2 grow".split(" "));
|
|
dl_button.addEventListener("click", (event) => {
|
|
const file = window.URL.createObjectURL(blob);
|
|
window.location.assign(file);
|
|
});
|
|
|
|
const threejs_view = document.createElement("div");
|
|
threejs_view.classList.add(..."w-100 center h6".split(" "));
|
|
|
|
container.appendChild(title);
|
|
container.appendChild(threejs_view);
|
|
container.appendChild(dl_button);
|
|
|
|
init(threejs_view, blob);
|
|
animate();
|
|
}
|
|
|
|
function init(container, blob) {
|
|
camera = new THREE.PerspectiveCamera( 35, container.clientWidth / container.clientHeight, 1, 1000 );
|
|
camera.position.set(10, 10, 10);
|
|
|
|
// Renderer
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
|
|
renderer.shadowMap.enabled = true;
|
|
|
|
// Controls
|
|
controls = new OrbitControls( camera, renderer.domElement );
|
|
|
|
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
|
|
controls.dampingFactor = 0.05;
|
|
|
|
controls.screenSpacePanning = false;
|
|
controls.enablePan = false;
|
|
|
|
controls.minDistance = 1;
|
|
controls.maxDistance = 500;
|
|
|
|
controls.maxPolarAngle = Math.PI;
|
|
|
|
// World
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color( 0xdddddd );
|
|
//scene.fog = new THREE.Fog(0xdddddd, 10, 100);
|
|
|
|
// Ground
|
|
const plane = new THREE.Mesh(
|
|
new THREE.PlaneGeometry( 1000, 1000 ),
|
|
new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
|
|
);
|
|
plane.rotation.x = - Math.PI / 2;
|
|
plane.position.y = - 0.5;
|
|
scene.add( plane );
|
|
|
|
plane.receiveShadow = true;
|
|
|
|
const loader = new STLLoader();
|
|
blob.arrayBuffer().then(buffer => {
|
|
const geometry = loader.parse(buffer);
|
|
|
|
const material = new THREE.MeshPhongMaterial( { color: 0xff5533, specular: 0x111111, shininess: 200 } );
|
|
const mesh = new THREE.Mesh( geometry, material );
|
|
|
|
mesh.position.set(0, 0, 0);
|
|
mesh.rotation.set(0, 0, 0);
|
|
mesh.scale.set(1, 1, 1 );
|
|
|
|
mesh.castShadow = true;
|
|
mesh.receiveShadow = true;
|
|
|
|
scene.add( mesh );
|
|
});
|
|
|
|
// Lights
|
|
scene.add( new THREE.HemisphereLight( 0x443333, 0x111122 ) );
|
|
|
|
addShadowedLight( 1, 1, 1, 0xffffff, 1.35 );
|
|
addShadowedLight( 0.5, 1, - 1, 0xffffff, 1 );
|
|
|
|
container.appendChild( renderer.domElement );
|
|
|
|
// Events listeners
|
|
window.addEventListener('resize', onWindowResize);
|
|
}
|
|
|
|
function addShadowedLight(x, y, z, color, intensity) {
|
|
const directionalLight = new THREE.DirectionalLight(color, intensity);
|
|
directionalLight.position.set(x, y, z);
|
|
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;
|
|
}
|
|
|
|
function onWindowResize() {
|
|
const container = renderer.domElement.parentElement;
|
|
|
|
camera.aspect = container.clientWidth / container.clientHeight;
|
|
camera.updateProjectionMatrix();
|
|
|
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame( animate );
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
renderer.render( scene, camera );
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |