Skip to content

Commit

Permalink
Add option to export a png preview of the path
Browse files Browse the repository at this point in the history
  • Loading branch information
geekuillaume committed Apr 3, 2022
1 parent 5a6011b commit 5459b68
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 40 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"d3": "^6.7.0",
"d3-fisheye": "^2.0.1",
"eslint-config-react-app": "^6.0.0",
"file-saver": "^2.0.5",
"gcode-toolpath": "^2.2.0",
"javascript-algorithms": "0.0.5",
"jest-canvas-mock": "^2.3.1",
Expand Down
2 changes: 2 additions & 0 deletions src/common/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ const debug = false
export const log = (message) => {
if (debug) { console.log(message) }
}

export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
54 changes: 23 additions & 31 deletions src/features/exporter/Downloader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { saveAs } from 'file-saver';
import { Button, Modal, Col, Row } from 'react-bootstrap'
import DropdownOption from '../../components/DropdownOption'
import InputOption from '../../components/InputOption'
Expand All @@ -13,6 +14,7 @@ import ScaraGCodeExporter from './ScaraGCodeExporter'
import SvgExporter from './SvgExporter'
import ThetaRhoExporter from './ThetaRhoExporter'
import { Exporter, GCODE, THETARHO, SVG, SCARA } from '../../models/Exporter'
import { exportCurrentPreviewWindow } from '../preview/PreviewWindow'

const exporters = {
[GCODE]: GCodeExporter,
Expand All @@ -24,6 +26,7 @@ const exporters = {
const mapStateToProps = (state, ownProps) => {
return {
reverse: state.exporter.reverse,
pngPreview: state.exporter.pngPreview,
show: state.exporter.show,
vertices: getAllComputedVertices(state),
comments: getComments(state),
Expand Down Expand Up @@ -75,7 +78,7 @@ class Downloader extends Component {
})
}

download() {
async download() {
let exporter = new exporters[this.props.fileType](this.props)
let startTime = performance.now()
let fileName = this.props.fileName
Expand All @@ -87,7 +90,16 @@ class Downloader extends Component {
}

this.gaRecord(exporter.label)
this.downloadFile(fileName, exporter.lines.join("\n"))
saveAs(new Blob([exporter.lines.join("\n")], {
type: this.props.fileType === SVG ? 'image/svg+xml;charset=utf-8' : 'text/plain;charset=utf-8'
}), fileName);

if (this.props.pngPreview) {
const preview = await exportCurrentPreviewWindow();
if (preview) {
saveAs(preview, `${fileName.match(/(.*)\.\w+$/)[1]}.png`);
}
}
this.props.close()

let endTime = performance.now()
Expand All @@ -98,35 +110,6 @@ class Downloader extends Component {
})
}

// Helper function to take a string and make the user download a text file with that text as the
// content. I don't really understand this, but I took it from here, and it seems to work:
// https://stackoverflow.com/a/18197511
downloadFile(fileName, text) {
let link = document.createElement('a')
link.download = fileName

let fileType = 'text/plain;charset=utf-8'
if (this.props.fileType === SVG) {
fileType = 'image/svg+xml;charset=utf-8'
}
let blob = new Blob([text],{type: fileType})

// Windows Edge fix
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName)
} else {
link.href = URL.createObjectURL(blob)
if (document.createEvent) {
var event = document.createEvent('MouseEvents')
event.initEvent('click', true, true)
link.dispatchEvent(event)
} else {
link.click()
}
URL.revokeObjectURL(link.href)
}
}

render() {
return (
<div>
Expand Down Expand Up @@ -212,6 +195,15 @@ class Downloader extends Component {
index={5}
model={this.props} />
</div>
<div className="mt-2">
<CheckboxOption
onChange={this.props.onChange}
options={this.props.options}
optionKey="pngPreview"
key="pngPreview"
index={5}
model={this.props} />
</div>
</Modal.Body>

<Modal.Footer>
Expand Down
1 change: 1 addition & 0 deletions src/features/exporter/exporterSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const exporterSlice = createSlice({
pre: localStorage.getItem('export_pre') ? localStorage.getItem('export_pre') : '',
post: localStorage.getItem('export_post') ? localStorage.getItem('export_post') : '',
reverse: false,
pngPreview: localStorage.getItem('export_pngPreview') !== null ? Boolean(localStorage.getItem('png_preview')) : false,
show: false,
polarRhoMax: localStorage.getItem('export_polarRhoMax') ? localStorage.getItem('export_polarRhoMax') : 1.0,
unitsPerCircle: localStorage.getItem('export_unitsPerCircle') ? localStorage.getItem('export_unitsPerCircle') : 6.0
Expand Down
8 changes: 4 additions & 4 deletions src/features/preview/PreviewLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const PreviewLayer = (ownProps) => {

drawLayerVertices(context, props.bounds)

if (props.start || props.end || isSelected) {
if (!ownProps.exportMode && (props.start || props.end || isSelected)) {
drawStartAndEndPoints(context)
}
helper.drawSliderEndPoint(context)
Expand Down Expand Up @@ -159,12 +159,12 @@ const PreviewLayer = (ownProps) => {
const trRef = React.createRef()

React.useEffect(() => {
if (props.layer.visible && isSelected && props.layer.canChangeSize) {
if (props.layer.visible && isSelected && props.layer.canChangeSize && !ownProps.exportMode) {
// we need to attach transformer manually
trRef.current.nodes([shapeRef.current])
trRef.current.getLayer().batchDraw()
}
}, [isSelected, props.layer, props.currentLayer.canMove, shapeRef, trRef])
}, [isSelected, props.layer, props.currentLayer.canMove, shapeRef, trRef, ownProps.exportMode])

return (
<React.Fragment>
Expand Down Expand Up @@ -214,7 +214,7 @@ const PreviewLayer = (ownProps) => {
})
}}
/>}
{props.layer.visible && isSelected && props.layer.canChangeSize && (
{props.layer.visible && isSelected && props.layer.canChangeSize && !ownProps.exportMode && (
<Transformer
ref={trRef}
centeredScaling={true}
Expand Down
27 changes: 22 additions & 5 deletions src/features/preview/PreviewWindow.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React, { Component } from 'react'
import React, { Component, useCallback, useEffect } from 'react'
import { connect, ReactReduxContext, Provider } from 'react-redux'
import { Stage, Layer, Circle, Rect } from 'react-konva'
import throttle from 'lodash/throttle'
import { setPreviewSize, updatePreview } from './previewSlice'
import { updateLayer } from '../layers/layersSlice'
import { getCurrentLayer, getKonvaLayerIds, getVisibleNonEffectIds, isDragging } from '../layers/selectors'
import { roundP } from '../../common/util'
import { roundP, sleep } from '../../common/util'
import PreviewLayer from './PreviewLayer'
import PreviewConnector from './PreviewConnector'

let getPreview = null;
export const exportCurrentPreviewWindow = () => {
if (getPreview) {
return getPreview();
}
}

const mapStateToProps = (state, ownProps) => {
return {
layers: state.layers,
Expand Down Expand Up @@ -43,6 +50,8 @@ const mapDispatchToProps = (dispatch, ownProps) => {

// Contains the preview window, and any parameters for the machine.
class PreviewWindow extends Component {
state = {exportMode: false};

componentDidMount() {
const wrapper = document.getElementById('preview-wrapper')

Expand All @@ -51,7 +60,15 @@ class PreviewWindow extends Component {
setTimeout(() => {
this.visible = true
this.resize(wrapper)
}, 250)
}, 250);

getPreview = async () => {
this.setState({exportMode: true});
await sleep(0); // sleep to let react re-render the canvas before we export it
const preview = await new Promise((resolve) => this.stageRef.toCanvas().toBlob(resolve));
this.setState({exportMode: false});
return preview;
}
}

resize(wrapper) {
Expand Down Expand Up @@ -87,7 +104,7 @@ class PreviewWindow extends Component {
// which is not our usual React Component
<ReactReduxContext.Consumer>
{({store}) => (
<Stage className={visibilityClass}
<Stage ref={(ref) => this.stageRef = ref} className={visibilityClass}
scaleX={scale * reduceScale}
scaleY={scale * reduceScale}
height={height * scale}
Expand Down Expand Up @@ -123,7 +140,7 @@ class PreviewWindow extends Component {
return (
[
nextId && <PreviewConnector startId={id} endId={nextId} key={'c-' + i} />,
<PreviewLayer id={id} key={i} index={i} />
<PreviewLayer id={id} key={i} index={i} exportMode={this.state.exportMode} />
].filter(e => e !== null)
)
}).flat()}
Expand Down
3 changes: 3 additions & 0 deletions src/models/Exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const exporterOptions = {
reverse: {
title: 'Reverse path in the code',
},
pngPreview: {
title: 'Export the preview image',
},
}

export class Exporter {
Expand Down

0 comments on commit 5459b68

Please sign in to comment.