Doodle3D-Slicer/src/interface/index.js

253 lines
8.3 KiB
JavaScript
Raw Normal View History

2017-11-11 20:23:45 +01:00
import React from 'react';
2017-11-12 16:58:59 +01:00
import * as THREE from 'three';
2017-11-12 00:11:05 +01:00
import { placeOnGround, createScene, createGcodeGeometry } from './utils.js';
2017-11-11 20:23:45 +01:00
import baseSettings from '../settings/default.yml';
import printerSettings from '../settings/printer.yml';
import materialSettings from '../settings/material.yml';
import qualitySettings from '../settings/quality.yml';
import PropTypes from 'proptypes';
import injectSheet from 'react-jss';
2017-11-12 00:46:00 +01:00
import { sliceGeometry } from '../slicer.js';
2017-11-12 16:58:59 +01:00
import RaisedButton from 'material-ui/RaisedButton';
import Paper from 'material-ui/Paper';
import Slider from 'material-ui/Slider';
2017-11-12 18:41:00 +01:00
import { Tabs, Tab } from 'material-ui/Tabs';
import SelectField from 'material-ui/SelectField'
import MenuItem from 'material-ui/MenuItem';
import { grey50 } from 'material-ui/styles/colors';
2017-11-11 20:23:45 +01:00
const styles = {
container: {
2017-11-12 18:41:00 +01:00
position: 'relative',
backgroundColor: grey50
2017-11-11 20:23:45 +01:00
},
canvas: {
position: 'absolute',
},
controlBar: {
position: 'absolute',
2017-11-12 16:58:59 +01:00
bottom: '10px',
left: '10px'
2017-11-11 20:23:45 +01:00
},
sliceBar: {
position: 'absolute',
2017-11-12 16:58:59 +01:00
top: '10px',
right: '10px',
2017-11-12 18:41:00 +01:00
width: '380px',
2017-11-12 16:58:59 +01:00
padding: '10px 20px',
2017-11-12 12:34:50 +01:00
},
overlay: {
position: 'absolute',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
color: 'white',
top: 0,
right: 0,
bottom: 0,
left: 0
},
sliceActions: {
listStyleType: 'none'
2017-11-12 18:41:00 +01:00
},
button: {
margin: '5px 0'
2017-11-11 20:23:45 +01:00
}
};
class Interface extends React.Component {
2017-11-12 01:41:05 +01:00
constructor(props) {
super(props);
this.state = {
2017-11-12 16:58:59 +01:00
controlMode: 'translate',
isSlicing: false,
sliced: false,
2017-11-12 01:41:05 +01:00
printer: props.defaultPrinter
};
}
2017-11-11 20:23:45 +01:00
componentDidMount() {
const { canvas } = this.refs;
const scene = createScene(canvas, this.props, this.state);
this.setState(scene);
}
resetMesh = () => {
const { mesh, render } = this.state;
if (mesh) {
mesh.position.set(0, 0, 0);
mesh.scale.set(1, 1, 1);
mesh.rotation.set(0, 0, 0);
mesh.updateMatrix();
placeOnGround(mesh);
render();
}
2017-11-12 01:04:26 +01:00
};
reset = () => {
const { control, mesh, render, gcode, scene } = this.state;
control.enabled = true;
2017-11-12 01:04:26 +01:00
control.visible = true;
mesh.visible = true;
scene.remove(gcode.linePreview);
gcode.linePreview.geometry.dispose();
this.setState({ sliced: false, gcode: null });
render();
};
2017-11-11 20:23:45 +01:00
2017-11-12 00:11:05 +01:00
slice = async () => {
2017-11-12 01:41:05 +01:00
const { mesh, render, scene, control, printer } = this.state;
2017-11-12 00:11:05 +01:00
const settings = {
...baseSettings,
...materialSettings.pla,
2017-11-12 01:41:05 +01:00
...printerSettings[printer]
2017-11-12 00:11:05 +01:00
};
2017-11-12 00:46:00 +01:00
const { dimensions } = settings;
const centerX = dimensions.x / 2;
const centerY = dimensions.y / 2;
const geometry = mesh.geometry.clone();
mesh.updateMatrix();
2017-11-12 12:34:50 +01:00
this.setState({ isSlicing: true, progress: { actions: [], percentage: 0 } });
2017-11-12 00:57:28 +01:00
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
2017-11-12 12:34:50 +01:00
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
} });
2017-11-12 00:11:05 +01:00
});
2017-11-12 12:34:50 +01:00
this.setState({ isSlicing: false });
2017-11-12 01:04:26 +01:00
// TODO
2017-11-12 11:53:45 +01:00
// can't disable control ui still interacts with mouse input
control.enabled = false;
2017-11-12 01:04:26 +01:00
control.visible = false;
mesh.visible = false;
2017-11-12 00:11:05 +01:00
2017-11-12 01:04:26 +01:00
gcode.linePreview.position.x = -centerY;
gcode.linePreview.position.z = -centerX;
scene.add(gcode.linePreview);
2017-11-12 00:11:05 +01:00
2017-11-12 01:04:26 +01:00
this.setState({ sliced: true, gcode });
2017-11-12 00:11:05 +01:00
render();
};
2017-11-12 16:58:59 +01:00
updateDrawRange = (event, value) => {
2017-11-12 11:28:32 +01:00
const { gcode, render } = this.state;
2017-11-12 16:58:59 +01:00
gcode.linePreview.geometry.setDrawRange(0, value);
2017-11-12 11:28:32 +01:00
render();
};
2017-11-11 20:23:45 +01:00
componentWillUnmount() {
if (this.state.editorControls) this.state.editorControls.dispose();
if (this.state.control) this.state.control.dispose();
}
componentWillUpdate(nextProps, nextState) {
const { control } = this.state;
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode);
}
render() {
2017-11-12 18:41:00 +01:00
const { width, height, classes, onCompleteActions, printers, materials, quality } = this.props;
const { sliced, isSlicing, progress, gcode, controlMode, printer } = this.state;
2017-11-12 01:15:38 +01:00
2017-11-11 20:23:45 +01:00
return (
<div style={{ width, height }} className={classes.container}>
<canvas className={classes.canvas} ref="canvas" width={width} height={height} />
2017-11-12 00:47:06 +01:00
{!sliced && <div className={classes.controlBar}>
2017-11-12 16:58:59 +01:00
<RaisedButton onTouchTap={this.resetMesh} primary label="reset" />
<RaisedButton disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} primary label="translate" />
<RaisedButton disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} primary label="rotate" />
<RaisedButton disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} primary label="scale" />
2017-11-12 00:47:06 +01:00
</div>}
2017-11-12 11:28:32 +01:00
{sliced && <div className={classes.controlBar}>
2017-11-12 16:58:59 +01:00
<Slider
axis="y"
style={{ height: '300px' }}
step={2}
min={1}
2017-11-12 11:28:32 +01:00
max={gcode.linePreview.geometry.getAttribute('position').count}
defaultValue={gcode.linePreview.geometry.getAttribute('position').count}
onChange={this.updateDrawRange}
/>
</div>}
2017-11-12 16:58:59 +01:00
{!sliced && <Paper className={classes.sliceBar}>
2017-11-12 18:41:00 +01:00
<Tabs>
<Tab label="basic settings">
<div>
<SelectField name="printer" value={printer} floatingLabelText="Printer" fullWidth>
{Object.entries(printers).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField value="medium" floatingLabelText="Quality" fullWidth>
{Object.entries(quality).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
<SelectField value="pla" floatingLabelText="Material" fullWidth>
{Object.entries(materials).map(([value, { title }]) => (
<MenuItem key={value} value={value} primaryText={title} />
))}
</SelectField>
</div>
</Tab>
<Tab label="advanced settings">
<div>
</div>
</Tab>
</Tabs>
<RaisedButton className={classes.button} fullWidth disabled={isSlicing} onTouchTap={this.slice} primary label="slice" />
2017-11-12 16:58:59 +01:00
</Paper>}
{sliced && <Paper className={classes.sliceBar}>
2017-11-12 18:41:00 +01:00
<RaisedButton className={classes.button} fullWidth onTouchTap={this.reset} primary label="slice again" />
2017-11-12 01:15:38 +01:00
{onCompleteActions.map(({ title, callback }, i) => (
2017-11-12 18:41:00 +01:00
<RaisedButton className={classes.button} key={i} fullWidth onTouchTap={() => callback(gcode.gcode)} primary label={title} />
2017-11-12 01:15:38 +01:00
))}
2017-11-12 16:58:59 +01:00
</Paper>}
2017-11-12 12:34:50 +01:00
{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>}
2017-11-11 20:23:45 +01:00
</div>
);
}
}
2017-11-12 19:12:32 +01:00
Interface.defaultProps = {
width: 720,
height: 480,
printers: printerSettings,
defaultPrinter: 'ultimaker2',
quality: qualitySettings,
defaultQuality: 'medium',
materials: materialSettings,
defaultMaterial: 'pla',
};
Interface.propTypes = {
geometry(props, propName) {
if (!(props[propName].isGeometry || props[propName].isBufferGeometry)) {
throw new Error('invalid prop, is not geometry');
}
},
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
classes: PropTypes.objectOf(PropTypes.string),
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
materials: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
onCompleteActions: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string, callback: PropTypes.func })).isRequired,
};
2017-11-11 20:23:45 +01:00
export default injectSheet(styles)(Interface);