2017-11-11 20:23:45 +01:00
|
|
|
import * as THREE from 'three';
|
2017-12-06 11:54:09 +01:00
|
|
|
import { Box3 } from 'three/src/math/Box3.js';
|
|
|
|
import { Matrix4 } from 'three/src/math/Matrix4.js';
|
|
|
|
import { Scene } from 'three/src/scenes/Scene.js';
|
|
|
|
import { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera.js';
|
|
|
|
import { AmbientLight } from 'three/src/lights/AmbientLight.js';
|
|
|
|
import { DirectionalLight } from 'three/src/lights/DirectionalLight.js';
|
|
|
|
import { MeshPhongMaterial } from 'three/src/materials/MeshPhongMaterial.js';
|
|
|
|
import { BoxGeometry } from 'three/src/geometries/BoxGeometry.js';
|
|
|
|
import { Mesh } from 'three/src/objects/Mesh.js';
|
|
|
|
import { BoxHelper } from 'three/src/helpers/BoxHelper.js';
|
|
|
|
import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer.js';
|
|
|
|
import { DoubleSide } from 'three/src/constants.js';
|
2017-11-11 20:23:45 +01:00
|
|
|
import 'three/examples/js/controls/EditorControls';
|
2017-12-04 15:08:29 +01:00
|
|
|
import printerSettings from '../settings/printer.yml';
|
|
|
|
import materialSettings from '../settings/material.yml';
|
|
|
|
import qualitySettings from '../settings/quality.yml';
|
2017-12-19 12:38:58 +01:00
|
|
|
import { sliceGeometry } from '../slicer.js';
|
2017-12-04 17:44:08 +01:00
|
|
|
import React from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
2017-12-18 16:37:03 +01:00
|
|
|
import fileSaver from 'file-saver';
|
2017-11-11 20:23:45 +01:00
|
|
|
|
|
|
|
export function placeOnGround(mesh) {
|
2017-12-06 11:54:09 +01:00
|
|
|
const boundingBox = new Box3().setFromObject(mesh);
|
2017-11-11 20:23:45 +01:00
|
|
|
|
|
|
|
mesh.position.y -= boundingBox.min.y;
|
|
|
|
mesh.updateMatrix();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createScene(canvas, props, state) {
|
2017-12-19 12:38:58 +01:00
|
|
|
const { pixelRatio, mesh: { geometry } } = props;
|
2017-12-18 16:37:03 +01:00
|
|
|
const { settings } = state;
|
2017-11-12 00:46:12 +01:00
|
|
|
|
|
|
|
// center geometry
|
2017-11-11 20:23:45 +01:00
|
|
|
geometry.computeBoundingBox();
|
2017-11-16 14:54:55 +01:00
|
|
|
const center = geometry.boundingBox.getCenter();
|
2017-12-06 11:54:09 +01:00
|
|
|
geometry.applyMatrix(new Matrix4().makeTranslation(-center.x, -center.y, -center.z));
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const scene = new Scene();
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const camera = new PerspectiveCamera(50, 1, 1, 10000);
|
2017-11-11 20:23:45 +01:00
|
|
|
camera.position.set(0, 400, 300);
|
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const directionalLightA = new DirectionalLight(0xa2a2a2);
|
2017-12-05 11:10:31 +01:00
|
|
|
directionalLightA.position.set(1, 1, 1);
|
|
|
|
scene.add(directionalLightA);
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const directionalLightB = new DirectionalLight(0xa2a2a2);
|
2017-12-05 11:10:31 +01:00
|
|
|
directionalLightB.position.set(-1, 1, -1);
|
|
|
|
scene.add(directionalLightB);
|
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const light = new AmbientLight(0x656565);
|
2017-11-11 20:23:45 +01:00
|
|
|
scene.add(light);
|
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const material = new MeshPhongMaterial({ color: 0x2194ce, side: DoubleSide, specular: 0xc5c5c5, shininess: 5 });
|
|
|
|
const mesh = new Mesh(geometry, material);
|
2017-11-11 20:23:45 +01:00
|
|
|
placeOnGround(mesh);
|
|
|
|
scene.add(mesh);
|
|
|
|
|
2017-12-06 11:54:09 +01:00
|
|
|
const box = new BoxHelper(new Mesh(new BoxGeometry(1, 1, 1).applyMatrix(new Matrix4().makeTranslation(0, 0.5, 0))), 0x72bcd4);
|
2017-12-04 15:08:29 +01:00
|
|
|
scene.add(box);
|
|
|
|
|
|
|
|
const { dimensions } = settings;
|
|
|
|
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
|
|
|
|
box.updateMatrix();
|
|
|
|
|
|
|
|
const render = () => renderer.render(scene, camera);
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-04 15:08:29 +01:00
|
|
|
const setSize = (width, height, pixelRatio = 1) => {
|
|
|
|
renderer.setSize(width, height);
|
|
|
|
renderer.setPixelRatio(pixelRatio);
|
|
|
|
camera.aspect = width / height;
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
render();
|
|
|
|
};
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-04 17:44:08 +01:00
|
|
|
let editorControls;
|
2017-12-04 15:08:29 +01:00
|
|
|
let renderer;
|
|
|
|
const updateCanvas = (canvas) => {
|
2017-12-04 17:44:08 +01:00
|
|
|
if (!renderer || renderer.domElement !== canvas) {
|
|
|
|
if (renderer) renderer.dispose();
|
2017-12-06 11:54:09 +01:00
|
|
|
renderer = new WebGLRenderer({ canvas, alpha: true, antialias: true });
|
2017-12-04 17:44:08 +01:00
|
|
|
renderer.setClearColor(0xffffff, 0);
|
|
|
|
}
|
|
|
|
if (!editorControls || editorControls.domElement !== canvas) {
|
|
|
|
if (editorControls) editorControls.dispose();
|
|
|
|
editorControls = new THREE.EditorControls(camera, canvas);
|
|
|
|
editorControls.focus(mesh);
|
|
|
|
editorControls.addEventListener('change', render);
|
|
|
|
}
|
|
|
|
|
2017-12-04 15:08:29 +01:00
|
|
|
render();
|
2017-11-11 20:23:45 +01:00
|
|
|
};
|
2017-12-04 15:08:29 +01:00
|
|
|
updateCanvas(canvas);
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-05 11:10:38 +01:00
|
|
|
const focus = () => editorControls.focus(mesh);
|
|
|
|
|
|
|
|
return { editorControls, scene, mesh, camera, renderer, render, box, setSize, updateCanvas, focus };
|
2017-12-04 15:08:29 +01:00
|
|
|
}
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-04 15:08:29 +01:00
|
|
|
export function fetchProgress(url, { method = 'get', headers = {}, body = {} } = {}, onProgress) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.open(method, url);
|
|
|
|
if (headers) {
|
|
|
|
for (const key in headers) {
|
|
|
|
const header = headers[key];
|
|
|
|
xhr.setRequestHeader(key, header);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
xhr.onload = event => resolve(event.target.responseText);
|
|
|
|
xhr.onerror = reject;
|
|
|
|
if (xhr.upload && onProgress) xhr.upload.onprogress = onProgress;
|
|
|
|
xhr.send(body);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const GCODE_SERVER_URL = 'https://gcodeserver.doodle3d.com';
|
|
|
|
const CONNECT_URL = 'http://connect.doodle3d.com/';
|
2017-11-11 20:23:45 +01:00
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
export async function slice(target, name, mesh, settings, printers, quality, material, updateProgress) {
|
2017-12-06 11:54:09 +01:00
|
|
|
if (!printers) throw new Error('Please select a printer');
|
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
let steps;
|
|
|
|
let currentStep = 0;
|
|
|
|
switch (target) {
|
|
|
|
case 'DOWNLOAD':
|
|
|
|
steps = 1;
|
|
|
|
break;
|
|
|
|
case 'WIFI':
|
|
|
|
steps = 2;
|
|
|
|
break;
|
|
|
|
default:
|
2018-01-15 13:47:16 +01:00
|
|
|
throw new Error('unknown target');
|
2017-12-24 17:18:33 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-11-13 02:47:53 +01:00
|
|
|
const { dimensions } = settings;
|
2017-12-04 15:08:29 +01:00
|
|
|
const centerX = dimensions.x / 2;
|
|
|
|
const centerY = dimensions.y / 2;
|
|
|
|
|
2017-12-19 12:38:58 +01:00
|
|
|
const matrix = new Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
|
2017-12-19 13:42:48 +01:00
|
|
|
const { gcode } = await sliceGeometry(settings, mesh.geometry, mesh.material, matrix, false, false, ({ progress }) => {
|
2017-12-04 15:08:29 +01:00
|
|
|
updateProgress({
|
|
|
|
action: progress.action,
|
2017-12-24 17:18:33 +01:00
|
|
|
percentage: currentStep / steps + progress.done / progress.total / steps
|
2017-12-04 15:08:29 +01:00
|
|
|
});
|
|
|
|
});
|
2017-12-24 17:18:33 +01:00
|
|
|
currentStep ++;
|
2017-12-04 15:08:29 +01:00
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
switch (target) {
|
|
|
|
case 'DOWNLOAD': {
|
|
|
|
const blob = new File([gcode], `${name}.gcode`, { type: 'text/plain;charset=utf-8' });
|
|
|
|
fileSaver.saveAs(blob);
|
|
|
|
break;
|
|
|
|
}
|
2017-12-18 16:37:03 +01:00
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
case 'WIFI': {
|
|
|
|
// upload G-code file to AWS S3
|
|
|
|
const { data: { reservation, id } } = await fetch(`${GCODE_SERVER_URL}/upload`, { method: 'POST' })
|
|
|
|
.then(response => response.json());
|
2017-12-04 15:08:29 +01:00
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
const body = new FormData();
|
|
|
|
const { fields } = reservation;
|
|
|
|
for (const key in fields) {
|
|
|
|
body.append(key, fields[key]);
|
|
|
|
}
|
2017-12-04 15:08:29 +01:00
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
const file = ';' + JSON.stringify({
|
|
|
|
name: `${name}.gcode`,
|
|
|
|
...settings,
|
|
|
|
printer: {
|
|
|
|
type: printers,
|
|
|
|
title: printerSettings[printers].title
|
|
|
|
},
|
|
|
|
material: {
|
|
|
|
type: material,
|
|
|
|
title: materialSettings[material].title
|
|
|
|
},
|
|
|
|
quality: {
|
|
|
|
type: quality,
|
|
|
|
title: qualitySettings[quality].title
|
|
|
|
}
|
|
|
|
}).trim() + '\n' + gcode;
|
|
|
|
body.append('file', file);
|
|
|
|
|
|
|
|
await fetchProgress(reservation.url, { method: 'POST', body }, (progess) => {
|
|
|
|
updateProgress({
|
|
|
|
action: 'Uploading',
|
|
|
|
percentage: currentStep / steps + progess.loaded / progess.total / steps
|
|
|
|
});
|
|
|
|
});
|
|
|
|
currentStep ++;
|
|
|
|
|
|
|
|
const popup = window.open(`${CONNECT_URL}?uuid=${id}`, '_blank');
|
|
|
|
if (!popup) throw new Error('popup was blocked by browser');
|
2017-12-04 15:08:29 +01:00
|
|
|
}
|
|
|
|
|
2017-12-24 17:18:33 +01:00
|
|
|
default:
|
2018-01-15 13:47:16 +01:00
|
|
|
throw new Error('unknown target');
|
2017-12-24 17:18:33 +01:00
|
|
|
break;
|
|
|
|
}
|
2017-11-11 20:23:45 +01:00
|
|
|
}
|
2017-12-04 17:44:08 +01:00
|
|
|
|
2017-12-18 16:37:03 +01:00
|
|
|
export const TabTemplate = ({ children, selected, style }) => {
|
|
|
|
const templateStyle = {
|
|
|
|
width: '100%',
|
|
|
|
position: 'relative',
|
|
|
|
textAlign: 'initial',
|
|
|
|
...style,
|
|
|
|
...(selected ? {} : {
|
|
|
|
height: 0,
|
|
|
|
width: 0,
|
|
|
|
overflow: 'hidden'
|
|
|
|
})
|
|
|
|
};
|
2017-12-04 17:44:08 +01:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div style={templateStyle}>
|
|
|
|
{children}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
TabTemplate.propTypes = {
|
|
|
|
children: PropTypes.node,
|
|
|
|
selected: PropTypes.bool,
|
|
|
|
style: PropTypes.object,
|
|
|
|
};
|