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}; |