src/model.cpp

changeset 1079
67c6e5d32e68
parent 1077
952d6b3e7d11
child 1082
1738bdaf36d6
equal deleted inserted replaced
1078:c72e3115a297 1079:67c6e5d32e68
1 #include "model.h" 1 #include "model.h"
2 #include "ldObject.h" 2 #include "ldObject.h"
3 3 #include "documentmanager.h"
4 Model::Model() {} 4
5 Model::Model(DocumentManager* manager) :
6 _manager {manager} {}
5 7
6 Model::~Model() 8 Model::~Model()
7 { 9 {
8 for (int i = 0; i < countof(_objects); ++i) 10 for (int i = 0; i < countof(_objects); ++i)
9 delete _objects[i]; 11 delete _objects[i];
195 197
196 bool Model::isEmpty() const 198 bool Model::isEmpty() const
197 { 199 {
198 return _objects.isEmpty(); 200 return _objects.isEmpty();
199 } 201 }
202
203 DocumentManager* Model::documentManager() const
204 {
205 return _manager;
206 }
207
208 // =============================================================================
209 //
210 static void CheckTokenCount (const QStringList& tokens, int num)
211 {
212 if (countof(tokens) != num)
213 throw QString (format ("Bad amount of tokens, expected %1, got %2", num, countof(tokens)));
214 }
215
216 // =============================================================================
217 //
218 static void CheckTokenNumbers (const QStringList& tokens, int min, int max)
219 {
220 bool ok;
221 QRegExp scientificRegex ("\\-?[0-9]+\\.[0-9]+e\\-[0-9]+");
222
223 for (int i = min; i <= max; ++i)
224 {
225 // Check for floating point
226 tokens[i].toDouble (&ok);
227 if (ok)
228 return;
229
230 // Check hex
231 if (tokens[i].startsWith ("0x"))
232 {
233 tokens[i].mid (2).toInt (&ok, 16);
234
235 if (ok)
236 return;
237 }
238
239 // Check scientific notation, e.g. 7.99361e-15
240 if (scientificRegex.exactMatch (tokens[i]))
241 return;
242
243 throw QString (format ("Token #%1 was `%2`, expected a number (matched length: %3)",
244 (i + 1), tokens[i], scientificRegex.matchedLength()));
245 }
246 }
247
248 // =============================================================================
249 //
250 static Vertex ParseVertex (QStringList& s, const int n)
251 {
252 Vertex v;
253 v.apply ([&] (Axis ax, double& a) { a = s[n + ax].toDouble(); });
254 return v;
255 }
256
257 static int32 StringToNumber (QString a, bool* ok = nullptr)
258 {
259 int base = 10;
260
261 if (a.startsWith ("0x"))
262 {
263 a.remove (0, 2);
264 base = 16;
265 }
266
267 return a.toLong (ok, base);
268 }
269
270 // =============================================================================
271 // This is the LDraw code parser function. It takes in a string containing LDraw
272 // code and returns the object parsed from it. parseLine never returns null,
273 // the object will be LDError if it could not be parsed properly.
274 // =============================================================================
275 LDObject* Model::insertFromString(int position, QString line)
276 {
277 try
278 {
279 QStringList tokens = line.split(" ", QString::SkipEmptyParts);
280
281 if (countof(tokens) <= 0)
282 {
283 // Line was empty, or only consisted of whitespace
284 return emplaceAt<LDEmpty>(position);
285 }
286
287 if (countof(tokens[0]) != 1 or not tokens[0][0].isDigit())
288 throw QString ("Illogical line code");
289
290 int num = tokens[0][0].digitValue();
291
292 switch (num)
293 {
294 case 0:
295 {
296 // Comment
297 QString commentText = line.mid (line.indexOf ("0") + 2);
298 QString commentTextSimplified = commentText.simplified();
299
300 // Handle BFC statements
301 if (countof(tokens) > 2 and tokens[1] == "BFC")
302 {
303 for (BfcStatement statement : iterateEnum<BfcStatement>())
304 {
305 if (commentTextSimplified == format("BFC %1", LDBfc::statementToString (statement)))
306 return emplaceAt<LDBfc>(position, statement);
307 }
308
309 // MLCAD is notorious for stuffing these statements in parts it
310 // creates. The above block only handles valid statements, so we
311 // need to handle MLCAD-style invertnext, clip and noclip separately.
312 if (commentTextSimplified == "BFC CERTIFY INVERTNEXT")
313 return emplaceAt<LDBfc>(position, BfcStatement::InvertNext);
314 else if (commentTextSimplified == "BFC CERTIFY CLIP")
315 return emplaceAt<LDBfc>(position, BfcStatement::Clip);
316 else if (commentTextSimplified == "BFC CERTIFY NOCLIP")
317 return emplaceAt<LDBfc>(position, BfcStatement::NoClip);
318 }
319
320 if (countof(tokens) > 2 and tokens[1] == "!LDFORGE")
321 {
322 // Handle LDForge-specific types, they're embedded into comments too
323 if (tokens[2] == "OVERLAY")
324 {
325 CheckTokenCount (tokens, 9);
326 CheckTokenNumbers (tokens, 5, 8);
327
328 LDOverlay* obj = emplaceAt<LDOverlay>(position);
329 obj->setFileName (tokens[3]);
330 obj->setCamera (tokens[4].toLong());
331 obj->setX (tokens[5].toLong());
332 obj->setY (tokens[6].toLong());
333 obj->setWidth (tokens[7].toLong());
334 obj->setHeight (tokens[8].toLong());
335 return obj;
336 }
337 else if (tokens[2] == "BEZIER_CURVE")
338 {
339 CheckTokenCount (tokens, 16);
340 CheckTokenNumbers (tokens, 3, 15);
341 LDBezierCurve* obj = emplaceAt<LDBezierCurve>(position);
342 obj->setColor (StringToNumber (tokens[3]));
343
344 for (int i = 0; i < 4; ++i)
345 obj->setVertex (i, ParseVertex (tokens, 4 + (i * 3)));
346
347 return obj;
348 }
349 }
350
351 // Just a regular comment:
352 return emplaceAt<LDComment>(position, commentText);
353 }
354
355 case 1:
356 {
357 // Subfile
358 CheckTokenCount (tokens, 15);
359 CheckTokenNumbers (tokens, 1, 13);
360 LDDocument* document = _manager->getDocumentByName(tokens[14]);
361
362 // If we cannot open the file, mark it an error. Note we cannot use LDParseError
363 // here because the error object needs the document reference.
364 if (not document)
365 {
366 LDError* obj = emplaceAt<LDError>(position, line, format ("Could not open %1", tokens[14]));
367 obj->setFileReferenced (tokens[14]);
368 return obj;
369 }
370
371 Vertex referncePosition = ParseVertex (tokens, 2); // 2 - 4
372 Matrix transform;
373
374 for (int i = 0; i < 9; ++i)
375 transform.value(i) = tokens[i + 5].toDouble(); // 5 - 13
376
377 LDSubfileReference* obj = emplaceAt<LDSubfileReference>(position, document, transform, referncePosition);
378 obj->setColor (StringToNumber (tokens[1]));
379 return obj;
380 }
381
382 case 2:
383 {
384 CheckTokenCount (tokens, 8);
385 CheckTokenNumbers (tokens, 1, 7);
386
387 // Line
388 LDLine* obj = emplaceAt<LDLine>(position);
389 obj->setColor (StringToNumber (tokens[1]));
390
391 for (int i = 0; i < 2; ++i)
392 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 7
393
394 return obj;
395 }
396
397 case 3:
398 {
399 CheckTokenCount (tokens, 11);
400 CheckTokenNumbers (tokens, 1, 10);
401
402 // Triangle
403 LDTriangle* obj = emplaceAt<LDTriangle>(position);
404 obj->setColor (StringToNumber (tokens[1]));
405
406 for (int i = 0; i < 3; ++i)
407 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 10
408
409 return obj;
410 }
411
412 case 4:
413 case 5:
414 {
415 CheckTokenCount (tokens, 14);
416 CheckTokenNumbers (tokens, 1, 13);
417
418 // Quadrilateral / Conditional line
419 LDObject* obj;
420
421 if (num == 4)
422 obj = emplaceAt<LDQuad>(position);
423 else
424 obj = emplaceAt<LDCondLine>(position);
425
426 obj->setColor (StringToNumber (tokens[1]));
427
428 for (int i = 0; i < 4; ++i)
429 obj->setVertex (i, ParseVertex (tokens, 2 + (i * 3))); // 2 - 13
430
431 return obj;
432 }
433
434 default:
435 throw QString {"Unknown line code number"};
436 }
437 }
438 catch (QString& errorMessage)
439 {
440 // Strange line we couldn't parse
441 return emplaceAt<LDError>(position, line, errorMessage);
442 }
443 }
444
445 LDObject* Model::addFromString(QString line)
446 {
447 return insertFromString(size(), line);
448 }
449
450 LDObject* Model::replaceWithFromString(LDObject* object, QString line)
451 {
452 if (object and object->model() == this)
453 {
454 int position = object->lineNumber();
455 removeAt(position);
456 return insertFromString(position, line);
457 }
458 else
459 return nullptr;
460 }

mercurial