-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserialworker.cpp
More file actions
270 lines (254 loc) · 8.32 KB
/
Copy pathserialworker.cpp
File metadata and controls
270 lines (254 loc) · 8.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#include "serialworker.h"
#include <QFile>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <QThread>
SerialWorker::SerialWorker(QObject *parent)
: QObject{parent}
, m_port(this)
{}
template <size_t N>
bool SerialWorker::waitForLine(char (&buf)[N])
{
static_assert(N > 1, "readLine() requires the array size to be greater than 1.");
// If there is at least a line already for us to read, read it.
while (!m_port.canReadLine())
{
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.write("M410\n"); // stop all movement
m_port.close();
emit stopped();
return false;
}
if (!m_port.waitForReadyRead(m_timeout_ms))
{
m_port.close();
emit error(QStringLiteral("Read timing out after %1 seconds.").arg(m_timeout_ms/1000));
return false;
}
}
m_port.readLine(buf, N);
return true;
}
template <size_t N>
bool SerialWorker::waitForOK(char (&buf)[N])
{
do
{
if (!waitForLine(buf)) return false;
} while (std::strcmp(buf, "ok\n") != 0);
return true;
}
void SerialWorker::start(const QString& filename, const SerialConfig& config)
{
m_port.setPortName(config.name);
if (!m_port.setBaudRate(config.baudrate))
{
emit error(m_port.errorString());
return;
}
if (!m_port.setStopBits(config.stopbits))
{
emit error(m_port.errorString());
return;
}
if (!m_port.setDataBits(config.databits))
{
emit error(m_port.errorString());
return;
}
if (!m_port.setParity(config.parity))
{
emit error(m_port.errorString());
return;
}
if (!m_port.setFlowControl(config.flowControl))
{
emit error(m_port.errorString());
return;
}
if (!parseFile(filename)) return;
char buffer[256];
if (!m_port.open(QIODeviceBase::ReadWrite | QIODeviceBase::ExistingOnly))
{
emit error(m_port.errorString());
return;
}
// confirm we are actually talking to a Marlin 3D printer
m_port.write("M115\n"); // obtaining firmware info
if (!waitForLine(buffer)) return;
if (!std::strstr(buffer, "Marlin"))
{
m_port.close();
emit error(QStringLiteral("Cannot detect a Marlin 3D printer - make sure your serial settings are correct!"));
return;
}
if (!waitForOK(buffer)) return;
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.close();
emit stopped();
return;
}
// home tool head
m_port.write("G28\n");
if (!waitForOK(buffer)) return;
// get tool head current position, which is the center of the printer bed
static const QRegularExpression re("X:([0-9]+\\.[0-9]+) Y:([0-9]+\\.[0-9]+) Z");
m_port.write("M114\n");
if (!waitForLine(buffer)) return;
auto match = re.match(buffer);
if (!match.hasMatch())
{
m_port.close();
emit error(QStringLiteral("Unable to parse current tool head position!"));
return;
}
if (!waitForOK(buffer)) return;
// now we parse x and y
bool ok1, ok2;
double centerx = match.captured(1).toDouble(&ok1);
double centery = match.captured(2).toDouble(&ok2);
if (!ok1 || !ok2)
{
m_port.close();
emit error(QStringLiteral("Unable to parse current tool head position!"));
return;
}
qDebug("Tool center position x=%f, y=%f", centerx, centery);
// now we punch holes
if (!punchHoles(centerx, centery, buffer)) return;
// we tell the main thread that we have finished
m_port.close();
emit finished();
}
template <size_t N>
bool SerialWorker::punchHoles(double cx, double cy, char (&buf)[N])
{
double svgWidth = m_svgCanvas.size.width, svgHeight = m_svgCanvas.size.height;
double printerWidth = cx * 2, printerHeight = cy * 2;
double scale = std::min(printerWidth / svgWidth, printerHeight / svgHeight);
double svgcx = svgWidth / 2, svgcy = svgHeight / 2;
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.close();
emit stopped();
return false;
}
m_port.write("G90\n"); // use absolute positioning
if (!waitForOK(buf)) return false;
m_port.write("G0 Z50\n"); // set z=50
if (!waitForOK(buf)) return false;
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.write("M410\n"); // stop all movement
m_port.close();
emit stopped();
return false;
}
m_port.write("M400\n");
if (!waitForOK(buf)) return false;
for (const auto& h : std::as_const(m_svgCanvas.holes))
{
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.close();
emit stopped();
return false;
}
// We try to fit the SVG canvas on top of the printer bed. Instead of their centers being aligned, now their bottom left corners are aligned.
double printerx = h.x * scale, printery = (svgHeight - h.y) * scale; // flip y-axis
qDebug("Punching hole at X=%.2f, Y=%.2f", printerx, printery);
m_port.write(QStringLiteral("G0 X%1 Y%2\n").arg(printerx, 0, 'f', 2).arg(printery, 0, 'f', 2).toUtf8());
if (!waitForOK(buf)) return false;
if (QThread::currentThread()->isInterruptionRequested())
{
m_port.write("M410\n"); // stop all movement
m_port.close();
emit stopped();
return false;
}
m_port.write("M400\n"); // wait for printer head to move to the correct position
if (!waitForOK(buf)) return false;
// TODO: lowerHolePunch();
QThread::sleep(2); // wait for 2 seconds
// TODO: raiseHolePunch();
}
return true;
}
bool SerialWorker::parseFile(const QString& filename)
{
if (filename.isEmpty())
{
emit error("SVG file name is not set.");
return false;
}
qDebug() << "Parsing file " << filename;
m_svgCanvas.holes.clear();
QFile svg(filename);
if (!svg.open(QIODeviceBase::ReadOnly))
{
emit error(svg.errorString());
return false;
}
QXmlStreamReader svgReader(&svg);
QXmlStreamReader::TokenType tokentype;
bool hassvg = false;
while ((tokentype = svgReader.readNext()) != QXmlStreamReader::EndDocument)
{
if (QThread::currentThread()->isInterruptionRequested())
{
emit stopped();
return false;
}
if (tokentype == QXmlStreamReader::Invalid)
{
static constexpr auto getXmlErrorString = [](QXmlStreamReader::Error err)
{
static const QString errString[] {
"No error has occurred.",
"Parser encountered unexpected element.",
"Custom error has occurred: ",
"SVG is not well-formed.",
"The SVG document has ended prematurely.",
};
return errString[err];
};
emit error(getXmlErrorString(svgReader.error()));
return false;
}
if (tokentype == QXmlStreamReader::StartElement)
{
if (svgReader.name() == "svg")
{
auto attr = svgReader.attributes();
bool ok1, ok2;
m_svgCanvas.size.width = attr.value("width").toDouble(&ok1);
m_svgCanvas.size.height = attr.value("height").toDouble(&ok2);
hassvg = ok1 && ok2;
}
else if (svgReader.name() == "ellipse" || svgReader.name() == "circle")
{
auto attr = svgReader.attributes();
bool ok1, ok2;
double x = attr.value("cx").toDouble(&ok1);
double y = attr.value("cy").toDouble(&ok2);
if (ok1 && ok2) m_svgCanvas.holes.emplace_back(x, y);
qDebug("Circle at x=%f, y=%f", x, y);
}
}
}
if (!hassvg)
{
emit error(QStringLiteral("Cannot detect SVG elements."));
return false;
}
if (m_svgCanvas.size.width < 0 || m_svgCanvas.size.height < 0)
{
emit error(QStringLiteral("SVG width or height is negative."));
return false;
}
qDebug("SVG width=%f, height=%f", m_svgCanvas.size.width, m_svgCanvas.size.height);
return true;
}