mirror of
https://github.com/Doodle3D/Doodle3D-Slicer.git
synced 2024-11-22 13:37:58 +01:00
Make changes to UI
This commit is contained in:
parent
bc9f0e673e
commit
65d44db405
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user