diff --git a/EzGraverCli/EzGraverCli.pro b/EzGraverCli/EzGraverCli.pro index eae45be..549d2a7 100644 --- a/EzGraverCli/EzGraverCli.pro +++ b/EzGraverCli/EzGraverCli.pro @@ -1,7 +1,7 @@ include(../common.pri) QT += core -QT += gui +QT += serialport TARGET = EzGraverCli CONFIG += console diff --git a/EzGraverCli/main.cpp b/EzGraverCli/main.cpp index aba1688..4a04b3b 100644 --- a/EzGraverCli/main.cpp +++ b/EzGraverCli/main.cpp @@ -49,7 +49,7 @@ void uploadImage(std::shared_ptr& engraver, QStringList const& argumen std::cout << "erasing EEPROM\n"; engraver->erase(); engraver->awaitTransmission(); - QThread::sleep(6); + QThread::msleep(EzGraver::EraseTimeMs); std::cout << "uploading image to EEPROM\n"; engraver->uploadImage(image); diff --git a/EzGraverCore/EzGraverCore.pro b/EzGraverCore/EzGraverCore.pro index aeaf7d5..7357b4c 100644 --- a/EzGraverCore/EzGraverCore.pro +++ b/EzGraverCore/EzGraverCore.pro @@ -1,6 +1,6 @@ include(../common.pri) -QT += gui +QT += core QT += serialport TARGET = EzGraverCore diff --git a/EzGraverCore/ezgraver.cpp b/EzGraverCore/ezgraver.cpp index 7530042..2a9f718 100644 --- a/EzGraverCore/ezgraver.cpp +++ b/EzGraverCore/ezgraver.cpp @@ -77,11 +77,10 @@ void EzGraver::erase() { _transmit(QByteArray{8, '\xFE'}); } -#include -void EzGraver::uploadImage(QImage const& originalImage) { +int EzGraver::uploadImage(QImage const& originalImage) { qDebug() << "converting image to bitmap"; QImage image{originalImage - .scaled(512, 512) + .scaled(ImageWidth, ImageHeight) .mirrored() .convertToFormat(QImage::Format_Mono)}; image.invertPixels(); @@ -89,26 +88,40 @@ void EzGraver::uploadImage(QImage const& originalImage) { QByteArray bytes{}; QBuffer buffer{&bytes}; image.save(&buffer, "BMP"); - uploadImage(bytes); + return uploadImage(bytes); } -void EzGraver::uploadImage(QByteArray const& image) { +int EzGraver::uploadImage(QByteArray const& image) { qDebug() << "uploading image"; - _transmit(image); + // Data is chunked in order to get at least some progress updates + _transmit(image, 8192); + return image.size(); } void EzGraver::awaitTransmission(int msecs) { _serial->waitForBytesWritten(msecs); } +std::shared_ptr EzGraver::serialPort() { + return _serial; +} + void EzGraver::_transmit(unsigned char const& data) { _transmit(QByteArray{1, static_cast(data)}); } void EzGraver::_transmit(QByteArray const& data) { - QString hex{data.count() < 10 ? data.toHex() : ""}; - qDebug() << "transmitting" << data.length() << "bytes:" << hex; + qDebug() << "transmitting" << data.size() << "bytes:" << data.toHex(); _serial->write(data); + _serial->flush(); +} + +void EzGraver::_transmit(QByteArray const& data, int chunkSize) { + qDebug() << "transmitting" << data.size() << "bytes in chunks of size" << chunkSize; + for(int i{0}; i < data.size(); i += chunkSize) { + _serial->write(data.mid(i, chunkSize)); + _serial->flush(); + } } EzGraver::~EzGraver() { diff --git a/EzGraverCore/ezgraver.h b/EzGraverCore/ezgraver.h index 51763fc..fe75516 100644 --- a/EzGraverCore/ezgraver.h +++ b/EzGraverCore/ezgraver.h @@ -5,18 +5,25 @@ #include #include +#include +#include #include -QT_BEGIN_NAMESPACE -class QSerialPort; -QT_END_NAMESPACE - /*! * Allows accessing a NEJE engraver using the serial port it was instantiated with. * The connection is closed as soon as the object is destroyed. */ struct EZGRAVERCORESHARED_EXPORT EzGraver { + /*! The time required to erase the EEPROM in milliseconds. */ + static int const EraseTimeMs{6000}; + + /*! The image width */ + static int const ImageWidth{512}; + + /*! The image height */ + static int const ImageHeight{512}; + /*! * Creates an instance and connects to the given \a portName. * @@ -84,16 +91,18 @@ struct EZGRAVERCORESHARED_EXPORT EzGraver { * mirrored and converted to a monochrome bitmap. * * \param image The image to upload to the EEPROM for engraving. + * \return The number of bytes being sent to the device. */ - void uploadImage(QImage const& image); + int uploadImage(QImage const& image); /*! * Uploads any given \a image byte array to the EEPROM. It has to be a monochrome * bitmap of the dimensions 512x512. Every white pixel is being engraved. * * \param image The image byte array to upload to the EEPROM. + * \return The number of bytes being sent to the device. */ - void uploadImage(QByteArray const& image); + int uploadImage(QByteArray const& image); /*! * Waits until the current serial port buffer is fully written to the device. @@ -102,6 +111,13 @@ struct EZGRAVERCORESHARED_EXPORT EzGraver { */ void awaitTransmission(int msecs=-1); + /*! + * Gets the serialport used by the EzGraver instance. + * + * \return The serial port used. + */ + std::shared_ptr serialPort(); + EzGraver() = delete; virtual ~EzGraver(); @@ -111,6 +127,7 @@ struct EZGRAVERCORESHARED_EXPORT EzGraver { void _transmit(unsigned char const& data); void _transmit(QByteArray const& data); + void _transmit(QByteArray const& data, int chunkSize); void _setBurnTime(unsigned char const& burnTime); }; diff --git a/EzGraverUi/EzGraverUi.pro b/EzGraverUi/EzGraverUi.pro index 11c6a75..d27867a 100644 --- a/EzGraverUi/EzGraverUi.pro +++ b/EzGraverUi/EzGraverUi.pro @@ -2,6 +2,7 @@ include(../common.pri) QT += core QT += gui +QT += serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/EzGraverUi/imagelabel.cpp b/EzGraverUi/imagelabel.cpp index 32b995d..3386073 100644 --- a/EzGraverUi/imagelabel.cpp +++ b/EzGraverUi/imagelabel.cpp @@ -2,6 +2,8 @@ #include +#include "ezgraver.h" + ImageLabel::ImageLabel(QWidget* parent) : ClickLabel{parent}, _image{}, _flags{Qt::DiffuseDither} {} ImageLabel::~ImageLabel() {} @@ -32,7 +34,7 @@ void ImageLabel::updateDisplayedImage() { } // Draw white background, otherwise transparency is converted to black. - QImage image{QSize{512, 512}, QImage::Format_ARGB32}; + QImage image{QSize{EzGraver::ImageWidth, EzGraver::ImageHeight}, QImage::Format_ARGB32}; image.fill(QColor{Qt::white}); QPainter painter{&image}; painter.drawImage(0, 0, _image.scaled(image.size())); @@ -43,3 +45,9 @@ void ImageLabel::updateDisplayedImage() { bool ImageLabel::imageLoaded() const { return !_image.isNull(); } + +void ImageLabel::setImageDimensions(QSize const& dimensions) { + auto span = this->lineWidth()*2; + setMinimumWidth(dimensions.width() + span); + setMinimumHeight(dimensions.height() + span); +} diff --git a/EzGraverUi/imagelabel.h b/EzGraverUi/imagelabel.h index e54edbb..376efda 100644 --- a/EzGraverUi/imagelabel.h +++ b/EzGraverUi/imagelabel.h @@ -59,6 +59,14 @@ class ImageLabel : public ClickLabel { * \return Returns \c true if an image is loaded. */ bool imageLoaded() const; + + /*! + * Sets the image dimensions. This enforces minimum dimensions of the + * component with respect to the border width. + * + * \param dimensions The image dimensions. + */ + void setImageDimensions(QSize const& dimensions); signals: /*! * Fired as soon as the image has been changed. diff --git a/EzGraverUi/mainwindow.cpp b/EzGraverUi/mainwindow.cpp index 2c21c3b..542f3ac 100644 --- a/EzGraverUi/mainwindow.cpp +++ b/EzGraverUi/mainwindow.cpp @@ -7,29 +7,31 @@ #include #include #include +#include #include -#include MainWindow::MainWindow(QWidget* parent) : QMainWindow{parent}, _ui{new Ui::MainWindow}, - _portTimer{}, _image{}, _ezGraver{}, _connected{false} { + _portTimer{}, _statusTimer{}, _image{}, _ezGraver{}, _bytesWrittenProcessor{[](qint64){}}, _connected{false} { _ui->setupUi(this); setAcceptDrops(true); connect(&_portTimer, &QTimer::timeout, this, &MainWindow::updatePorts); - _portTimer.start(1000); + _portTimer.start(PortUpdateDelay); - initBindings(); - initConversionFlags(); - setConnected(false); + _initBindings(); + _initConversionFlags(); + _setConnected(false); + + _ui->image->setImageDimensions(QSize{EzGraver::ImageWidth, EzGraver::ImageHeight}); } MainWindow::~MainWindow() { delete _ui; } -void MainWindow::initBindings() { +void MainWindow::_initBindings() { connect(_ui->burnTime, &QSlider::valueChanged, [this](int const& v) { _ui->burnTimeLabel->setText(QString::number(v)); }); connect(this, &MainWindow::connectedChanged, _ui->ports, &QComboBox::setDisabled); @@ -55,14 +57,14 @@ void MainWindow::initBindings() { connect(_ui->image, &ImageLabel::imageLoadedChanged, uploadEnabled); } -void MainWindow::initConversionFlags() { +void MainWindow::_initConversionFlags() { _ui->conversionFlags->addItem("DiffuseDither", Qt::DiffuseDither); _ui->conversionFlags->addItem("OrderedDither", Qt::OrderedDither); _ui->conversionFlags->addItem("ThresholdDither", Qt::ThresholdDither); _ui->conversionFlags->setCurrentIndex(0); } -void MainWindow::printVerbose(QString const& verbose) { +void MainWindow::_printVerbose(QString const& verbose) { _ui->verbose->appendPlainText(verbose); } @@ -79,12 +81,12 @@ void MainWindow::updatePorts() { } } -void MainWindow::loadImage(QString const& fileName) { - printVerbose(QString{"loading image: %1"}.arg(fileName)); +void MainWindow::_loadImage(QString const& fileName) { + _printVerbose(QString{"loading image: %1"}.arg(fileName)); QImage image{}; if(!image.load(fileName)) { - printVerbose("failed to load image"); + _printVerbose("failed to load image"); return; } @@ -95,24 +97,40 @@ bool MainWindow::connected() const { return _connected; } -void MainWindow::setConnected(bool connected) { +void MainWindow::_setConnected(bool connected) { _connected = connected; emit connectedChanged(connected); } +void MainWindow::bytesWritten(qint64 bytes) { + _bytesWrittenProcessor(bytes); +} + +void MainWindow::updateProgress(qint64 bytes) { + qDebug() << "Bytes written:" << bytes; + auto progress = _ui->progress->value() + bytes; + _ui->progress->setValue(progress); + if(progress >= _ui->progress->maximum()) { + _printVerbose("upload completed"); + _bytesWrittenProcessor = [](qint64){}; + } +} + void MainWindow::on_connect_clicked() { try { - printVerbose(QString{"connecting to port %1"}.arg(_ui->ports->currentText())); + _printVerbose(QString{"connecting to port %1"}.arg(_ui->ports->currentText())); _ezGraver = EzGraver::create(_ui->ports->currentText()); - printVerbose("connection established successfully"); - setConnected(true); + _printVerbose("connection established successfully"); + _setConnected(true); + + connect(_ezGraver->serialPort().get(), &QSerialPort::bytesWritten, this, &MainWindow::bytesWritten); } catch(std::runtime_error const& e) { - printVerbose(QString{"Error: %1"}.arg(e.what())); + _printVerbose(QString{"Error: %1"}.arg(e.what())); } } void MainWindow::on_home_clicked() { - printVerbose("moving to home"); + _printVerbose("moving to home"); _ezGraver->home(); } @@ -125,7 +143,7 @@ void MainWindow::on_left_clicked() { } void MainWindow::on_center_clicked() { - printVerbose("moving to center"); + _printVerbose("moving to center"); _ezGraver->center(); } @@ -138,49 +156,69 @@ void MainWindow::on_down_clicked() { } void MainWindow::on_upload_clicked() { + _printVerbose("erasing EEPROM"); + _ezGraver->erase(); + QImage image{_ui->image->pixmap()->toImage()}; + QTimer* eraseProgressTimer{new QTimer{this}}; + _ui->progress->setValue(0); + _ui->progress->setMaximum(EzGraver::EraseTimeMs); - printVerbose("erasing EEPROM"); - _ezGraver->erase(); + auto eraseProgress = std::bind(&MainWindow::_eraseProgressed, this, eraseProgressTimer, image); + connect(eraseProgressTimer, &QTimer::timeout, eraseProgress); + eraseProgressTimer->start(EraseProgressDelay); +} - QTimer::singleShot(6000, [this, image] { - printVerbose("uploading image to EEPROM"); - _ezGraver->uploadImage(image); - printVerbose("upload completed"); - }); +void MainWindow::_eraseProgressed(QTimer* eraseProgressTimer, QImage const& image) { + auto value = _ui->progress->value() + EraseProgressDelay; + _ui->progress->setValue(value); + if(value < EzGraver::EraseTimeMs) { + return; + } + eraseProgressTimer->stop(); + + _uploadImage(image); +} + +void MainWindow::_uploadImage(QImage const& image) { + _bytesWrittenProcessor = std::bind(&MainWindow::updateProgress, this, std::placeholders::_1); + _printVerbose("uploading image to EEPROM"); + auto bytes = _ezGraver->uploadImage(image); + _ui->progress->setValue(0); + _ui->progress->setMaximum(bytes); } void MainWindow::on_preview_clicked() { - printVerbose("drawing preview"); + _printVerbose("drawing preview"); _ezGraver->preview(); } void MainWindow::on_start_clicked() { - printVerbose(QString{"starting engrave process with burn time %1"}.arg(_ui->burnTime->value())); + _printVerbose(QString{"starting engrave process with burn time %1"}.arg(_ui->burnTime->value())); _ezGraver->start(_ui->burnTime->value()); } void MainWindow::on_pause_clicked() { - printVerbose("pausing engrave process"); + _printVerbose("pausing engrave process"); _ezGraver->pause(); } void MainWindow::on_reset_clicked() { - printVerbose("resetting engraver"); + _printVerbose("resetting engraver"); _ezGraver->reset(); } void MainWindow::on_disconnect_clicked() { - printVerbose("disconnecting"); - setConnected(false); + _printVerbose("disconnecting"); + _setConnected(false); _ezGraver.reset(); - printVerbose("disconnected"); + _printVerbose("disconnected"); } void MainWindow::on_image_clicked() { auto fileName = QFileDialog::getOpenFileName(this, "Open Image", "", "Images (*.png *.jpeg *.jpg *.bmp)"); if(!fileName.isNull()) { - loadImage(fileName); + _loadImage(fileName); } } @@ -192,6 +230,6 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event) { void MainWindow::dropEvent(QDropEvent* event) { QString fileName{event->mimeData()->urls()[0].toLocalFile()}; - loadImage(fileName); + _loadImage(fileName); } diff --git a/EzGraverUi/mainwindow.h b/EzGraverUi/mainwindow.h index 70253ce..7e88bec 100644 --- a/EzGraverUi/mainwindow.h +++ b/EzGraverUi/mainwindow.h @@ -5,6 +5,7 @@ #include #include +#include #include "ezgraver.h" @@ -41,25 +42,36 @@ private slots: void on_image_clicked(); void updatePorts(); + void bytesWritten(qint64 bytes); + void updateProgress(qint64 bytes); protected: void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); private: + /*! The delay between each port list update. */ + static int const PortUpdateDelay{1000}; + /*! The delay between each progress update while erasing the EEPROM. */ + static int const EraseProgressDelay{500}; + Ui::MainWindow* _ui; QTimer _portTimer; + QTimer _statusTimer; QImage _image; std::shared_ptr _ezGraver; + std::function _bytesWrittenProcessor; bool _connected; - void initBindings(); - void initConversionFlags(); + void _initBindings(); + void _initConversionFlags(); - void setConnected(bool connected); - void printVerbose(QString const& verbose); - void loadImage(QString const& fileName); + void _setConnected(bool connected); + void _printVerbose(QString const& verbose); + void _loadImage(QString const& fileName); + void _eraseProgressed(QTimer* eraseProgressTimer, QImage const& image); + void _uploadImage(QImage const& image); }; #endif // MAINWINDOW_H diff --git a/EzGraverUi/mainwindow.ui b/EzGraverUi/mainwindow.ui index ebd6bb9..5cd18a9 100644 --- a/EzGraverUi/mainwindow.ui +++ b/EzGraverUi/mainwindow.ui @@ -158,6 +158,13 @@ + + + + 0 + + + @@ -408,14 +415,6 @@ - - - TopToolBarArea - - - false - - diff --git a/README.md b/README.md index 2c49407..9124497 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,12 @@ # About EzGraver is an open source software allowing users to use with laser engravers by NEJE. It supports Linux, OSX and Windows. It provides both a command line interface and a graphical user interface. The latest release is available on the [releases page](https://github.com/camrein/EzGraver/releases/latest). -![](screenshot.png) +![EzGraver Screenshot](screenshot.png) # Before Running -On all platforms (Linux, OSX and Windows), it is required that the proper drivers are installed. If that's not the case, the engraver will not be detected and not listed in the dropdown. Additionally, windows requires the installation of [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) too. Otherwise, errors like *VCRUNTIME140.dll* or *MSVCP140.dll* could not be found will be shown. +On all platforms (Linux, OSX and Windows), it is required that the proper drivers are installed. If that's not the case, the engraver will not be detected and not listed in the dropdown. There were reports (at least for Windows) that the [Arduino](https://www.arduino.cc/en/Main/Software) drivers are working. + +Additionally, Windows requires the installation of [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) too. Otherwise, errors like *VCRUNTIME140.dll* or *MSVCP140.dll* could not be found will be shown. # Command Line Interface Beside the graphical user interface, EzGraver provides a pure command line interface too. diff --git a/screenshot.png b/screenshot.png index ab1e7dc..b49e47d 100644 Binary files a/screenshot.png and b/screenshot.png differ