Make changes to UI

This commit is contained in:
casperlamboo 2017-12-04 15:08:29 +01:00
parent bc9f0e673e
commit 65d44db405
5 changed files with 312 additions and 248 deletions

View File

@ -5,57 +5,35 @@ import injectSheet from 'react-jss';
import MaterialUISelectField from 'material-ui/SelectField'
import MaterialUICheckbox from 'material-ui/Checkbox';
import MaterialUITextField from 'material-ui/TextField';
import { grey100, grey300, grey500 } from 'material-ui/styles/colors';
const styles = {
fieldSet: {
border: 'none',
backgroundColor: grey100,
marginTop: '20px',
'& legend': {
fontFamily: 'sans-serif',
border: `1px solid ${grey300}`,
backgroundColor: 'white',
padding: '3px 13px',
color: grey500
}
}
};
export const SettingsGroup = injectSheet(styles)(({ name, classes, children }) => (
<fieldset className={classes.fieldSet}>
<legend>{name}</legend>
{children}
</fieldset>
));
SettingsGroup.propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
name: PropTypes.string.isRequired,
children: PropTypes.node
};
const contextTypes = { state: PropTypes.object, onChange: PropTypes.func, disabled: PropTypes.bool };
export const SelectField = (props, context) => (
<MaterialUISelectField
{ ...props }
disabled={context.disabled}
value={_.get(context.state, props.name)}
onChange={(event, index, value) => context.onChange(props.name, value)}
/>
);
SelectField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
SelectField.contextTypes = contextTypes;
export const TextField = (props, context) => (
<MaterialUITextField
{ ...props }
disabled={context.disabled}
value={_.get(context.state, props.name)}
onChange={(event, value) => context.onChange(props.name, value)}
/>
);
TextField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
TextField.contextTypes = contextTypes;
export const Checkbox = (props, context) => (
<MaterialUICheckbox
{ ...props }
disabled={context.disabled}
checked={_.get(context.state, props.name)}
onCheck={(event, value) => context.onChange(props.name, value)}
/>
);
Checkbox.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
Checkbox.contextTypes = contextTypes;

View File

@ -4,17 +4,29 @@ import _ from 'lodash';
import { Tabs, Tab } from 'material-ui/Tabs';
import MenuItem from 'material-ui/MenuItem';
import injectSheet from 'react-jss';
import { SettingsGroup, SelectField, TextField, Checkbox } from './FormComponents.js';
import { grey500 } from 'material-ui/styles/colors';
import { SelectField, TextField, Checkbox } from './FormComponents.js';
import { grey900 } from 'material-ui/styles/colors';
const styles = {
textFieldRow: {
display: 'flex'
},
text: {
fontWeight: 'bold',
color: grey900
},
container: {
width: '100%',
flexGrow: 1,
overflowY: 'auto'
}
};
class Settings extends React.Component {
static childContextTypes = { state: PropTypes.object, onChange: PropTypes.func };
static childContextTypes = { state: PropTypes.object, onChange: PropTypes.func, disabled: PropTypes.bool };
static defaultProps: {
disabled: false
};
static propTypes = {
classes: PropTypes.objectOf(PropTypes.string),
onChange: PropTypes.func,
@ -24,7 +36,8 @@ class Settings extends React.Component {
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
initialSettings: PropTypes.object.isRequired
initialSettings: PropTypes.object.isRequired,
disabled: PropTypes.bool.isRequired
};
constructor(props) {
super();
@ -59,97 +72,87 @@ class Settings extends React.Component {
};
getChildContext() {
return { state: this.state, onChange: this.changeSettings };
return { state: this.state, onChange: this.changeSettings, disabled: this.props.disabled };
}
render() {
const { classes, printers, quality, material } = this.props;
const { classes, printers, quality, material, disabled } = this.props;
return (
<Tabs>
<Tab label="basic">
<div>
<div className={classes.container}>
<SelectField name="printers" floatingLabelText="Printer" fullWidth>
{Object.entries(printers).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField name="quality" floatingLabelText="Quality" fullWidth>
{Object.entries(quality).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField name="material" floatingLabelText="Material" fullWidth>
{Object.entries(material).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<h3 className={classes.text}>Printer Setup</h3>
<Tabs>
<Tab label="basic">
<div>
<SelectField name="quality" floatingLabelText="Quality" fullWidth>
{Object.entries(quality).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
</div>
</Tab>
<Tab label="advanced">
<div>
<SettingsGroup name="Printer dimensions">
<p className={classes.text}>Printer dimensions</p>
<div className={classes.textFieldRow}>
<TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" />
<TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" />
<TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" />
</div>
</SettingsGroup>
<SettingsGroup name="Nozzle">
<p className={classes.text}>Nozzle</p>
<TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" />
</SettingsGroup>
<SettingsGroup name="Bed">
<p className={classes.text}>Bed</p>
<TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" />
<Checkbox name="settings.heatedBed" label="Heated" />
</SettingsGroup>
<SettingsGroup name="Material">
<p className={classes.text}>Material</p>
<TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" />
<TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" />
</SettingsGroup>
<SettingsGroup name="Thickness">
<p className={classes.text}>Thickness</p>
<TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" />
<TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" />
<TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" />
</SettingsGroup>
<SettingsGroup name="Retraction">
<p className={classes.text}>Retraction</p>
<Checkbox name="settings.retraction.enabled" label="Enabled" />
<TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" />
<TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" />
</SettingsGroup>
<SettingsGroup name="Travel">
<p className={classes.text}>Travel</p>
<TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" />
<Checkbox name="settings.combing" label="Combing" />
</SettingsGroup>
<SettingsGroup name="Inner shell">
<p className={classes.text}>Inner shell</p>
<TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Outer shell">
<p className={classes.text}>Outer shell</p>
<TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Inner infill">
<p className={classes.text}>Inner infill</p>
<TextField name="settings.innerInfill.gridSize" fullWidth floatingLabelText="Grid size" type="number" />
<TextField name="settings.innerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.innerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Outer infill">
<p className={classes.text}>Outer infill</p>
<TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="Brim">
<p className={classes.text}>Brim</p>
<TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" />
<TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
<SettingsGroup name="First layer">
<p className={classes.text}>First layer</p>
<TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" />
<TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
</SettingsGroup>
</div>
</Tab>
</Tabs>
</div>
);
}
}

View File

@ -2,12 +2,12 @@ import _ from 'lodash';
import React from 'react';
import * as THREE from 'three';
import PropTypes from 'proptypes';
import { placeOnGround, createScene, createGcodeGeometry } from './utils.js';
import { placeOnGround, createScene, fetchProgress, slice } from './utils.js';
import injectSheet from 'react-jss';
import { sliceGeometry } from '../slicer.js';
import RaisedButton from 'material-ui/RaisedButton';
import Slider from 'material-ui/Slider';
import { grey100, grey300 } from 'material-ui/styles/colors';
import LinearProgress from 'material-ui/LinearProgress';
import { grey100, grey300, red500 } from 'material-ui/styles/colors';
import Settings from './Settings.js';
import baseSettings from '../settings/default.yml';
import printerSettings from '../settings/printer.yml';
@ -15,13 +15,16 @@ import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml';
import ReactResizeDetector from 'react-resize-detector';
const MAX_FULLSCREEN_WIDTH = 720;
const styles = {
container: {
position: 'relative',
display: 'flex',
height: '100%',
backgroundColor: grey100,
overflow: 'hidden'
overflow: 'hidden',
fontFamily: 'roboto, sans-serif'
},
controlBar: {
position: 'absolute',
@ -29,38 +32,40 @@ const styles = {
left: '10px'
},
d3View: {
flexGrow: 1
flexGrow: 1,
flexBasis: 0
},
canvas: {
position: 'absolute'
},
sliceBar: {
width: '240px',
display: 'flex',
flexDirection: 'column',
maxWidth: '380px',
boxSizing: 'border-box',
padding: '10px',
overflowY: 'auto',
backgroundColor: 'white',
borderLeft: `1px solid ${grey300}`
},
overlay: {
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
color: 'white',
top: 0,
right: 0,
bottom: 0,
left: 0,
padding: '20px',
fontFamily: 'monospace'
},
sliceActions: {
listStyleType: 'none',
paddingLeft: 0
flexShrink: 0,
},
sliceButtons: {
justifyContent: 'flex-end',
display: 'flex'
},
button: {
margin: '5px 0'
margin: '5px 0 5px 5px'
},
controlButton: {
marginRight: '2px'
},
buttonContainer: {
width: '100%',
padding: '10px'
},
error: {
color: red500
}
};
@ -99,8 +104,12 @@ class Interface extends React.Component {
const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props;
this.state = {
controlMode: 'translate',
showFullScreen: {
active: false,
settings: true
},
isSlicing: false,
sliced: false,
error: null,
printers: defaultPrinter,
quality: defaultQuality,
material: defaultMaterial,
@ -120,6 +129,10 @@ class Interface extends React.Component {
this.setState({ ...scene });
}
componentWillUnmount() {
if (this.state.editorControls) this.state.editorControls.dispose();
}
resetMesh = () => {
const { mesh, render } = this.state;
if (mesh) {
@ -132,84 +145,68 @@ class Interface extends React.Component {
}
};
reset = () => {
const { control, mesh, render, gcode, scene } = this.state;
control.enabled = true;
control.setSize(1);
control.visible = true;
mesh.visible = true;
scene.remove(gcode.linePreview);
gcode.linePreview.geometry.dispose();
this.setState({ sliced: false, gcode: null });
scaleUp = () => this.scaleMesh(0.9);
scaleDown = () => this.scaleMesh(1.0 / 0.9);
scaleMesh = (factor) => {
const { mesh, render } = this.state;
if (mesh) {
mesh.scale.multiplyScalar(factor);
mesh.updateMatrix();
placeOnGround(mesh);
render();
}
};
rotateX = () => this.rotate(new THREE.Vector3(0, 0, 1));
rotateY = () => this.rotate(new THREE.Vector3(1, 0, 0));
rotateZ = () => this.rotate(new THREE.Vector3(0, 1, 0));
rotate = (axis, angle = Math.PI / 2.0) => {
const { mesh, render } = this.state;
if (mesh) {
const quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(axis, angle);
mesh.quaternion.premultiply(quaternion);
mesh.updateMatrix();
placeOnGround(mesh);
render();
}
};
slice = async () => {
const { mesh, render, scene, control, settings } = this.state;
const { mesh, settings, isSlicing, printers, quality, material } = this.state;
const { dimensions } = settings;
const centerX = dimensions.x / 2;
const centerY = dimensions.y / 2;
if (isSlicing) return;
const geometry = mesh.geometry.clone();
mesh.updateMatrix();
this.setState({ isSlicing: true, progress: { action: '', slicing: 0, uploading: 0 }, error: null });
this.setState({ isSlicing: true, progress: { actions: [], percentage: 0 } });
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
const gcode = await sliceGeometry(settings, geometry, matrix, false, true, ({ progress }) => {
this.setState({ progress: {
actions: [...this.state.progress.actions, progress.action],
percentage: progress.done / progress.total
} });
try {
await slice(mesh, settings, printers, quality, material, progress => {
this.setState({ progress: { ...this.state.progress, ...progress } });
});
} catch (error) {
this.setState({ error: error.message });
}
this.setState({ isSlicing: false });
// TODO
// can't disable control ui still interacts with mouse input
control.enabled = false;
// hack to disable control
control.setSize(0);
control.visible = false;
mesh.visible = false;
gcode.linePreview.position.x = -centerY;
gcode.linePreview.position.z = -centerX;
scene.add(gcode.linePreview);
this.setState({ sliced: true, gcode });
render();
};
onChangeSettings = (settings) => {
this.setState(settings);
};
updateDrawRange = (event, value) => {
const { gcode, render } = this.state;
gcode.linePreview.geometry.setDrawRange(0, value);
render();
};
componentWillUnmount() {
if (this.state.editorControls) this.state.editorControls.dispose();
if (this.state.control) this.state.control.dispose();
}
componentWillUpdate(nextProps, nextState) {
const { control, box, render, setSize } = this.state;
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode);
const { box, render, setSize } = this.state;
let changed = false;
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
const { dimensions } = nextState.settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
render();
box.updateMatrix();
changed = true;
}
if (changed) render();
}
onResize = (width, height) => {
onResize3dView = (width, height) => {
window.requestAnimationFrame(() => {
const { setSize } = this.state;
const { pixelRatio } = this.props;
@ -217,35 +214,61 @@ class Interface extends React.Component {
});
};
onResizeContainer = (width) => {
this.setState({
showFullScreen: {
active: width > MAX_FULLSCREEN_WIDTH,
settings: this.state.showFullScreen.settings
}
});
};
render() {
const { classes, onCompleteActions, defaultPrinter, defaultQuality, defaultMaterial } = this.props;
const { sliced, isSlicing, progress, gcode, controlMode, settings, printers, quality, material } = this.state;
const { isSlicing, progress, gcode, settings, printers, quality, material, showFullScreen, error } = this.state;
const showSettings = showFullScreen.active || showFullScreen.settings;
const showPreview = showFullScreen.active || !showFullScreen.settings;
const percentage = progress ? (progress.uploading + progress.slicing) / 2.0 * 100.0 : 0.0;
const toggleFullScreen = () => {
this.setState({
showFullScreen: {
...this.state.showFullScreen,
settings: !this.state.showFullScreen.settings
}
});
};
return (
<div className={classes.container}>
<div className={classes.d3View}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
{<div style={{ display: showPreview ? 'inherit' : 'none' }} className={classes.d3View}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize3dView} />
<canvas className={classes.canvas} ref="canvas" />
{!sliced && <div className={classes.controlBar}>
{!showFullScreen.active && <div className={classes.buttonContainer}>
<RaisedButton fullWidth label="Edit settings" onTouchTap={toggleFullScreen}/>
</div>}
{!isSlicing && <div className={classes.controlBar}>
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} label="reset" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} label="translate" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} label="rotate" />
<RaisedButton className={classes.controlButton} disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} label="scale" />
<RaisedButton className={classes.controlButton} onTouchTap={this.scaleUp} label="scale down" />
<RaisedButton className={classes.controlButton} onTouchTap={this.scaleDown} label="scale up" />
<RaisedButton className={classes.controlButton} onTouchTap={this.rotateX} label="rotate x" />
<RaisedButton className={classes.controlButton} onTouchTap={this.rotateY} label="rotate y" />
<RaisedButton className={classes.controlButton} onTouchTap={this.rotateZ} label="rotate z" />
</div>}
</div>
{sliced && <div className={classes.controlBar}>
<Slider
axis="y"
style={{ height: '300px' }}
step={2}
min={1}
max={gcode.linePreview.geometry.getAttribute('position').count}
defaultValue={gcode.linePreview.geometry.getAttribute('position').count}
onChange={this.updateDrawRange}
/>
</div>}
{!sliced && <div className={classes.sliceBar}>
<div
className={classes.sliceBar}
style={{
display: showSettings ? 'inherit' : 'none',
...showPreview ? {} : { maxWidth: 'inherit', width: '100%' }
}}
>
{!showFullScreen.active && <RaisedButton label="Edit model" onTouchTap={toggleFullScreen}/>}
<Settings
disabled={isSlicing}
printers={printerSettings}
defaultPrinter={defaultPrinter}
quality={qualitySettings}
@ -255,20 +278,21 @@ class Interface extends React.Component {
initialSettings={settings}
onChange={this.onChangeSettings}
/>
<RaisedButton className={classes.button} fullWidth disabled={isSlicing} onTouchTap={this.slice} primary label="slice" />
</div>}
{sliced && <div className={classes.sliceBar}>
<RaisedButton className={classes.button} fullWidth onTouchTap={this.reset} primary label="slice again" />
{onCompleteActions.map(({ title, callback }, i) => (
<RaisedButton className={classes.button} key={i} fullWidth onTouchTap={() => callback({ gcode, settings, printers, quality, material })} primary label={title} />
))}
</div>}
{isSlicing && <div className={classes.overlay}>
<p>Slicing: {progress.percentage.toLocaleString(navigator.language, { style: 'percent' })}</p>
<ul className={classes.sliceActions}>
{progress.actions.map((action, i) => <li key={i}>{action}</li>)}
</ul>
</div>}
<div className={classes.sliceActions}>
{error && <p className={classes.error}>{error}</p>}
{isSlicing && <p>{progress.action}</p>}
{isSlicing && <LinearProgress mode="determinate" value={percentage} />}
<div className={classes.sliceButtons}>
<RaisedButton
label="Print"
primary
className={`${classes.button}`}
onTouchTap={this.slice}
disabled={isSlicing}
/>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,6 +1,9 @@
import * as THREE from 'three';
import 'three/examples/js/controls/EditorControls';
import 'three/examples/js/controls/TransformControls';
import printerSettings from '../settings/printer.yml';
import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml';
import { sliceGeometry } from '../slicer.js';
export function placeOnGround(mesh) {
const boundingBox = new THREE.Box3().setFromObject(mesh);
@ -18,22 +21,11 @@ export function createScene(canvas, props, state) {
const center = geometry.boundingBox.getCenter();
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z));
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
renderer.setClearColor(0xffffff, 0);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
camera.position.set(0, 400, 300);
const setSize = (width, height, pixelRatio = 1) => {
renderer.setSize(width, height);
renderer.setPixelRatio(pixelRatio);
camera.aspect = width / height;
camera.updateProjectionMatrix();
render();
};
const directionalLight = new THREE.DirectionalLight(0xd5d5d5);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
@ -45,36 +37,109 @@ export function createScene(canvas, props, state) {
placeOnGround(mesh);
scene.add(mesh);
const editorControls = new THREE.EditorControls(camera, canvas);
editorControls.focus(mesh);
const control = new THREE.TransformControls(camera, canvas);
control.setMode(controlMode);
control.setRotationSnap(THREE.Math.degToRad(45));
control.addEventListener('mouseDown', () => editorControls.enabled = false);
control.addEventListener('mouseUp', () => {
editorControls.enabled = true;
placeOnGround(mesh);
});
control.attach(mesh);
scene.add(control);
const render = () => {
control.update();
renderer.render(scene, camera);
};
control.addEventListener('change', render);
editorControls.addEventListener('change', render);
const box = new THREE.BoxHelper();
box.update(new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1).applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0))));
box.material.color.setHex(0x72bcd4);
const box = new THREE.BoxHelper(new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1).applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0))), 0x72bcd4);
scene.add(box);
const { dimensions } = settings;
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
box.updateMatrix();
return { control, editorControls, scene, mesh, camera, renderer, render, box, setSize };
const editorControls = new THREE.EditorControls(camera, canvas);
editorControls.focus(mesh);
const render = () => renderer.render(scene, camera);
editorControls.addEventListener('change', render);
const setSize = (width, height, pixelRatio = 1) => {
renderer.setSize(width, height);
renderer.setPixelRatio(pixelRatio);
camera.aspect = width / height;
camera.updateProjectionMatrix();
render();
};
let renderer;
const updateCanvas = (canvas) => {
renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
renderer.setClearColor(0xffffff, 0);
render();
};
updateCanvas(canvas);
return { editorControls, scene, mesh, camera, renderer, render, box, setSize, updateCanvas };
}
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/';
export async function slice(mesh, settings, printers, quality, material, updateProgress) {
const { dimensions } = settings;
const centerX = dimensions.x / 2;
const centerY = dimensions.y / 2;
const geometry = mesh.geometry.clone();
mesh.updateMatrix();
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
const gcode = await sliceGeometry(settings, geometry, matrix, false, false, ({ progress }) => {
updateProgress({
action: progress.action,
slicing: progress.done / progress.total
});
});
// upload G-code file to AWS S3
const { data: { reservation, id } } = await fetch(`${GCODE_SERVER_URL}/upload`, { method: 'POST' })
.then(response => response.json());
const body = new FormData();
const { fields } = reservation;
for (const key in fields) {
body.append(key, fields[key]);
}
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',
uploading: progess.loaded / progess.total
});
});
const popup = window.open(`${CONNECT_URL}?uuid=${id}`, '_blank');
if (!popup) throw new Error('popup was blocked by browser');
}

View File

@ -99,31 +99,25 @@ function gcodeToString(gcode) {
}
const MAX_SPEED = 100 * 60;
const COLOR = new THREE.Color();
function createGcodeGeometry(gcode) {
const positions = [];
const colors = [];
let lastPoint
let lastPoint = [0, 0, 0];
for (let i = 0; i < gcode.length; i ++) {
const { G, F, X, Y, Z } = gcode[i];
if (X || Y || Z) {
let color;
if (G === 0) {
color = new THREE.Color(0x00ff00);
} else if (G === 1) {
color = new THREE.Color().setHSL(F / MAX_SPEED, 0.5, 0.5);
}
if (G === 1) {
if (lastPoint) positions.push(lastPoint[0], lastPoint[1], lastPoint[2]);
positions.push(lastPoint.Y, lastPoint.Z, lastPoint.X);
positions.push(Y, Z, X);
const color = (G === 0) ? COLOR.setHex(0x00ff00) : COLOR.setHSL(F / MAX_SPEED, 0.5, 0.5);
colors.push(color.r, color.g, color.b);
colors.push(color.r, color.g, color.b);
}
lastPoint = [Y, Z, X];
lastPoint = { X, Y, Z };
}
}