Doodle3D-Slicer/src/interface/index.js

309 lines
9.3 KiB
JavaScript
Raw Normal View History

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-12-04 17:44:08 +01:00
import { placeOnGround, createScene, fetchProgress, slice, TabTemplate } from './utils.js';
2017-11-11 20:23:45 +01:00
import injectSheet from 'react-jss';
2017-11-12 16:58:59 +01:00
import RaisedButton from 'material-ui/RaisedButton';
import Slider from 'material-ui/Slider';
2017-12-04 15:08:29 +01:00
import LinearProgress from 'material-ui/LinearProgress';
import { grey100, grey300, red500 } from 'material-ui/styles/colors';
2017-12-04 17:44:08 +01:00
import { Tabs, Tab } from 'material-ui/Tabs';
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-12-04 15:08:29 +01:00
const MAX_FULLSCREEN_WIDTH = 720;
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,
2017-12-04 15:08:29 +01:00
overflow: 'hidden',
fontFamily: 'roboto, sans-serif'
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: {
2017-12-04 15:08:29 +01:00
flexGrow: 1,
flexBasis: 0
2017-11-13 12:42:35 +01:00
},
canvas: {
position: 'absolute'
},
2017-12-04 17:44:08 +01:00
settingsBar: {
2017-12-04 15:08:29 +01:00
display: 'flex',
flexDirection: 'column',
maxWidth: '380px',
boxSizing: 'border-box',
2017-11-13 13:37:36 +01:00
padding: '10px',
2017-11-13 12:42:35 +01:00
backgroundColor: 'white',
borderLeft: `1px solid ${grey300}`
2017-11-12 12:34:50 +01:00
},
sliceActions: {
2017-12-04 15:08:29 +01:00
flexShrink: 0,
},
sliceButtons: {
justifyContent: 'flex-end',
display: 'flex'
2017-11-12 18:41:00 +01:00
},
button: {
2017-12-04 15:08:29 +01:00
margin: '5px 0 5px 5px'
2017-11-13 11:01:57 +01:00
},
controlButton: {
marginRight: '2px'
2017-12-04 15:08:29 +01:00
},
buttonContainer: {
width: '100%',
padding: '10px'
},
error: {
color: red500
2017-11-11 20:23:45 +01:00
}
};
class Interface extends React.Component {
2017-11-16 14:54:47 +01:00
static 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,
defaultSettings: PropTypes.object.isRequired,
printers: PropTypes.object.isRequired,
defaultPrinter: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
defaultQuality: PropTypes.string.isRequired,
material: PropTypes.object.isRequired,
defaultMaterial: PropTypes.string.isRequired,
pixelRatio: PropTypes.number.isRequired
};
static defaultProps = {
defaultSettings: baseSettings,
printers: printerSettings,
defaultPrinter: 'ultimaker2',
quality: qualitySettings,
defaultQuality: 'medium',
material: materialSettings,
defaultMaterial: 'pla',
pixelRatio: 1
};
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',
2017-12-04 17:44:08 +01:00
showFullScreen: false,
2017-11-12 16:58:59 +01:00
isSlicing: false,
2017-12-04 15:08:29 +01:00
error: null,
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);
2017-11-16 14:54:47 +01:00
this.setState({ ...scene });
2017-11-11 20:23:45 +01:00
}
2017-12-04 15:08:29 +01:00
componentWillUnmount() {
if (this.state.editorControls) this.state.editorControls.dispose();
}
2017-11-11 20:23:45 +01:00
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
};
2017-12-04 15:08:29 +01:00
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();
}
};
2017-11-12 01:04:26 +01:00
2017-12-04 15:08:29 +01:00
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();
}
2017-11-12 01:04:26 +01:00
};
2017-11-11 20:23:45 +01:00
2017-11-12 00:11:05 +01:00
slice = async () => {
2017-12-04 15:08:29 +01:00
const { mesh, settings, isSlicing, printers, quality, material } = this.state;
2017-11-12 00:46:00 +01:00
2017-12-04 15:08:29 +01:00
if (isSlicing) return;
2017-11-12 00:46:00 +01:00
2017-12-04 15:08:29 +01:00
this.setState({ isSlicing: true, progress: { action: '', slicing: 0, uploading: 0 }, error: null });
2017-11-12 12:34:50 +01:00
2017-12-04 15:08:29 +01:00
try {
await slice(mesh, settings, printers, quality, material, progress => {
this.setState({ progress: { ...this.state.progress, ...progress } });
});
} catch (error) {
this.setState({ error: error.message });
}
2017-11-12 00:11:05 +01:00
2017-11-12 12:34:50 +01:00
this.setState({ isSlicing: false });
2017-11-12 00:11:05 +01:00
};
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-11 20:23:45 +01:00
componentWillUpdate(nextProps, nextState) {
2017-12-04 15:08:29 +01:00
const { box, render, setSize } = this.state;
let changed = false;
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);
2017-12-04 15:08:29 +01:00
box.updateMatrix();
changed = true;
2017-11-13 02:47:53 +01:00
}
2017-12-04 15:08:29 +01:00
if (changed) render();
2017-11-11 20:23:45 +01:00
}
2017-12-04 17:44:08 +01:00
componentDidUpdate() {
const { updateCanvas } = this.state;
const { canvas } = this.refs;
if (updateCanvas && canvas) updateCanvas(canvas);
}
2017-12-04 15:08:29 +01:00
onResize3dView = (width, height) => {
2017-11-13 12:42:35 +01:00
window.requestAnimationFrame(() => {
const { setSize } = this.state;
const { pixelRatio } = this.props;
setSize(width, height, pixelRatio);
});
};
2017-12-04 15:08:29 +01:00
onResizeContainer = (width) => {
2017-12-04 17:44:08 +01:00
this.setState({ showFullScreen: width > MAX_FULLSCREEN_WIDTH });
2017-12-04 15:08:29 +01:00
};
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-12-04 15:08:29 +01:00
const { isSlicing, progress, gcode, settings, printers, quality, material, showFullScreen, error } = this.state;
const percentage = progress ? (progress.uploading + progress.slicing) / 2.0 * 100.0 : 0.0;
2017-12-04 17:44:08 +01:00
const settingsPanel = (
<div className={classes.settingsBar} style={{ ...(showFullScreen ? {} : { maxWidth: 'inherit', width: '100%', height: '100%' }) }}>
<Settings
disabled={isSlicing}
printers={printerSettings}
defaultPrinter={defaultPrinter}
quality={qualitySettings}
defaultQuality={defaultQuality}
material={materialSettings}
defaultMaterial={defaultMaterial}
initialSettings={settings}
onChange={this.onChangeSettings}
/>
<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}
/>
2017-12-04 15:08:29 +01:00
</div>
</div>
2017-11-11 20:23:45 +01:00
</div>
);
2017-12-04 17:44:08 +01:00
const d3Panel = (
<div className={classes.d3View}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize3dView} />
<canvas className={classes.canvas} ref="canvas" />
{!isSlicing && <div className={classes.controlBar}>
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} label="reset" />
<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>
);
if (showFullScreen) {
return (
<div className={classes.container}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
{d3Panel}
{settingsPanel}
</div>
);
} else {
return (
<div className={classes.container}>
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
<Tabs
style={{ width: '100%', display: 'flex', flexDirection: 'column' }}
tabItemContainerStyle={{ flexShrink: 0 }}
contentContainerStyle={{ flexGrow: 1, display: 'flex' }}
tabTemplateStyle={{ display: 'flex' }}
tabTemplate={TabTemplate}
>
<Tab label="Settings">
{settingsPanel}
</Tab>
<Tab label="Preview">
{d3Panel}
</Tab>
</Tabs>
</div>
);
}
2017-11-11 20:23:45 +01:00
}
}
export default injectSheet(styles)(Interface);