File indexing completed on 2024-05-12 04:41:10

0001 /* AtCore KDE Libary for 3D Printers
0002     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0003     SPDX-FileCopyrightText: 2017-2018, 2020 Chris Rizzitello <rizzitello@kde.org>
0004     SPDX-FileCopyrightText: 2017-2018 Patrick José Pereira <patrickjp@kde.org>
0005     SPDX-FileCopyrightText: 2018 Tomaz Canabrava <tcanabrava@kde.org>
0006     SPDX-FileCopyrightText: 2018 Leandro Santiago <leandrosansilva@gmail.com>
0007 */
0008 
0009 #include <QCommandLineOption>
0010 #include <QCommandLineParser>
0011 #include <QLoggingCategory>
0012 #include <QRegularExpression>
0013 #include <QTextStream>
0014 #include <QTime>
0015 
0016 #include "printthread.h"
0017 
0018 Q_LOGGING_CATEGORY(PRINT_THREAD, "org.kde.atelier.core.printThread")
0019 /**
0020  * @brief The PrintThreadPrivate class
0021  */
0022 class PrintThread::PrintThreadPrivate
0023 {
0024 public:
0025     AtCore *core = nullptr;              //!<@param core: Pointer to AtCore
0026     QTextStream *gcodestream = nullptr;  //!<@param gcodestream: Steam the job is read from
0027     float printProgress = 0;             //!<@param printProgress: Progress of the print job
0028     qint64 totalSize = 0;                //!<@param totalSize: total file size
0029     qint64 stillSize = 0;                //!<@param stillSize: remaining file
0030     QString cline;                       //!<@param cline: current line
0031     AtCore::STATES state = AtCore::IDLE; //!<@param state: printer state
0032     QFile *file = nullptr;               //!<@param file: gcode File to stream from
0033     QList<QCommandLineOption> options = {{QCommandLineOption(QStringLiteral("pause"))},
0034                                          {QCommandLineOption(QStringLiteral("extruder temperature"))},
0035                                          {QCommandLineOption(QStringLiteral("bed temperature"))},
0036                                          {QCommandLineOption(QStringLiteral("print speed"))},
0037                                          {QCommandLineOption(QStringLiteral("fan speed"))},
0038                                          {QCommandLineOption(QStringLiteral("flow rate"))},
0039                                          {QCommandLineOption(QStringLiteral("message"))},
0040                                          {QCommandLineOption(QStringLiteral("command"))}}; //!<@param options: injectable commands.
0041 };
0042 
0043 PrintThread::PrintThread(AtCore *parent, const QString &fileName)
0044     : d(new PrintThreadPrivate)
0045 {
0046     d->core = parent;
0047     d->state = d->core->state();
0048     d->file = new QFile(fileName);
0049     d->file->open(QFile::ReadOnly);
0050     d->totalSize = d->file->bytesAvailable();
0051     d->stillSize = d->totalSize;
0052     d->gcodestream = new QTextStream(d->file);
0053 }
0054 
0055 PrintThread::~PrintThread()
0056 {
0057     delete d;
0058 }
0059 
0060 void PrintThread::start()
0061 {
0062     // we only want to do this when printing
0063     connect(d->core->firmwarePlugin(), &IFirmware::readyForCommand, this, &PrintThread::processJob, Qt::QueuedConnection);
0064     connect(this, &PrintThread::nextCommand, d->core, &AtCore::pushCommand, Qt::QueuedConnection);
0065     connect(this, &PrintThread::stateChanged, d->core, &AtCore::setState, Qt::QueuedConnection);
0066     connect(d->core, &AtCore::stateChanged, this, &PrintThread::setState, Qt::QueuedConnection);
0067     connect(this, &PrintThread::finished, this, &PrintThread::deleteLater);
0068     // force a command if the printer doesn't send "wait" when idle
0069     processJob();
0070 }
0071 
0072 void PrintThread::processJob()
0073 {
0074     if (d->gcodestream->atEnd()) {
0075         endPrint();
0076     }
0077 
0078     switch (d->state) {
0079     case AtCore::STARTPRINT:
0080     case AtCore::IDLE:
0081     case AtCore::BUSY:
0082         setState(AtCore::BUSY);
0083         nextLine();
0084         while (d->cline.isEmpty() && !d->gcodestream->atEnd()) {
0085             nextLine();
0086         }
0087         if (!d->cline.isEmpty() && d->core->state() != AtCore::PAUSE) {
0088             qCDebug(PRINT_THREAD) << "cline:" << d->cline;
0089             Q_EMIT nextCommand(d->cline);
0090         }
0091         break;
0092 
0093     case AtCore::ERRORSTATE:
0094         qCDebug(PRINT_THREAD) << "Error State";
0095         break;
0096 
0097     case AtCore::STOP: {
0098         endPrint();
0099         break;
0100     }
0101 
0102     case AtCore::PAUSE:
0103         if (d->cline.startsWith(QStringLiteral(";-"))) {
0104             nextLine();
0105         }
0106         break;
0107 
0108     default:
0109         qCDebug(PRINT_THREAD) << "Unknown State";
0110         break;
0111     }
0112 }
0113 
0114 void PrintThread::endPrint()
0115 {
0116     Q_EMIT printProgressChanged(100);
0117     qCDebug(PRINT_THREAD) << "atEnd";
0118     disconnect(d->core->firmwarePlugin(), &IFirmware::readyForCommand, this, &PrintThread::processJob);
0119     disconnect(this, &PrintThread::nextCommand, d->core, &AtCore::pushCommand);
0120     disconnect(d->core, &AtCore::stateChanged, this, &PrintThread::setState);
0121     Q_EMIT stateChanged(AtCore::FINISHEDPRINT);
0122     Q_EMIT stateChanged(AtCore::IDLE);
0123     disconnect(this, &PrintThread::stateChanged, d->core, &AtCore::setState);
0124     Q_EMIT finished();
0125 }
0126 void PrintThread::nextLine()
0127 {
0128     d->cline = d->gcodestream->readLine();
0129     qCDebug(PRINT_THREAD) << "Nextline:" << d->cline;
0130     d->stillSize -= d->cline.size() + 1; // remove read chars
0131     d->printProgress = float(d->totalSize - d->stillSize) * 100 / float(d->totalSize);
0132     qCDebug(PRINT_THREAD) << "progress:" << QString::number(double(d->printProgress));
0133     Q_EMIT printProgressChanged(d->printProgress);
0134 
0135     if (d->cline.startsWith(QStringLiteral(";-"))) {
0136         injectCommand(d->cline);
0137         d->cline = QLatin1String("");
0138         return;
0139     }
0140     // Remove Comments from the gcode.
0141     // Type 1: Anything after ; is comment.
0142     // Example G28 Z; Home Axis Z
0143     if (d->cline.contains(QChar::fromLatin1(';'))) {
0144         d->cline.resize(d->cline.indexOf(QChar::fromLatin1(';')));
0145     }
0146     // Type 2: Block Type anything between ( and ) is a comment
0147     // Example G28 (Home)Z
0148     if (d->cline.contains(QChar::fromLatin1('('))) {
0149         // Remove (.....) from the line
0150         d->cline.remove(QRegularExpression(QStringLiteral(".(?<=[(])(.*)(?=[)]).")));
0151     }
0152 
0153     d->cline = d->cline.simplified();
0154 }
0155 
0156 void PrintThread::setState(const AtCore::STATES &newState)
0157 {
0158     if (d->state == AtCore::STATES::DISCONNECTED && (newState == AtCore::STATES::PAUSE || newState == AtCore::STATES::STOP)) {
0159         qCDebug(PRINT_THREAD) << "Serial not connected !";
0160         return;
0161     }
0162     if (newState != d->state) {
0163         qCDebug(PRINT_THREAD) << QStringLiteral("State changed from [%1] to [%2]").arg(QVariant::fromValue(d->state).toString(), QVariant::fromValue(newState).toString());
0164         disconnect(d->core, &AtCore::stateChanged, this, &PrintThread::setState);
0165         d->state = newState;
0166         Q_EMIT stateChanged(d->state);
0167         connect(d->core, &AtCore::stateChanged, this, &PrintThread::setState, Qt::QueuedConnection);
0168     }
0169 }
0170 
0171 void PrintThread::injectCommand(QString &command)
0172 {
0173     // remove the ;
0174     command.remove(0, 1);
0175     command.prepend(QStringLiteral("0:"));
0176     QStringList cmd = command.split(QLatin1Char(':'));
0177     cmd.replace(1, cmd.at(1).simplified().toLower());
0178     cmd.replace(2, cmd.at(2).simplified());
0179 
0180     static QCommandLineParser parser;
0181     if (parser.optionNames().isEmpty()) {
0182         parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
0183         parser.addOptions(d->options);
0184     }
0185 
0186     qCDebug(PRINT_THREAD) << "attempting to inject " << cmd;
0187     parser.process(cmd);
0188 
0189     if (parser.isSet(QStringLiteral("pause"))) {
0190         d->core->pause(parser.positionalArguments().at(0));
0191     } else if (parser.isSet(QStringLiteral("extruder temperature"))) {
0192         QStringList args = parser.positionalArguments().at(0).split(QLatin1Char(','));
0193         bool wait = !QString::compare(args.at(2).simplified(), QStringLiteral("true"), Qt::CaseInsensitive);
0194         d->core->setExtruderTemp(args.at(0).toUInt(), args.at(1).toUInt(), wait);
0195     } else if (parser.isSet(QStringLiteral("bed temperature"))) {
0196         QStringList args = parser.positionalArguments().at(0).split(QLatin1Char(','));
0197         bool wait = !QString::compare(args.at(1).simplified(), QStringLiteral("true"), Qt::CaseInsensitive);
0198         d->core->setBedTemp(args.at(0).toUInt(), wait);
0199     } else if (parser.isSet(QStringLiteral("print speed"))) {
0200         d->core->setPrinterSpeed(parser.positionalArguments().at(0).toUInt());
0201     } else if (parser.isSet(QStringLiteral("fan speed"))) {
0202         d->core->setFanSpeed(parser.positionalArguments().at(0).toUInt(), parser.positionalArguments().at(1).toUInt());
0203     } else if (parser.isSet(QStringLiteral("flow rate"))) {
0204         d->core->setFlowRate(parser.positionalArguments().at(0).toUInt());
0205     } else if (parser.isSet(QStringLiteral("message"))) {
0206         d->core->showMessage(parser.positionalArguments().at(0));
0207     } else if (parser.isSet(QStringLiteral("command"))) {
0208         d->core->pushCommand(parser.positionalArguments().at(0));
0209     } else {
0210         qCDebug(PRINT_THREAD) << "Attempted to inject unknown command: " << parser.positionalArguments();
0211     }
0212 }