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