2017-11-13 02:47:53 +01:00
|
|
|
import _ from 'lodash';
|
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-11 20:23:45 +01:00
|
|
|
import PropTypes from 'proptypes';
|
2017-11-13 02:09:39 +01:00
|
|
|
import { placeOnGround, createScene, createGcodeGeometry } from './utils.js';
|
2017-11-11 20:23:45 +01:00
|
|
|
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 Slider from 'material-ui/Slider';
|
2017-11-13 12:42:35 +01:00
|
|
|
import { grey100, grey300 } from 'material-ui/styles/colors';
|
2017-11-13 02:09:39 +01:00
|
|
|
import Settings from './Settings.js';
|
|
|
|
import baseSettings from '../settings/default.yml';
|
2017-11-13 02:47:53 +01:00
|
|
|
import printerSettings from '../settings/printer.yml';
|
|
|
|
import materialSettings from '../settings/material.yml';
|
|
|
|
import qualitySettings from '../settings/quality.yml';
|
2017-11-13 12:42:35 +01:00
|
|
|
import ReactResizeDetector from 'react-resize-detector';
|
2017-11-13 02:47:53 +01:00
|
|
|
|
2017-11-11 20:23:45 +01:00
|
|
|
const styles = {
|
|
|
|
container: {
|
2017-11-12 18:41:00 +01:00
|
|
|
position: 'relative',
|
2017-11-13 12:42:35 +01:00
|
|
|
display: 'flex',
|
|
|
|
height: '100%',
|
|
|
|
backgroundColor: grey100,
|
|
|
|
overflow: 'hidden'
|
2017-11-11 20:23:45 +01:00
|
|
|
},
|
|
|
|
controlBar: {
|
|
|
|
position: 'absolute',
|
2017-11-12 16:58:59 +01:00
|
|
|
bottom: '10px',
|
|
|
|
left: '10px'
|
2017-11-11 20:23:45 +01:00
|
|
|
},
|
2017-11-13 12:42:35 +01:00
|
|
|
d3View: {
|
|
|
|
flexGrow: 1
|
|
|
|
},
|
|
|
|
canvas: {
|
|
|
|
position: 'absolute'
|
|
|
|
},
|
2017-11-11 20:23:45 +01:00
|
|
|
sliceBar: {
|
2017-11-13 12:42:35 +01:00
|
|
|
width: '240px',
|
2017-11-13 13:37:36 +01:00
|
|
|
padding: '10px',
|
2017-11-13 12:42:35 +01:00
|
|
|
overflowY: 'auto',
|
|
|
|
backgroundColor: 'white',
|
|
|
|
borderLeft: `1px solid ${grey300}`
|
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,
|
2017-11-14 11:22:24 +01:00
|
|
|
left: 0,
|
|
|
|
padding: '20px'
|
2017-11-12 12:34:50 +01:00
|
|
|
},
|
|
|
|
sliceActions: {
|
2017-11-14 11:22:24 +01:00
|
|
|
listStyleType: 'none',
|
|
|
|
paddingLeft: 0
|
2017-11-12 18:41:00 +01:00
|
|
|
},
|
|
|
|
button: {
|
|
|
|
margin: '5px 0'
|
2017-11-13 11:01:57 +01:00
|
|
|
},
|
|
|
|
controlButton: {
|
|
|
|
marginRight: '2px'
|
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);
|
2017-11-13 10:40:58 +01:00
|
|
|
const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props;
|
2017-11-12 01:41:05 +01:00
|
|
|
this.state = {
|
2017-11-12 16:58:59 +01:00
|
|
|
controlMode: 'translate',
|
|
|
|
isSlicing: false,
|
2017-11-13 02:47:53 +01:00
|
|
|
sliced: false,
|
2017-11-13 15:47:19 +01:00
|
|
|
printers: defaultPrinter,
|
2017-11-13 15:32:23 +01:00
|
|
|
quality: defaultQuality,
|
|
|
|
material: defaultMaterial,
|
2017-11-13 10:40:58 +01:00
|
|
|
settings: _.merge(
|
|
|
|
{},
|
|
|
|
defaultSettings,
|
|
|
|
printers[defaultPrinter],
|
|
|
|
quality[defaultQuality],
|
|
|
|
material[defaultMaterial]
|
|
|
|
)
|
2017-11-12 01:41:05 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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;
|
2017-11-12 12:58:19 +01:00
|
|
|
control.enabled = true;
|
2017-11-13 12:44:03 +01:00
|
|
|
control.setSize(1);
|
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-13 02:47:53 +01:00
|
|
|
const { mesh, render, scene, control, settings } = this.state;
|
2017-11-12 00:46:00 +01:00
|
|
|
|
2017-11-13 02:47:53 +01:00
|
|
|
const { dimensions } = settings;
|
2017-11-12 00:46:00 +01:00
|
|
|
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-13 02:47:53 +01:00
|
|
|
const gcode = await sliceGeometry(settings, geometry, matrix, false, true, ({ progress }) => {
|
2017-11-12 12:34:50 +01:00
|
|
|
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
|
2017-11-12 12:58:19 +01:00
|
|
|
control.enabled = false;
|
2017-11-13 12:44:03 +01:00
|
|
|
// hack to disable control
|
|
|
|
control.setSize(0);
|
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-13 02:47:53 +01:00
|
|
|
onChangeSettings = (settings) => {
|
2017-11-13 15:12:59 +01:00
|
|
|
this.setState(settings);
|
2017-11-13 02:47:53 +01:00
|
|
|
};
|
|
|
|
|
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) {
|
2017-11-13 11:15:00 +01:00
|
|
|
const { control, box, render, setSize } = this.state;
|
2017-11-11 20:23:45 +01:00
|
|
|
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode);
|
2017-11-13 02:47:53 +01:00
|
|
|
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
|
|
|
|
const { dimensions } = nextState.settings;
|
|
|
|
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
|
|
|
|
render();
|
|
|
|
}
|
2017-11-11 20:23:45 +01:00
|
|
|
}
|
|
|
|
|
2017-11-13 12:42:35 +01:00
|
|
|
onResize = (width, height) => {
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
const { setSize } = this.state;
|
|
|
|
const { pixelRatio } = this.props;
|
|
|
|
setSize(width, height, pixelRatio);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-11-11 20:23:45 +01:00
|
|
|
render() {
|
2017-11-14 11:21:58 +01:00
|
|
|
const { classes, onCompleteActions, defaultPrinter, defaultQuality, defaultMaterial } = this.props;
|
2017-11-13 15:47:19 +01:00
|
|
|
const { sliced, isSlicing, progress, gcode, controlMode, settings, printers, quality, material } = this.state;
|
2017-11-12 01:15:38 +01:00
|
|
|
|
2017-11-11 20:23:45 +01:00
|
|
|
return (
|
2017-11-13 12:42:35 +01:00
|
|
|
<div className={classes.container}>
|
|
|
|
<div className={classes.d3View}>
|
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
|
2017-11-14 11:21:58 +01:00
|
|
|
<canvas className={classes.canvas} ref="canvas" />
|
2017-11-13 12:42:35 +01:00
|
|
|
{!sliced && <div className={classes.controlBar}>
|
2017-11-13 11:01:57 +01:00
|
|
|
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} primary label="reset" />
|
|
|
|
<RaisedButton className={classes.controlButton} disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} primary label="translate" />
|
|
|
|
<RaisedButton className={classes.controlButton} disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} primary label="rotate" />
|
|
|
|
<RaisedButton className={classes.controlButton} disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} primary label="scale" />
|
2017-11-13 12:42:35 +01:00
|
|
|
</div>}
|
|
|
|
</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-13 12:42:35 +01:00
|
|
|
{!sliced && <div className={classes.sliceBar}>
|
2017-11-13 02:47:53 +01:00
|
|
|
<Settings
|
|
|
|
printers={printerSettings}
|
2017-11-13 10:40:58 +01:00
|
|
|
defaultPrinter={defaultPrinter}
|
2017-11-13 02:47:53 +01:00
|
|
|
quality={qualitySettings}
|
2017-11-13 10:40:58 +01:00
|
|
|
defaultQuality={defaultQuality}
|
2017-11-13 02:47:53 +01:00
|
|
|
material={materialSettings}
|
2017-11-13 10:40:58 +01:00
|
|
|
defaultMaterial={defaultMaterial}
|
2017-11-13 10:43:42 +01:00
|
|
|
initialSettings={settings}
|
2017-11-13 02:47:53 +01:00
|
|
|
onChange={this.onChangeSettings}
|
|
|
|
/>
|
2017-11-12 18:41:00 +01:00
|
|
|
<RaisedButton className={classes.button} fullWidth disabled={isSlicing} onTouchTap={this.slice} primary label="slice" />
|
2017-11-13 12:42:35 +01:00
|
|
|
</div>}
|
|
|
|
{sliced && <div 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-13 15:47:19 +01:00
|
|
|
<RaisedButton className={classes.button} key={i} fullWidth onTouchTap={() => callback({ gcode, settings, printers, quality, material })} primary label={title} />
|
2017-11-12 01:15:38 +01:00
|
|
|
))}
|
2017-11-13 12:42:35 +01:00
|
|
|
</div>}
|
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.propTypes = {
|
|
|
|
geometry(props, propName) {
|
|
|
|
if (!(props[propName].isGeometry || props[propName].isBufferGeometry)) {
|
|
|
|
throw new Error('invalid prop, is not geometry');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
classes: PropTypes.objectOf(PropTypes.string),
|
|
|
|
onCompleteActions: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string, callback: PropTypes.func })).isRequired,
|
2017-11-13 10:40:58 +01:00
|
|
|
defaultSettings: PropTypes.object.isRequired,
|
|
|
|
printers: PropTypes.object.isRequired,
|
|
|
|
defaultPrinter: PropTypes.string.isRequired,
|
|
|
|
quality: PropTypes.object.isRequired,
|
|
|
|
defaultQuality: PropTypes.string.isRequired,
|
|
|
|
material: PropTypes.object.isRequired,
|
2017-11-13 11:15:00 +01:00
|
|
|
defaultMaterial: PropTypes.string.isRequired,
|
|
|
|
pixelRatio: PropTypes.number.isRequired
|
2017-11-13 10:40:58 +01:00
|
|
|
};
|
|
|
|
Interface.defaultProps = {
|
|
|
|
defaultSettings: baseSettings,
|
|
|
|
printers: printerSettings,
|
|
|
|
defaultPrinter: 'ultimaker2',
|
|
|
|
quality: qualitySettings,
|
|
|
|
defaultQuality: 'medium',
|
|
|
|
material: materialSettings,
|
2017-11-13 11:15:00 +01:00
|
|
|
defaultMaterial: 'pla',
|
|
|
|
pixelRatio: 1
|
2017-11-12 19:12:32 +01:00
|
|
|
};
|
2017-11-11 20:23:45 +01:00
|
|
|
|
|
|
|
export default injectSheet(styles)(Interface);
|