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 MaterialUISelectField from 'material-ui/SelectField'
|
||||||
import MaterialUICheckbox from 'material-ui/Checkbox';
|
import MaterialUICheckbox from 'material-ui/Checkbox';
|
||||||
import MaterialUITextField from 'material-ui/TextField';
|
import MaterialUITextField from 'material-ui/TextField';
|
||||||
import { grey100, grey300, grey500 } from 'material-ui/styles/colors';
|
|
||||||
|
|
||||||
const styles = {
|
const contextTypes = { state: PropTypes.object, onChange: PropTypes.func, disabled: PropTypes.bool };
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SelectField = (props, context) => (
|
export const SelectField = (props, context) => (
|
||||||
<MaterialUISelectField
|
<MaterialUISelectField
|
||||||
{ ...props }
|
{ ...props }
|
||||||
|
disabled={context.disabled}
|
||||||
value={_.get(context.state, props.name)}
|
value={_.get(context.state, props.name)}
|
||||||
onChange={(event, index, value) => context.onChange(props.name, value)}
|
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) => (
|
export const TextField = (props, context) => (
|
||||||
<MaterialUITextField
|
<MaterialUITextField
|
||||||
{ ...props }
|
{ ...props }
|
||||||
|
disabled={context.disabled}
|
||||||
value={_.get(context.state, props.name)}
|
value={_.get(context.state, props.name)}
|
||||||
onChange={(event, value) => context.onChange(props.name, value)}
|
onChange={(event, value) => context.onChange(props.name, value)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
TextField.contextTypes = { state: PropTypes.object, onChange: PropTypes.func };
|
TextField.contextTypes = contextTypes;
|
||||||
|
|
||||||
export const Checkbox = (props, context) => (
|
export const Checkbox = (props, context) => (
|
||||||
<MaterialUICheckbox
|
<MaterialUICheckbox
|
||||||
{ ...props }
|
{ ...props }
|
||||||
|
disabled={context.disabled}
|
||||||
checked={_.get(context.state, props.name)}
|
checked={_.get(context.state, props.name)}
|
||||||
onCheck={(event, value) => context.onChange(props.name, value)}
|
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 { Tabs, Tab } from 'material-ui/Tabs';
|
||||||
import MenuItem from 'material-ui/MenuItem';
|
import MenuItem from 'material-ui/MenuItem';
|
||||||
import injectSheet from 'react-jss';
|
import injectSheet from 'react-jss';
|
||||||
import { SettingsGroup, SelectField, TextField, Checkbox } from './FormComponents.js';
|
import { SelectField, TextField, Checkbox } from './FormComponents.js';
|
||||||
import { grey500 } from 'material-ui/styles/colors';
|
import { grey900 } from 'material-ui/styles/colors';
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
textFieldRow: {
|
textFieldRow: {
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: grey900
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
flexGrow: 1,
|
||||||
|
overflowY: 'auto'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Settings extends React.Component {
|
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 = {
|
static propTypes = {
|
||||||
classes: PropTypes.objectOf(PropTypes.string),
|
classes: PropTypes.objectOf(PropTypes.string),
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
@ -24,7 +36,8 @@ class Settings extends React.Component {
|
|||||||
defaultQuality: PropTypes.string.isRequired,
|
defaultQuality: PropTypes.string.isRequired,
|
||||||
material: PropTypes.object.isRequired,
|
material: PropTypes.object.isRequired,
|
||||||
defaultMaterial: PropTypes.string.isRequired,
|
defaultMaterial: PropTypes.string.isRequired,
|
||||||
initialSettings: PropTypes.object.isRequired
|
initialSettings: PropTypes.object.isRequired,
|
||||||
|
disabled: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super();
|
super();
|
||||||
@ -59,97 +72,87 @@ class Settings extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getChildContext() {
|
getChildContext() {
|
||||||
return { state: this.state, onChange: this.changeSettings };
|
return { state: this.state, onChange: this.changeSettings, disabled: this.props.disabled };
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes, printers, quality, material } = this.props;
|
const { classes, printers, quality, material, disabled } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs>
|
<div className={classes.container}>
|
||||||
<Tab label="basic">
|
<SelectField name="printers" floatingLabelText="Printer" fullWidth>
|
||||||
<div>
|
{Object.entries(printers).map(([value, { title }]) => (
|
||||||
<SelectField name="printers" floatingLabelText="Printer" fullWidth>
|
<MenuItem key={value} value={value} primaryText={title} />
|
||||||
{Object.entries(printers).map(([value, { title }]) => (
|
))}
|
||||||
<MenuItem key={value} value={value} primaryText={title} />
|
</SelectField>
|
||||||
))}
|
<SelectField name="material" floatingLabelText="Material" fullWidth>
|
||||||
</SelectField>
|
{Object.entries(material).map(([value, { title }]) => (
|
||||||
<SelectField name="quality" floatingLabelText="Quality" fullWidth>
|
<MenuItem key={value} value={value} primaryText={title} />
|
||||||
{Object.entries(quality).map(([value, { title }]) => (
|
))}
|
||||||
<MenuItem key={value} value={value} primaryText={title} />
|
</SelectField>
|
||||||
))}
|
<h3 className={classes.text}>Printer Setup</h3>
|
||||||
</SelectField>
|
<Tabs>
|
||||||
<SelectField name="material" floatingLabelText="Material" fullWidth>
|
<Tab label="basic">
|
||||||
{Object.entries(material).map(([value, { title }]) => (
|
<div>
|
||||||
<MenuItem key={value} value={value} primaryText={title} />
|
<SelectField name="quality" floatingLabelText="Quality" fullWidth>
|
||||||
))}
|
{Object.entries(quality).map(([value, { title }]) => (
|
||||||
</SelectField>
|
<MenuItem key={value} value={value} primaryText={title} />
|
||||||
</div>
|
))}
|
||||||
</Tab>
|
</SelectField>
|
||||||
<Tab label="advanced">
|
</div>
|
||||||
<div>
|
</Tab>
|
||||||
<SettingsGroup name="Printer dimensions">
|
<Tab label="advanced">
|
||||||
|
<div>
|
||||||
|
<p className={classes.text}>Printer dimensions</p>
|
||||||
<div className={classes.textFieldRow}>
|
<div className={classes.textFieldRow}>
|
||||||
<TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" />
|
<TextField name="settings.dimensions.x" fullWidth floatingLabelText="X" type="number" />
|
||||||
<TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" />
|
<TextField name="settings.dimensions.y" fullWidth floatingLabelText="Y" type="number" />
|
||||||
<TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" />
|
<TextField name="settings.dimensions.z" fullWidth floatingLabelText="Z" type="number" />
|
||||||
</div>
|
</div>
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Nozzle</p>
|
||||||
<SettingsGroup name="Nozzle">
|
|
||||||
<TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" />
|
<TextField name="settings.nozzleDiameter" fullWidth floatingLabelText="Diameter" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Bed</p>
|
||||||
<SettingsGroup name="Bed">
|
|
||||||
<TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" />
|
<TextField name="settings.bedTemperature" fullWidth floatingLabelText="Temperature" type="number" />
|
||||||
<Checkbox name="settings.heatedBed" label="Heated" />
|
<Checkbox name="settings.heatedBed" label="Heated" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Material</p>
|
||||||
<SettingsGroup name="Material">
|
|
||||||
<TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" />
|
<TextField name="settings.filamentThickness" fullWidth floatingLabelText="Thickness" type="number" />
|
||||||
<TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" />
|
<TextField name="settings.temperature" fullWidth floatingLabelText="Temperature" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Thickness</p>
|
||||||
<SettingsGroup name="Thickness">
|
|
||||||
<TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" />
|
<TextField name="settings.thickness.top" fullWidth floatingLabelText="top" type="number" />
|
||||||
<TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" />
|
<TextField name="settings.thickness.bottom" fullWidth floatingLabelText="bottom" type="number" />
|
||||||
<TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" />
|
<TextField name="settings.thickness.shell" fullWidth floatingLabelText="shell" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Retraction</p>
|
||||||
<SettingsGroup name="Retraction">
|
|
||||||
<Checkbox name="settings.retraction.enabled" label="Enabled" />
|
<Checkbox name="settings.retraction.enabled" label="Enabled" />
|
||||||
<TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" />
|
<TextField name="settings.retraction.amount" fullWidth floatingLabelText="Amount" type="number" />
|
||||||
<TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.retraction.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" />
|
<TextField name="settings.retraction.minDistance" fullWidth floatingLabelText="Min distance" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Travel</p>
|
||||||
<SettingsGroup name="Travel">
|
|
||||||
<TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.travel.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<Checkbox name="settings.combing" label="Combing" />
|
<Checkbox name="settings.combing" label="Combing" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Inner shell</p>
|
||||||
<SettingsGroup name="Inner shell">
|
|
||||||
<TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.innerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.innerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Outer shell</p>
|
||||||
<SettingsGroup name="Outer shell">
|
|
||||||
<TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.outerShell.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.outerShell.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Inner infill</p>
|
||||||
<SettingsGroup name="Inner infill">
|
|
||||||
<TextField name="settings.innerInfill.gridSize" fullWidth floatingLabelText="Grid size" type="number" />
|
<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.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.innerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.innerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Outer infill</p>
|
||||||
<SettingsGroup name="Outer infill">
|
|
||||||
<TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.outerInfill.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.outerInfill.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>Brim</p>
|
||||||
<SettingsGroup name="Brim">
|
|
||||||
<TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" />
|
<TextField name="settings.brim.offset" fullWidth floatingLabelText="Offset" type="number" />
|
||||||
<TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.brim.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.brim.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
<p className={classes.text}>First layer</p>
|
||||||
<SettingsGroup name="First layer">
|
|
||||||
<TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" />
|
<TextField name="settings.firstLayer.speed" fullWidth floatingLabelText="Speed" type="number" />
|
||||||
<TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
<TextField name="settings.firstLayer.flowRate" fullWidth floatingLabelText="Flow rate" type="number" />
|
||||||
</SettingsGroup>
|
</div>
|
||||||
</div>
|
</Tab>
|
||||||
</Tab>
|
</Tabs>
|
||||||
</Tabs>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ import _ from 'lodash';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import PropTypes from 'proptypes';
|
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 injectSheet from 'react-jss';
|
||||||
import { sliceGeometry } from '../slicer.js';
|
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import Slider from 'material-ui/Slider';
|
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 Settings from './Settings.js';
|
||||||
import baseSettings from '../settings/default.yml';
|
import baseSettings from '../settings/default.yml';
|
||||||
import printerSettings from '../settings/printer.yml';
|
import printerSettings from '../settings/printer.yml';
|
||||||
@ -15,13 +15,16 @@ import materialSettings from '../settings/material.yml';
|
|||||||
import qualitySettings from '../settings/quality.yml';
|
import qualitySettings from '../settings/quality.yml';
|
||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
|
|
||||||
|
const MAX_FULLSCREEN_WIDTH = 720;
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
container: {
|
container: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
backgroundColor: grey100,
|
backgroundColor: grey100,
|
||||||
overflow: 'hidden'
|
overflow: 'hidden',
|
||||||
|
fontFamily: 'roboto, sans-serif'
|
||||||
},
|
},
|
||||||
controlBar: {
|
controlBar: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -29,38 +32,40 @@ const styles = {
|
|||||||
left: '10px'
|
left: '10px'
|
||||||
},
|
},
|
||||||
d3View: {
|
d3View: {
|
||||||
flexGrow: 1
|
flexGrow: 1,
|
||||||
|
flexBasis: 0
|
||||||
},
|
},
|
||||||
canvas: {
|
canvas: {
|
||||||
position: 'absolute'
|
position: 'absolute'
|
||||||
},
|
},
|
||||||
sliceBar: {
|
sliceBar: {
|
||||||
width: '240px',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
maxWidth: '380px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
overflowY: 'auto',
|
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
borderLeft: `1px solid ${grey300}`
|
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: {
|
sliceActions: {
|
||||||
listStyleType: 'none',
|
flexShrink: 0,
|
||||||
paddingLeft: 0
|
},
|
||||||
|
sliceButtons: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
display: 'flex'
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
margin: '5px 0'
|
margin: '5px 0 5px 5px'
|
||||||
},
|
},
|
||||||
controlButton: {
|
controlButton: {
|
||||||
marginRight: '2px'
|
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;
|
const { defaultPrinter, defaultQuality, defaultMaterial, printers, quality, material, defaultSettings } = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
controlMode: 'translate',
|
controlMode: 'translate',
|
||||||
|
showFullScreen: {
|
||||||
|
active: false,
|
||||||
|
settings: true
|
||||||
|
},
|
||||||
isSlicing: false,
|
isSlicing: false,
|
||||||
sliced: false,
|
error: null,
|
||||||
printers: defaultPrinter,
|
printers: defaultPrinter,
|
||||||
quality: defaultQuality,
|
quality: defaultQuality,
|
||||||
material: defaultMaterial,
|
material: defaultMaterial,
|
||||||
@ -120,6 +129,10 @@ class Interface extends React.Component {
|
|||||||
this.setState({ ...scene });
|
this.setState({ ...scene });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.state.editorControls) this.state.editorControls.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
resetMesh = () => {
|
resetMesh = () => {
|
||||||
const { mesh, render } = this.state;
|
const { mesh, render } = this.state;
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
@ -132,84 +145,68 @@ class Interface extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
reset = () => {
|
scaleUp = () => this.scaleMesh(0.9);
|
||||||
const { control, mesh, render, gcode, scene } = this.state;
|
scaleDown = () => this.scaleMesh(1.0 / 0.9);
|
||||||
control.enabled = true;
|
scaleMesh = (factor) => {
|
||||||
control.setSize(1);
|
const { mesh, render } = this.state;
|
||||||
control.visible = true;
|
if (mesh) {
|
||||||
mesh.visible = true;
|
mesh.scale.multiplyScalar(factor);
|
||||||
|
mesh.updateMatrix();
|
||||||
|
placeOnGround(mesh);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
scene.remove(gcode.linePreview);
|
rotateX = () => this.rotate(new THREE.Vector3(0, 0, 1));
|
||||||
gcode.linePreview.geometry.dispose();
|
rotateY = () => this.rotate(new THREE.Vector3(1, 0, 0));
|
||||||
|
rotateZ = () => this.rotate(new THREE.Vector3(0, 1, 0));
|
||||||
this.setState({ sliced: false, gcode: null });
|
rotate = (axis, angle = Math.PI / 2.0) => {
|
||||||
render();
|
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 () => {
|
slice = async () => {
|
||||||
const { mesh, render, scene, control, settings } = this.state;
|
const { mesh, settings, isSlicing, printers, quality, material } = this.state;
|
||||||
|
|
||||||
const { dimensions } = settings;
|
if (isSlicing) return;
|
||||||
const centerX = dimensions.x / 2;
|
|
||||||
const centerY = dimensions.y / 2;
|
|
||||||
|
|
||||||
const geometry = mesh.geometry.clone();
|
this.setState({ isSlicing: true, progress: { action: '', slicing: 0, uploading: 0 }, error: null });
|
||||||
mesh.updateMatrix();
|
|
||||||
|
|
||||||
this.setState({ isSlicing: true, progress: { actions: [], percentage: 0 } });
|
try {
|
||||||
|
await slice(mesh, settings, printers, quality, material, progress => {
|
||||||
const matrix = new THREE.Matrix4().makeTranslation(centerY, 0, centerX).multiply(mesh.matrix);
|
this.setState({ progress: { ...this.state.progress, ...progress } });
|
||||||
const gcode = await sliceGeometry(settings, geometry, matrix, false, true, ({ progress }) => {
|
});
|
||||||
this.setState({ progress: {
|
} catch (error) {
|
||||||
actions: [...this.state.progress.actions, progress.action],
|
this.setState({ error: error.message });
|
||||||
percentage: progress.done / progress.total
|
}
|
||||||
} });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({ isSlicing: false });
|
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) => {
|
onChangeSettings = (settings) => {
|
||||||
this.setState(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) {
|
componentWillUpdate(nextProps, nextState) {
|
||||||
const { control, box, render, setSize } = this.state;
|
const { box, render, setSize } = this.state;
|
||||||
if (control && nextState.controlMode !== this.state.controlMode) control.setMode(nextState.controlMode);
|
let changed = false;
|
||||||
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
|
if (box && nextState.settings.dimensions !== this.state.settings.dimensions) {
|
||||||
const { dimensions } = nextState.settings;
|
const { dimensions } = nextState.settings;
|
||||||
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
|
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(() => {
|
window.requestAnimationFrame(() => {
|
||||||
const { setSize } = this.state;
|
const { setSize } = this.state;
|
||||||
const { pixelRatio } = this.props;
|
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() {
|
render() {
|
||||||
const { classes, onCompleteActions, defaultPrinter, defaultQuality, defaultMaterial } = this.props;
|
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 (
|
return (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<div className={classes.d3View}>
|
<ReactResizeDetector handleWidth handleHeight onResize={this.onResizeContainer} />
|
||||||
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
|
{<div style={{ display: showPreview ? 'inherit' : 'none' }} className={classes.d3View}>
|
||||||
|
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize3dView} />
|
||||||
<canvas className={classes.canvas} ref="canvas" />
|
<canvas className={classes.canvas} ref="canvas" />
|
||||||
{!sliced && <div className={classes.controlBar}>
|
{!showFullScreen.active && <div className={classes.buttonContainer}>
|
||||||
<RaisedButton className={classes.controlButton} onTouchTap={this.resetMesh} label="reset" />
|
<RaisedButton fullWidth label="Edit settings" onTouchTap={toggleFullScreen}/>
|
||||||
<RaisedButton className={classes.controlButton} disabled={controlMode === 'translate'} onTouchTap={() => this.setState({ controlMode: 'translate' })} label="translate" />
|
</div>}
|
||||||
<RaisedButton className={classes.controlButton} disabled={controlMode === 'rotate'} onTouchTap={() => this.setState({ controlMode: 'rotate' })} label="rotate" />
|
{!isSlicing && <div className={classes.controlBar}>
|
||||||
<RaisedButton className={classes.controlButton} disabled={controlMode === 'scale'} onTouchTap={() => this.setState({ controlMode: 'scale' })} label="scale" />
|
<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>}
|
||||||
</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>}
|
</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
|
<Settings
|
||||||
|
disabled={isSlicing}
|
||||||
printers={printerSettings}
|
printers={printerSettings}
|
||||||
defaultPrinter={defaultPrinter}
|
defaultPrinter={defaultPrinter}
|
||||||
quality={qualitySettings}
|
quality={qualitySettings}
|
||||||
@ -255,20 +278,21 @@ class Interface extends React.Component {
|
|||||||
initialSettings={settings}
|
initialSettings={settings}
|
||||||
onChange={this.onChangeSettings}
|
onChange={this.onChangeSettings}
|
||||||
/>
|
/>
|
||||||
<RaisedButton className={classes.button} fullWidth disabled={isSlicing} onTouchTap={this.slice} primary label="slice" />
|
<div className={classes.sliceActions}>
|
||||||
</div>}
|
{error && <p className={classes.error}>{error}</p>}
|
||||||
{sliced && <div className={classes.sliceBar}>
|
{isSlicing && <p>{progress.action}</p>}
|
||||||
<RaisedButton className={classes.button} fullWidth onTouchTap={this.reset} primary label="slice again" />
|
{isSlicing && <LinearProgress mode="determinate" value={percentage} />}
|
||||||
{onCompleteActions.map(({ title, callback }, i) => (
|
<div className={classes.sliceButtons}>
|
||||||
<RaisedButton className={classes.button} key={i} fullWidth onTouchTap={() => callback({ gcode, settings, printers, quality, material })} primary label={title} />
|
<RaisedButton
|
||||||
))}
|
label="Print"
|
||||||
</div>}
|
primary
|
||||||
{isSlicing && <div className={classes.overlay}>
|
className={`${classes.button}`}
|
||||||
<p>Slicing: {progress.percentage.toLocaleString(navigator.language, { style: 'percent' })}</p>
|
onTouchTap={this.slice}
|
||||||
<ul className={classes.sliceActions}>
|
disabled={isSlicing}
|
||||||
{progress.actions.map((action, i) => <li key={i}>{action}</li>)}
|
/>
|
||||||
</ul>
|
</div>
|
||||||
</div>}
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import 'three/examples/js/controls/EditorControls';
|
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) {
|
export function placeOnGround(mesh) {
|
||||||
const boundingBox = new THREE.Box3().setFromObject(mesh);
|
const boundingBox = new THREE.Box3().setFromObject(mesh);
|
||||||
@ -18,22 +21,11 @@ export function createScene(canvas, props, state) {
|
|||||||
const center = geometry.boundingBox.getCenter();
|
const center = geometry.boundingBox.getCenter();
|
||||||
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z));
|
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 scene = new THREE.Scene();
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
|
const camera = new THREE.PerspectiveCamera(50, 1, 1, 10000);
|
||||||
camera.position.set(0, 400, 300);
|
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);
|
const directionalLight = new THREE.DirectionalLight(0xd5d5d5);
|
||||||
directionalLight.position.set(1, 1, 1);
|
directionalLight.position.set(1, 1, 1);
|
||||||
scene.add(directionalLight);
|
scene.add(directionalLight);
|
||||||
@ -45,36 +37,109 @@ export function createScene(canvas, props, state) {
|
|||||||
placeOnGround(mesh);
|
placeOnGround(mesh);
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
|
|
||||||
const editorControls = new THREE.EditorControls(camera, canvas);
|
const box = new THREE.BoxHelper(new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1).applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0))), 0x72bcd4);
|
||||||
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);
|
|
||||||
scene.add(box);
|
scene.add(box);
|
||||||
|
|
||||||
const { dimensions } = settings;
|
const { dimensions } = settings;
|
||||||
box.scale.set(dimensions.y, dimensions.z, dimensions.x);
|
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 MAX_SPEED = 100 * 60;
|
||||||
|
const COLOR = new THREE.Color();
|
||||||
function createGcodeGeometry(gcode) {
|
function createGcodeGeometry(gcode) {
|
||||||
const positions = [];
|
const positions = [];
|
||||||
const colors = [];
|
const colors = [];
|
||||||
|
|
||||||
let lastPoint
|
let lastPoint = [0, 0, 0];
|
||||||
for (let i = 0; i < gcode.length; i ++) {
|
for (let i = 0; i < gcode.length; i ++) {
|
||||||
const { G, F, X, Y, Z } = gcode[i];
|
const { G, F, X, Y, Z } = gcode[i];
|
||||||
|
|
||||||
if (X || Y || Z) {
|
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 (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);
|
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);
|
||||||
colors.push(color.r, color.g, color.b);
|
colors.push(color.r, color.g, color.b);
|
||||||
}
|
}
|
||||||
|
lastPoint = { X, Y, Z };
|
||||||
lastPoint = [Y, Z, X];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user