246 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") |
252 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") |
247 { |
253 { |
248 invertNext = true; |
254 invertNext = true; |
249 continue; |
255 continue; |
250 } |
256 } |
251 modelobjects::BaseObject* object = parseFromString(editor, line); |
257 std::unique_ptr<modelobjects::BaseObject> object = parseFromString(line); |
252 if (invertNext) |
258 if (invertNext) |
253 { |
259 { |
254 editor.setObjectProperty(object, modelobjects::Property::IsInverted, true); |
260 editor.setObjectProperty(object.get(), modelobjects::Property::IsInverted, true); |
255 } |
261 } |
|
262 editor.append(std::move(object)); |
256 invertNext = false; |
263 invertNext = false; |
257 } |
264 } |
258 } |
265 } |
259 |
266 |
260 namespace |
267 static Color colorFromString(const QString& colorString) |
261 { |
268 { |
262 namespace regexes |
269 bool colorSucceeded; |
263 { |
270 const Color color = {colorString.toInt(&colorSucceeded)}; |
264 static const QRegExp comment {R"(^\s*0\s*\/\/\s*(.+)$)"}; |
271 if (colorSucceeded) |
265 static const QRegExp metacommand {R"(^\s*0\s*(.+)$)"}; |
272 { |
266 static const QRegExp edgeline |
273 return color; |
267 { |
|
268 R"(^\s*2)" // starting 2-token |
|
269 R"(\s+(\d+))" // colour |
|
270 R"(((?:\s+[^\s]+){3}))" // 1st vertex |
|
271 R"(((?:\s+[^\s]+){3}))" // 2nd vertex |
|
272 R"(\s*$)" // end |
|
273 }; |
|
274 static const QRegExp triangle |
|
275 { |
|
276 R"(^\s*3)" // starting 3-token |
|
277 R"(\s+(\d+))" // colour |
|
278 R"(((?:\s+[^\s]+){3}))" // 1st vertex |
|
279 R"(((?:\s+[^\s]+){3}))" // 2nd vertex |
|
280 R"(((?:\s+[^\s]+){3}))" // 3rd vertex |
|
281 R"(\s*$)" // end |
|
282 }; |
|
283 static const QRegExp quadrilateral |
|
284 { |
|
285 R"(^\s*4)" // starting 4-token |
|
286 R"(\s+(\d+))" // colour |
|
287 R"(((?:\s+[^\s]+){3}))" // 1st vertex |
|
288 R"(((?:\s+[^\s]+){3}))" // 2nd vertex |
|
289 R"(((?:\s+[^\s]+){3}))" // 3rd vertex |
|
290 R"(((?:\s+[^\s]+){3}))" // 4th vertex |
|
291 R"(\s*$)" // end |
|
292 }; |
|
293 static const QRegExp conditionaledge |
|
294 { |
|
295 R"(^\s*5)" // starting 5-token |
|
296 R"(\s+(\d+))" // colour |
|
297 R"(((?:\s+[^\s]+){3}))" // 1st vertex |
|
298 R"(((?:\s+[^\s]+){3}))" // 2nd vertex |
|
299 R"(((?:\s+[^\s]+){3}))" // 1st control point |
|
300 R"(((?:\s+[^\s]+){3}))" // 2nd control point |
|
301 R"(\s*$)" // end |
|
302 }; |
|
303 } |
|
304 } |
|
305 |
|
306 static Vertex vertexFromString(const QString& vertex_string) |
|
307 { |
|
308 static const QRegExp pattern {R"(^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$)"}; |
|
309 const bool succeeded = pattern.exactMatch(vertex_string); |
|
310 if (succeeded) |
|
311 { |
|
312 const float x = pattern.cap(1).toFloat(nullptr); |
|
313 const float y = pattern.cap(2).toFloat(nullptr); |
|
314 const float z = pattern.cap(3).toFloat(nullptr); |
|
315 return {x, y, z}; |
|
316 } |
274 } |
317 else |
275 else |
318 { |
276 { |
319 return {}; |
277 throw BodyParseError{"colour was not an integer value"}; |
320 } |
278 } |
321 } |
279 } |
322 |
280 |
323 static modelobjects::Edge* parseEdgeline( |
281 static Vertex vertexFromStrings( |
324 Model::EditContext& editor, |
282 const QStringList& tokens, |
325 const QString& line) |
283 const int startingPosition) |
326 { |
284 { |
327 const bool succeeded = regexes::edgeline.exactMatch(line); |
285 bool ok_x; |
328 if (succeeded) |
286 const float x = tokens[startingPosition].toFloat(&ok_x); |
329 { |
287 bool ok_y; |
330 const Color colour = {regexes::edgeline.cap(1).toInt(nullptr)}; |
288 const float y = tokens[startingPosition + 1].toFloat(&ok_y); |
331 const Vertex v_1 = vertexFromString(regexes::edgeline.cap(2)); |
289 bool ok_z; |
332 const Vertex v_2 = vertexFromString(regexes::edgeline.cap(3)); |
290 const float z = tokens[startingPosition + 2].toFloat(&ok_z); |
333 return editor.append<modelobjects::Edge>(v_1, v_2, colour); |
291 if (not ok_x or not ok_y or not ok_z) |
|
292 { |
|
293 throw BodyParseError{"vertex contained illegal co-ordinates"}; |
|
294 } |
|
295 return {x, y, z}; |
|
296 } |
|
297 |
|
298 static Matrix3x3 matrixFromStrings(const QStringList& tokens, const int startingPosition) |
|
299 { |
|
300 Matrix3x3 result; |
|
301 for (int i = 0; i < 9; i += 1) |
|
302 { |
|
303 const int row = i / 3; |
|
304 const int column = i % 3; |
|
305 const int index = i + startingPosition; |
|
306 if (index >= tokens.size()) |
|
307 { |
|
308 throw BodyParseError{"too few tokens available"}; |
|
309 } |
|
310 bool ok; |
|
311 result(row, column) = tokens[index].toFloat(&ok); |
|
312 if (not ok) |
|
313 { |
|
314 throw BodyParseError{"non-numeric values for matrix"}; |
|
315 } |
|
316 } |
|
317 return result; |
|
318 } |
|
319 |
|
320 static std::unique_ptr<modelobjects::BaseObject> parseType0Line( |
|
321 const QString& line, |
|
322 const QStringList& tokens) |
|
323 { |
|
324 Q_UNUSED(tokens) |
|
325 if (line.startsWith("0 //")) |
|
326 { |
|
327 return std::make_unique<modelobjects::Comment>(line.mid(std::strlen("0 //")).simplified()); |
334 } |
328 } |
335 else |
329 else |
336 { |
330 { |
337 return nullptr; |
331 return std::make_unique<modelobjects::MetaCommand>(line.mid(1).simplified()); |
338 } |
332 } |
339 } |
333 } |
340 |
334 |
341 modelobjects::BaseObject* Parser::parseFromString( |
335 static std::unique_ptr<modelobjects::SubfileReference> parseType1Line( |
342 Model::EditContext& editor, |
336 const QString& line, |
343 const QString& line) |
337 const QStringList& tokens) |
344 { |
338 { |
345 modelobjects::Edge* edge = parseEdgeline(editor, line); |
339 Q_UNUSED(line) |
346 if (edge) |
340 constexpr int colorPosition = 1; |
347 { |
341 constexpr int transformPosition = 2; // 2..10 |
348 return edge; |
342 constexpr int positionPosition = 11; // 11..13 |
349 } |
343 constexpr int namePosition = 14; |
350 return editor.append<modelobjects::ErrorLine>(line); |
344 if (tokens.size() != 15) |
351 } |
345 { |
|
346 throw BodyParseError{"wrong amount of tokens in a type-1 line"}; |
|
347 } |
|
348 const Color color = colorFromString(tokens[colorPosition]); |
|
349 const Vertex position = vertexFromStrings(tokens, positionPosition); |
|
350 const Matrix3x3 transform = matrixFromStrings(tokens, transformPosition); |
|
351 const QString& name = tokens[namePosition]; |
|
352 return std::make_unique<modelobjects::SubfileReference>(position, transform, name, color); |
|
353 } |
|
354 |
|
355 template<typename T, int NumVertices> |
|
356 static std::unique_ptr<T> parsePolygon( |
|
357 const QString& line, |
|
358 const QStringList& tokens) |
|
359 { |
|
360 Q_UNUSED(line) |
|
361 constexpr int colorPosition = 1; |
|
362 auto vertexPosition = [](int n) { return 2 + 3*n; }; |
|
363 if (tokens.size() != 2 + 3 * NumVertices) |
|
364 { |
|
365 throw BodyParseError{"wrong amount of tokens"}; |
|
366 } |
|
367 const Color color = colorFromString(tokens[colorPosition]); |
|
368 QVector<Vertex> vertices; |
|
369 vertices.reserve(NumVertices); |
|
370 for (int i = 0; i < NumVertices; i += 1) |
|
371 { |
|
372 vertices.append(vertexFromStrings(tokens, vertexPosition(i))); |
|
373 } |
|
374 return std::make_unique<T>(vertices, color); |
|
375 } |
|
376 |
|
377 std::unique_ptr<modelobjects::BaseObject> Parser::parseFromString(QString line) |
|
378 { |
|
379 line = line.simplified(); |
|
380 try |
|
381 { |
|
382 const QStringList tokens = line.split(QRegExp{R"(\s+)"}); |
|
383 if (tokens.empty()) |
|
384 { |
|
385 return std::make_unique<modelobjects::Empty>(); |
|
386 } |
|
387 bool ok_code; |
|
388 const int code = tokens[0].toInt(&ok_code); |
|
389 if (not ok_code) |
|
390 { |
|
391 throw BodyParseError{"line type was not an integer"}; |
|
392 } |
|
393 switch (code) |
|
394 { |
|
395 case 0: |
|
396 return parseType0Line(line, tokens); |
|
397 case 1: |
|
398 return parseType1Line(line, tokens); |
|
399 case 2: |
|
400 return parsePolygon<modelobjects::Edge, 2>(line, tokens); |
|
401 case 3: |
|
402 return parsePolygon<modelobjects::Triangle, 3>(line, tokens); |
|
403 case 4: |
|
404 return parsePolygon<modelobjects::Quadrilateral, 4>(line, tokens); |
|
405 case 5: |
|
406 return parsePolygon<modelobjects::ConditionalEdge, 4>(line, tokens); |
|
407 default: |
|
408 throw BodyParseError{utility::format("bad line type '%1'", code)}; |
|
409 } |
|
410 } |
|
411 catch(const BodyParseError& error) |
|
412 { |
|
413 return std::make_unique<modelobjects::ErrorLine>(line, error.message); |
|
414 } |
|
415 } |