src/model.cpp

changeset 1288
d1e45f90654b
parent 1281
f9c01b455594
child 1306
be85306198a2
equal deleted inserted replaced
1287:f1da43b7f5c6 1288:d1e45f90654b
18 18
19 #include "model.h" 19 #include "model.h"
20 #include "linetypes/modelobject.h" 20 #include "linetypes/modelobject.h"
21 #include "documentmanager.h" 21 #include "documentmanager.h"
22 #include "generics/migrate.h" 22 #include "generics/migrate.h"
23 #include "linetypes/comment.h"
24 #include "linetypes/conditionaledge.h"
25 #include "linetypes/edgeline.h"
26 #include "linetypes/empty.h"
27 #include "linetypes/quadrilateral.h"
28 #include "linetypes/triangle.h"
29 #include "editHistory.h" 23 #include "editHistory.h"
30 24
31 Model::Model(DocumentManager* manager) : 25 Model::Model(DocumentManager* manager) :
32 QAbstractListModel {manager}, 26 QAbstractListModel {manager},
33 _manager {manager} {} 27 _manager {manager} {}
287 * Returns the model's associated document manager. This pointer is used to resolve subfile references. 281 * Returns the model's associated document manager. This pointer is used to resolve subfile references.
288 */ 282 */
289 DocumentManager* Model::documentManager() const 283 DocumentManager* Model::documentManager() const
290 { 284 {
291 return _manager; 285 return _manager;
292 }
293
294 // =============================================================================
295 //
296 static void CheckTokenCount (const QStringList& tokens, int num)
297 {
298 if (countof(tokens) != num)
299 throw QString (format ("Bad amount of tokens, expected %1, got %2", num, countof(tokens)));
300 }
301
302 // =============================================================================
303 //
304 static void CheckTokenNumbers (const QStringList& tokens, int min, int max)
305 {
306 bool ok;
307 QRegExp scientificRegex ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
308
309 for (int i = min; i <= max; ++i)
310 {
311 // Check for floating point
312 tokens[i].toDouble (&ok);
313 if (ok)
314 return;
315
316 // Check hex
317 if (tokens[i].startsWith ("0x"))
318 {
319 tokens[i].mid (2).toInt (&ok, 16);
320
321 if (ok)
322 return;
323 }
324
325 // Check scientific notation, e.g. 7.99361e-15
326 if (scientificRegex.exactMatch (tokens[i]))
327 return;
328
329 throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)",
330 (i + 1), tokens[i], scientificRegex.matchedLength()));
331 }
332 }
333
334 // =============================================================================
335 //
336 static Vertex ParseVertex (QStringList& s, const int n)
337 {
338 Vertex v;
339 v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); });
340 return v;
341 }
342
343 static qint32 StringToNumber (QString a, bool* ok = nullptr)
344 {
345 int base = 10;
346
347 if (a.startsWith ("0x"))
348 {
349 a.remove (0, 2);
350 base = 16;
351 }
352
353 return a.toLong (ok, base);
354 }
355
356 // =============================================================================
357 // This is the LDraw code parser function. It takes in a string containing LDraw
358 // code and returns the object parsed from it. parseLine never returns null,
359 // the object will be LDError if it could not be parsed properly.
360 // =============================================================================
361 LDObject* Model::insertFromString(int position, QString line)
362 {
363 try
364 {
365 QStringList tokens = line.split(" ", QString::SkipEmptyParts);
366
367 if (countof(tokens) <= 0)
368 {
369 // Line was empty, or only consisted of whitespace
370 return emplaceAt<LDEmpty>(position);
371 }
372
373 if (countof(tokens[0]) != 1 or not tokens[0][0].isDigit())
374 throw QString ("Illogical line code");
375
376 int num = tokens[0][0].digitValue();
377
378 switch (num)
379 {
380 case 0:
381 {
382 // Comment
383 QString commentText = line.mid (line.indexOf ("0") + 2);
384 QString commentTextSimplified = commentText.simplified();
385
386 // Handle BFC statements
387 if (countof(tokens) > 2 and tokens[1] == "BFC")
388 {
389 for (BfcStatement statement : iterateEnum<BfcStatement>())
390 {
391 if (commentTextSimplified == format("BFC %1", LDBfc::statementToString (statement)))
392 return emplaceAt<LDBfc>(position, statement);
393 }
394
395 // MLCAD is notorious for stuffing these statements in parts it
396 // creates. The above block only handles valid statements, so we
397 // need to handle MLCAD-style invertnext, clip and noclip separately.
398 if (commentTextSimplified == "BFC CERTIFY INVERTNEXT")
399 return emplaceAt<LDBfc>(position, BfcStatement::InvertNext);
400 else if (commentTextSimplified == "BFC CERTIFY CLIP")
401 return emplaceAt<LDBfc>(position, BfcStatement::Clip);
402 else if (commentTextSimplified == "BFC CERTIFY NOCLIP")
403 return emplaceAt<LDBfc>(position, BfcStatement::NoClip);
404 }
405
406 if (countof(tokens) > 2 and tokens[1] == "!LDFORGE")
407 {
408 // Handle LDForge-specific types, they're embedded into comments too
409 if (tokens[2] == "BEZIER_CURVE")
410 {
411 CheckTokenCount (tokens, 16);
412 CheckTokenNumbers (tokens, 3, 15);
413 LDBezierCurve* obj = emplaceAt<LDBezierCurve>(position);
414 obj->setColor (StringToNumber (tokens[3]));
415
416 for (int i = 0; i < 4; ++i)
417 obj->setVertex (i, ParseVertex (tokens, 4 + (i * 3)));
418
419 return obj;
420 }
421 }
422
423 // Just a regular comment:
424 return emplaceAt<LDComment>(position, commentText);
425 }
426
427 case 1:
428 {
429 // Subfile
430 CheckTokenCount (tokens, 15);
431 CheckTokenNumbers (tokens, 1, 13);
432
433 Vertex referncePosition = ParseVertex (tokens, 2); // 2 - 4
434 Matrix transform;
435
436 for (int i = 0; i < 9; ++i)
437 transform.value(i) = tokens[i + 5].toDouble(); // 5 - 13
438
439 LDSubfileReference* obj = emplaceAt<LDSubfileReference>(position, tokens[14], transform, referncePosition);
440 obj->setColor (StringToNumber (tokens[1]));
441 return obj;
442 }
443
444 case 2:
445 {
446 CheckTokenCount (tokens, 8);
447 CheckTokenNumbers (tokens, 1, 7);
448
449 // Line
450 LDEdgeLine* obj = emplaceAt<LDEdgeLine>(position);
451 obj->setColor (StringToNumber (tokens[1]));
452
453 for (int i = 0; i < 2; ++i)
454 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 7
455
456 return obj;
457 }
458
459 case 3:
460 {
461 CheckTokenCount (tokens, 11);
462 CheckTokenNumbers (tokens, 1, 10);
463
464 // Triangle
465 LDTriangle* obj = emplaceAt<LDTriangle>(position);
466 obj->setColor (StringToNumber (tokens[1]));
467
468 for (int i = 0; i < 3; ++i)
469 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 10
470
471 return obj;
472 }
473
474 case 4:
475 case 5:
476 {
477 CheckTokenCount (tokens, 14);
478 CheckTokenNumbers (tokens, 1, 13);
479
480 // Quadrilateral / Conditional line
481 LDObject* obj;
482
483 if (num == 4)
484 obj = emplaceAt<LDQuadrilateral>(position);
485 else
486 obj = emplaceAt<LDConditionalEdge>(position);
487
488 obj->setColor (StringToNumber (tokens[1]));
489
490 for (int i = 0; i < 4; ++i)
491 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 13
492
493 return obj;
494 }
495
496 default:
497 throw QString {"Unknown line code number"};
498 }
499 }
500 catch (QString& errorMessage)
501 {
502 // Strange line we couldn't parse
503 return emplaceAt<LDError>(position, line, errorMessage);
504 }
505 }
506
507 /*
508 * Given an LDraw object string, parses it and inserts it into the model.
509 */
510 LDObject* Model::addFromString(QString line)
511 {
512 return insertFromString(size(), line);
513 }
514
515 /*
516 * Replaces the given object with a new one that is parsed from the given LDraw object string.
517 * If the parsing fails, the object is replaced with an error object.
518 */
519 LDObject* Model::replaceWithFromString(LDObject* object, QString line)
520 {
521 QModelIndex index = this->indexOf(object);
522
523 if (index.isValid())
524 {
525 removeAt(index.row());
526 return insertFromString(index.row(), line);
527 }
528 else
529 return nullptr;
530 } 286 }
531 287
532 IndexGenerator Model::indices() const 288 IndexGenerator Model::indices() const
533 { 289 {
534 return {this}; 290 return {this};

mercurial