OdbDesignLib
OdbDesign ODB++ Parsing Library
 
Loading...
Searching...
No Matches
FeaturesFile.cpp
1#include "FeaturesFile.h"
2#include "ArchiveExtractor.h"
3#include <fstream>
4#include "Logger.h"
5#include "../invalid_odb_error.h"
6#include "str_utils.h"
7#include "../parse_error.h"
8#include "SymbolName.h"
9#include "equals_within.h"
10#include "macros.h"
11#include "../../Text/Utf8Sanitizer.h"
12
13#include <algorithm>
14#include <cctype>
15
16namespace Odb::Lib::FileModel::Design
17{
18 namespace
19 {
20 void apply_default_units_to_symbols(const std::string &rawUnits, SymbolName::Vector &symbols)
21 {
22 const auto inferred = Odb::Lib::FileModel::Design::inferred_unit_type_from_features_units(rawUnits);
23 if (inferred == UnitType::None)
24 return;
25 for (const auto &sym : symbols)
26 {
27 if (sym)
28 {
29 sym->ApplyDefaultUnitTypeIfNone(inferred);
30 }
31 }
32 }
33 }
34
35 FeaturesFile::FeaturesFile()
36 : m_path(""), m_directory(""), m_numFeatures(0), m_id(static_cast<unsigned>(-1))
37 {
38 }
39
40 FeaturesFile::~FeaturesFile()
41 {
42 m_featureRecords.clear();
43 m_attributeNames.clear();
44 m_attributeTextValues.clear();
45 m_symbolNamesByName.clear();
46 m_symbolNames.clear();
47 }
48
49 bool FeaturesFile::Parse(std::filesystem::path directory, const std::string &alternateFilename /*= ""*/)
50 {
51 std::ifstream featuresFile;
52 int lineNumber = 0;
53 std::string line;
54
55 try
56 {
57 m_directory = directory;
58
59 loginfo("checking for extraction...");
60
61 std::vector<std::string> filenames;
62 if (alternateFilename.empty())
63 {
64 std::copy(std::begin(FEATURES_FILENAMES), std::end(FEATURES_FILENAMES), std::back_inserter(filenames));
65 }
66 else
67 {
68 filenames.push_back(alternateFilename);
69 }
70
71 std::filesystem::path featuresFilePath;
72 for (const auto &featuresFilename : filenames)
73 {
74 loginfo("trying features file: [" + featuresFilename + "]...");
75
76 featuresFilePath = Utils::ArchiveExtractor::getUncompressedFilePath(m_directory, featuresFilename);
77 if (exists(featuresFilePath) && is_regular_file(featuresFilePath))
78 {
79 loginfo("found features file: [" + featuresFilePath.string() + "]");
80 break;
81 }
82 }
83
84 m_path = featuresFilePath;
85
86 loginfo("any extraction complete, parsing data...");
87
88 if (!std::filesystem::exists(m_path))
89 {
90 auto message = "features file does not exist: [" + m_path.string() + "]";
91 throw invalid_odb_error(message.c_str());
92 }
93 else if (!std::filesystem::is_regular_file(m_path))
94 {
95 auto message = "features is not a file: [" + m_path.string() + "]";
96 throw invalid_odb_error(message.c_str());
97 }
98
99 featuresFile.open(m_path.string(), std::ios::in);
100 if (!featuresFile.is_open())
101 {
102 auto message = "unable to open features file: [" + m_path.string() + "]";
103 throw invalid_odb_error(message.c_str());
104 }
105
106 std::shared_ptr<FeatureRecord> pCurrentFeatureRecord;
107 std::shared_ptr<ContourPolygon> pCurrentContourPolygon;
108
109 while (std::getline(featuresFile, line))
110 {
111 lineNumber++;
112
113 // trim whitespace from beginning and end of line
114 Utils::str_trim(line);
115 if (!line.empty())
116 {
117 std::stringstream lineStream(line);
118 // char firstChar = line[0];
119
120 if (line.find(COMMENT_TOKEN) == 0)
121 {
122 // comment line
123 }
124 else if (line.find(UNITS_TOKEN) == 0)
125 {
126 // units line
127 std::string token;
128 if (!std::getline(lineStream, token, '='))
129 {
130 throw_parse_error(m_path, line, token, lineNumber);
131 }
132 else if (!std::getline(lineStream, token, '='))
133 {
134 throw_parse_error(m_path, line, token, lineNumber);
135 }
136
137 m_units = token;
138 apply_default_units_to_symbols(m_units, m_symbolNames);
139 }
140 else if (line.find("U") == 0)
141 {
142 std::string token;
143 if (!std::getline(lineStream, token, ' '))
144 {
145 throw_parse_error(m_path, line, token, lineNumber);
146 }
147 else if (!std::getline(lineStream, token, ' '))
148 {
149 throw_parse_error(m_path, line, token, lineNumber);
150 }
151
152 m_units = token;
153 apply_default_units_to_symbols(m_units, m_symbolNames);
154 }
155 else if (line.find(ID_TOKEN) == 0)
156 {
157 std::string token;
158 if (!std::getline(lineStream, token, '='))
159 {
160 throw_parse_error(m_path, line, token, lineNumber);
161 }
162 else if (!std::getline(lineStream, token, '='))
163 {
164 throw_parse_error(m_path, line, token, lineNumber);
165 }
166 m_id = std::stoul(token);
167 }
168 else if (line.find(NUM_FEATURES_TOKEN) == 0)
169 {
170 // component record line
171 std::string token;
172
173 if (!(lineStream >> token))
174 {
175 throw_parse_error(m_path, line, token, lineNumber);
176 }
177
178 if (token != NUM_FEATURES_TOKEN)
179 {
180 throw_parse_error(m_path, line, token, lineNumber);
181 }
182
183 if (!(lineStream >> m_numFeatures))
184 {
185 throw_parse_error(m_path, line, token, lineNumber);
186 }
187 }
188 else if (line.find(ATTRIBUTE_NAME_TOKEN) == 0)
189 {
190 // component attribute name line
191 std::string token;
192 // TODO: continue on failing line parse, to make a less strict/more robust parser (make a flag: enum ParseStrictness { strict, lax })
193 if (!std::getline(lineStream, token, ' '))
194 {
195 throw_parse_error(m_path, line, token, lineNumber);
196 }
197 else if (!std::getline(lineStream, token, ' '))
198 {
199 throw_parse_error(m_path, line, token, lineNumber);
200 }
201 m_attributeNames.push_back(token);
202 }
203 else if (line.find(ATTRIBUTE_VALUE_TOKEN) == 0)
204 {
205 // component attribute text string values
206 std::string token;
207 if (!std::getline(lineStream, token, ' '))
208 {
209 throw_parse_error(m_path, line, token, lineNumber);
210 }
211 else if (!std::getline(lineStream >> std::ws, token))
212 {
213 throw_parse_error(m_path, line, token, lineNumber);
214 }
215 m_attributeTextValues.push_back(token);
216 }
217 else if (line.find(SYMBOL_NAME_TOKEN) == 0)
218 {
219 // component attribute text string values
220 auto pSymbolName = std::make_shared<SymbolName>();
221 if (!pSymbolName->SymbolName::Parse(m_path, line, lineNumber))
222 {
223 throw_parse_error(m_path, line, "", lineNumber);
224 }
225 pSymbolName->ApplyDefaultUnitTypeIfNone(Odb::Lib::FileModel::Design::inferred_unit_type_from_features_units(m_units));
226 m_symbolNamesByName[pSymbolName->GetName()] = pSymbolName;
227 m_symbolNames.push_back(pSymbolName);
228 }
229 else if (line.find(FeatureRecord::LINE_TOKEN) == 0)
230 {
231 std::string token;
232 if (!(lineStream >> token) || token != FeatureRecord::LINE_TOKEN)
233 {
234 throw_parse_error(m_path, line, token, lineNumber);
235 }
236
237 auto pFeatureRecord = std::make_shared<FeatureRecord>();
238 pFeatureRecord->type = FeatureRecord::Type::Line;
239
240 if (!(lineStream >> pFeatureRecord->xs))
241 {
242 throw_parse_error(m_path, line, token, lineNumber);
243 }
244
245 if (!(lineStream >> pFeatureRecord->ys))
246 {
247 throw_parse_error(m_path, line, token, lineNumber);
248 }
249
250 if (!(lineStream >> pFeatureRecord->xe))
251 {
252 throw_parse_error(m_path, line, token, lineNumber);
253 }
254
255 if (!(lineStream >> pFeatureRecord->ye))
256 {
257 throw_parse_error(m_path, line, token, lineNumber);
258 }
259
260 if (!(lineStream >> pFeatureRecord->sym_num))
261 {
262 throw_parse_error(m_path, line, token, lineNumber);
263 }
264
265 char polarity;
266 if (!(lineStream >> polarity))
267 {
268 throw_parse_error(m_path, line, token, lineNumber);
269 }
270 switch (polarity)
271 {
272 case 'P':
273 pFeatureRecord->polarity = Polarity::Positive;
274 break;
275 case 'N':
276 pFeatureRecord->polarity = Polarity::Negative;
277 break;
278 default:
279 throw_parse_error(m_path, line, token, lineNumber);
280 }
281
282 if (!(lineStream >> pFeatureRecord->dcode))
283 {
284 throw_parse_error(m_path, line, token, lineNumber);
285 }
286
287 std::string attrIdString;
288 lineStream >> attrIdString;
289
290 if (!pFeatureRecord->ParseAttributeLookupTable(attrIdString))
291 {
292 throw_parse_error(m_path, line, token, lineNumber);
293 }
294
295 m_featureRecords.push_back(pFeatureRecord);
296 }
297 else if (line.find(FeatureRecord::PAD_TOKEN) == 0)
298 {
299 std::string token;
300 if (!(lineStream >> token) || token != FeatureRecord::PAD_TOKEN)
301 {
302 throw_parse_error(m_path, line, token, lineNumber);
303 }
304
305 auto pFeatureRecord = std::make_shared<FeatureRecord>();
306 pFeatureRecord->type = FeatureRecord::Type::Pad;
307
308 if (!(lineStream >> pFeatureRecord->x))
309 {
310 throw_parse_error(m_path, line, token, lineNumber);
311 }
312
313 if (!(lineStream >> pFeatureRecord->y))
314 {
315 throw_parse_error(m_path, line, token, lineNumber);
316 }
317
318 if (!(lineStream >> pFeatureRecord->apt_def_symbol_num))
319 {
320 throw_parse_error(m_path, line, token, lineNumber);
321 }
322 if (pFeatureRecord->apt_def_symbol_num == -1)
323 {
324 if (!(lineStream >> pFeatureRecord->apt_def_resize_factor))
325 {
326 throw_parse_error(m_path, line, token, lineNumber);
327 }
328 }
329
330 // Contract: Pads reference a symbol by FeatureRecord.sym_num.
331 // In ODB++ pad records this comes from apt_def_symbol_num; keep sym_num unset when apt_def_symbol_num == -1.
332 if (pFeatureRecord->apt_def_symbol_num >= 0 && pFeatureRecord->sym_num == -1)
333 {
334 pFeatureRecord->sym_num = pFeatureRecord->apt_def_symbol_num;
335 }
336
337 char polarity;
338 if (!(lineStream >> polarity))
339 {
340 throw_parse_error(m_path, line, token, lineNumber);
341 }
342 switch (polarity)
343 {
344 case 'P':
345 pFeatureRecord->polarity = Polarity::Positive;
346 break;
347 case 'N':
348 pFeatureRecord->polarity = Polarity::Negative;
349 break;
350 default:
351 throw_parse_error(m_path, line, token, lineNumber);
352 }
353
354 if (!(lineStream >> pFeatureRecord->dcode))
355 {
356 throw_parse_error(m_path, line, token, lineNumber);
357 }
358
359 if (!(lineStream >> pFeatureRecord->orient_def))
360 {
361 throw_parse_error(m_path, line, token, lineNumber);
362 }
363
364 std::string attrIdString;
365 lineStream >> attrIdString;
366
367 if (!pFeatureRecord->ParseAttributeLookupTable(attrIdString))
368 {
369 throw_parse_error(m_path, line, token, lineNumber);
370 }
371
372 m_featureRecords.push_back(pFeatureRecord);
373 }
374 else if (line.find(FeatureRecord::TEXT_TOKEN) == 0)
375 {
376 std::string token;
377 if (!(lineStream >> token) || token != FeatureRecord::TEXT_TOKEN)
378 {
379 throw_parse_error(m_path, line, token, lineNumber);
380 }
381
382 auto pFeatureRecord = std::make_shared<FeatureRecord>();
383 pFeatureRecord->type = FeatureRecord::Type::Text;
384
385 if (!(lineStream >> pFeatureRecord->x))
386 {
387 throw_parse_error(m_path, line, token, lineNumber);
388 }
389
390 if (!(lineStream >> pFeatureRecord->y))
391 {
392 throw_parse_error(m_path, line, token, lineNumber);
393 }
394
395 if (!(lineStream >> pFeatureRecord->font))
396 {
397 throw_parse_error(m_path, line, token, lineNumber);
398 }
399
400 char polarity;
401 if (!(lineStream >> polarity))
402 {
403 throw_parse_error(m_path, line, token, lineNumber);
404 }
405 switch (polarity)
406 {
407 case 'P':
408 pFeatureRecord->polarity = Polarity::Positive;
409 break;
410 case 'N':
411 pFeatureRecord->polarity = Polarity::Negative;
412 break;
413 default:
414 throw_parse_error(m_path, line, token, lineNumber);
415 }
416
417 if (!(lineStream >> pFeatureRecord->orient_def))
418 {
419 throw_parse_error(m_path, line, token, lineNumber);
420 }
421
422 if (pFeatureRecord->orient_def == 8 ||
423 pFeatureRecord->orient_def == 9)
424 {
425 if (!(lineStream >> pFeatureRecord->orient_def_rotation))
426 {
427 throw_parse_error(m_path, line, token, lineNumber);
428 }
429 }
430
431 if (!(lineStream >> pFeatureRecord->xsize))
432 {
433 throw_parse_error(m_path, line, token, lineNumber);
434 }
435
436 if (!(lineStream >> pFeatureRecord->ysize))
437 {
438 throw_parse_error(m_path, line, token, lineNumber);
439 }
440
441 if (!(lineStream >> pFeatureRecord->width_factor))
442 {
443 throw_parse_error(m_path, line, token, lineNumber);
444 }
445
446 if (!(lineStream >> std::quoted(pFeatureRecord->text, '\'')))
447 {
448 throw_parse_error(m_path, line, token, lineNumber);
449 }
450
451 if (!(lineStream >> pFeatureRecord->version))
452 {
453 throw_parse_error(m_path, line, token, lineNumber);
454 }
455
456 std::string attrIdString;
457 lineStream >> attrIdString;
458
459 if (!pFeatureRecord->ParseAttributeLookupTable(attrIdString))
460 {
461 throw_parse_error(m_path, line, token, lineNumber);
462 }
463
464 m_featureRecords.push_back(pFeatureRecord);
465 }
466 else if (line.find(FeatureRecord::ARC_TOKEN) == 0)
467 {
468 std::string token;
469 if (!(lineStream >> token) || token != FeatureRecord::ARC_TOKEN)
470 {
471 throw_parse_error(m_path, line, token, lineNumber);
472 }
473
474 auto pFeatureRecord = std::make_shared<FeatureRecord>();
475 pFeatureRecord->type = FeatureRecord::Type::Arc;
476
477 if (!(lineStream >> pFeatureRecord->xs))
478 {
479 throw_parse_error(m_path, line, token, lineNumber);
480 }
481
482 if (!(lineStream >> pFeatureRecord->ys))
483 {
484 throw_parse_error(m_path, line, token, lineNumber);
485 }
486
487 if (!(lineStream >> pFeatureRecord->xe))
488 {
489 throw_parse_error(m_path, line, token, lineNumber);
490 }
491
492 if (!(lineStream >> pFeatureRecord->ye))
493 {
494 throw_parse_error(m_path, line, token, lineNumber);
495 }
496
497 if (!(lineStream >> pFeatureRecord->xc))
498 {
499 throw_parse_error(m_path, line, token, lineNumber);
500 }
501
502 if (!(lineStream >> pFeatureRecord->yc))
503 {
504 throw_parse_error(m_path, line, token, lineNumber);
505 }
506
507 if (!(lineStream >> pFeatureRecord->sym_num))
508 {
509 throw_parse_error(m_path, line, token, lineNumber);
510 }
511
512 char polarity;
513 if (!(lineStream >> polarity))
514 {
515 throw_parse_error(m_path, line, token, lineNumber);
516 }
517 switch (polarity)
518 {
519 case 'P':
520 pFeatureRecord->polarity = Polarity::Positive;
521 break;
522 case 'N':
523 pFeatureRecord->polarity = Polarity::Negative;
524 break;
525 default:
526 throw_parse_error(m_path, line, token, lineNumber);
527 }
528
529 if (!(lineStream >> pFeatureRecord->dcode))
530 {
531 throw_parse_error(m_path, line, token, lineNumber);
532 }
533
534 char cw;
535 if (!(lineStream >> cw))
536 {
537 throw_parse_error(m_path, line, token, lineNumber);
538 }
539 switch (cw)
540 {
541 case 'Y':
542 pFeatureRecord->cw = true;
543 break;
544 case 'N':
545 pFeatureRecord->cw = false;
546 break;
547 default:
548 throw_parse_error(m_path, line, token, lineNumber);
549 }
550
551 std::string attrIdString;
552 lineStream >> attrIdString;
553
554 if (!pFeatureRecord->ParseAttributeLookupTable(attrIdString))
555 {
556 throw_parse_error(m_path, line, token, lineNumber);
557 }
558
559 m_featureRecords.push_back(pFeatureRecord);
560 }
561 else if (line.find(FeatureRecord::BARCODE_TOKEN) == 0)
562 {
563 std::string token;
564 if (!(lineStream >> token) || token != FeatureRecord::BARCODE_TOKEN)
565 {
566 throw_parse_error(m_path, line, token, lineNumber);
567 }
568
569 auto pFeatureRecord = std::make_shared<FeatureRecord>();
570 pFeatureRecord->type = FeatureRecord::Type::Barcode;
571
572 // TODO: barcode feature record type
573
574 // std::string attrIdString;
575 // lineStream >> attrIdString;
576
577 // if (!pFeatureRecord->ParseAttributeLookupTable(attrIdString))
578 //{
579 // throw_parse_error(m_path, line, token, lineNumber);
580 // }
581
582 m_featureRecords.push_back(pFeatureRecord);
583 }
584 else if (line.find(FeatureRecord::SURFACE_START_TOKEN) == 0 &&
585 line.size() > 1 && line[1] == ' ')
586 {
587 std::string token;
588 if (!(lineStream >> token) || token != FeatureRecord::SURFACE_START_TOKEN)
589 {
590 throw_parse_error(m_path, line, token, lineNumber);
591 }
592
593 pCurrentFeatureRecord = std::make_shared<FeatureRecord>();
594 pCurrentFeatureRecord->type = FeatureRecord::Type::Surface;
595
596 char polarity;
597 if (!(lineStream >> polarity))
598 {
599 throw_parse_error(m_path, line, token, lineNumber);
600 }
601 switch (polarity)
602 {
603 case 'P':
604 pCurrentFeatureRecord->polarity = Polarity::Positive;
605 break;
606 case 'N':
607 pCurrentFeatureRecord->polarity = Polarity::Negative;
608 break;
609 default:
610 throw_parse_error(m_path, line, token, lineNumber);
611 }
612
613 if (!(lineStream >> pCurrentFeatureRecord->dcode))
614 {
615 throw_parse_error(m_path, line, token, lineNumber);
616 }
617
618 std::string attrIdString;
619 lineStream >> attrIdString;
620
621 if (!pCurrentFeatureRecord->ParseAttributeLookupTable(attrIdString))
622 {
623 throw_parse_error(m_path, line, token, lineNumber);
624 }
625 }
626 else if (line.find(FeatureRecord::SURFACE_END_TOKEN) == 0)
627 {
628 std::string token;
629 if (!(lineStream >> token) || token != FeatureRecord::SURFACE_END_TOKEN)
630 {
631 throw_parse_error(m_path, line, token, lineNumber);
632 }
633
634 if (pCurrentFeatureRecord != nullptr)
635 {
636 m_featureRecords.push_back(pCurrentFeatureRecord);
637 pCurrentFeatureRecord.reset();
638 }
639 else
640 {
641 throw_parse_error(m_path, line, token, lineNumber);
642 }
643 }
644 else if (line.find(ContourPolygon::BEGIN_RECORD_TOKEN) == 0)
645 {
646 std::string token;
647 if (!(lineStream >> token))
648 {
649 throw_parse_error(m_path, line, token, lineNumber);
650 }
651
652 if (token != ContourPolygon::BEGIN_RECORD_TOKEN)
653 {
654 throw_parse_error(m_path, line, token, lineNumber);
655 }
656
657 pCurrentContourPolygon = std::make_shared<ContourPolygon>();
658
659 if (!(lineStream >> pCurrentContourPolygon->xStart))
660 {
661 throw_parse_error(m_path, line, token, lineNumber);
662 }
663
664 if (!(lineStream >> pCurrentContourPolygon->yStart))
665 {
666 throw_parse_error(m_path, line, token, lineNumber);
667 }
668
669 if (!(lineStream >> token))
670 {
671 throw_parse_error(m_path, line, token, lineNumber);
672 }
673
674 if (token == ContourPolygon::ISLAND_TYPE_TOKEN)
675 {
676 pCurrentContourPolygon->type = ContourPolygon::Type::Island;
677 }
678 else if (token == ContourPolygon::HOLE_TYPE_TOKEN)
679 {
680 pCurrentContourPolygon->type = ContourPolygon::Type::Hole;
681 }
682 else
683 {
684 throw_parse_error(m_path, line, token, lineNumber);
685 }
686 }
687 else if (line.find(ContourPolygon::END_RECORD_TOKEN) == 0)
688 {
689 std::string token;
690 if (!(lineStream >> token))
691 {
692 throw_parse_error(m_path, line, token, lineNumber);
693 }
694
695 if (token != ContourPolygon::END_RECORD_TOKEN)
696 {
697 throw_parse_error(m_path, line, token, lineNumber);
698 }
699
700 if (pCurrentFeatureRecord != nullptr)
701 {
702 pCurrentFeatureRecord->m_contourPolygons.push_back(pCurrentContourPolygon);
703 pCurrentContourPolygon.reset();
704 }
705 else
706 {
707 throw_parse_error(m_path, line, token, lineNumber);
708 }
709 }
710 else if (line.find(ContourPolygon::PolygonPart::ARC_RECORD_TOKEN) == 0)
711 {
712 std::string token;
713 if (!(lineStream >> token))
714 {
715 throw_parse_error(m_path, line, token, lineNumber);
716 }
717
718 if (token != ContourPolygon::PolygonPart::ARC_RECORD_TOKEN)
719 {
720 throw_parse_error(m_path, line, token, lineNumber);
721 }
722
723 auto pPolygonPart = std::make_shared<ContourPolygon::PolygonPart>();
724 pPolygonPart->type = ContourPolygon::PolygonPart::Type::Arc;
725
726 if (!(lineStream >> pPolygonPart->endX))
727 {
728 throw_parse_error(m_path, line, token, lineNumber);
729 }
730
731 if (!(lineStream >> pPolygonPart->endY))
732 {
733 throw_parse_error(m_path, line, token, lineNumber);
734 }
735
736 if (!(lineStream >> pPolygonPart->xCenter))
737 {
738 throw_parse_error(m_path, line, token, lineNumber);
739 }
740
741 if (!(lineStream >> pPolygonPart->yCenter))
742 {
743 throw_parse_error(m_path, line, token, lineNumber);
744 }
745
746 if (!(lineStream >> token))
747 {
748 throw_parse_error(m_path, line, token, lineNumber);
749 }
750
751 if (token == "y" || token == "Y")
752 {
753 pPolygonPart->isClockwise = true;
754 }
755 else if (token == "n" || token == "N")
756 {
757 pPolygonPart->isClockwise = false;
758 }
759 else
760 {
761 throw_parse_error(m_path, line, token, lineNumber);
762 }
763
764 if (pCurrentContourPolygon != nullptr)
765 {
766 pCurrentContourPolygon->m_polygonParts.push_back(pPolygonPart);
767 }
768 else
769 {
770 throw_parse_error(m_path, line, token, lineNumber);
771 }
772 }
773 else if (line.find(ContourPolygon::PolygonPart::SEGMENT_RECORD_TOKEN) == 0)
774 {
775 std::string token;
776 if (!(lineStream >> token))
777 {
778 throw_parse_error(m_path, line, token, lineNumber);
779 }
780
781 if (token != ContourPolygon::PolygonPart::SEGMENT_RECORD_TOKEN)
782 {
783 throw_parse_error(m_path, line, token, lineNumber);
784 }
785
786 auto pPolygonPart = std::make_shared<ContourPolygon::PolygonPart>();
787 pPolygonPart->type = ContourPolygon::PolygonPart::Type::Segment;
788
789 if (!(lineStream >> pPolygonPart->endX))
790 {
791 throw_parse_error(m_path, line, token, lineNumber);
792 }
793
794 if (!(lineStream >> pPolygonPart->endY))
795 {
796 throw_parse_error(m_path, line, token, lineNumber);
797 }
798
799 if (pCurrentContourPolygon != nullptr)
800 {
801 pCurrentContourPolygon->m_polygonParts.push_back(pPolygonPart);
802 }
803 else
804 {
805 throw_parse_error(m_path, line, token, lineNumber);
806 }
807 }
808 else
809 {
810 // unrecognized record line
811 parse_info pi(m_path, line, lineNumber);
812 logwarn(pi.toString("unrecognized record line in features file:"));
813 }
814 }
815 }
816
817 // do we have a current feature record? (finish up the last record in the file- i.e. ran out of file)
818 if (pCurrentFeatureRecord != nullptr)
819 {
820 // finish it up and add it to the list
821 m_featureRecords.push_back(pCurrentFeatureRecord);
822 pCurrentFeatureRecord.reset();
823 }
824
825 featuresFile.close();
826 }
827 catch (parse_error &pe)
828 {
829 auto m = pe.toString("Parse Error:");
830 logerror(m);
831 featuresFile.close();
832 throw pe;
833 }
834 catch (std::exception &e)
835 {
836 parse_info pi(m_path, line, lineNumber);
837 const auto m = pi.toString();
838 logexception_msg(e, m);
839 featuresFile.close();
840 throw e;
841 }
842
843 return true;
844 }
845
846 std::string FeaturesFile::GetUnits() const
847 {
848 return m_units;
849 }
850
851 std::filesystem::path FeaturesFile::GetPath()
852 {
853 return m_path;
854 }
855
856 std::filesystem::path FeaturesFile::GetDirectory()
857 {
858 return m_directory;
859 }
860
861 int FeaturesFile::GetNumFeatures() const
862 {
863 return m_numFeatures;
864 }
865
866 unsigned int FeaturesFile::GetId() const
867 {
868 return m_id;
869 }
870
871 const SymbolName::StringMap &FeaturesFile::GetSymbolNamesByName() const
872 {
873 return m_symbolNamesByName;
874 }
875
876 const SymbolName::Vector &FeaturesFile::GetSymbolNames() const
877 {
878 return m_symbolNames;
879 }
880
881 const FeaturesFile::FeatureRecord::Vector &FeaturesFile::GetFeatureRecords() const
882 {
883 return m_featureRecords;
884 }
885
886 std::unique_ptr<Odb::Lib::Protobuf::FeaturesFile> FeaturesFile::to_protobuf() const
887 {
888 std::unique_ptr<Odb::Lib::Protobuf::FeaturesFile> pFeaturesFileMessage(new Odb::Lib::Protobuf::FeaturesFile);
889 pFeaturesFileMessage->set_id(m_id);
890 pFeaturesFileMessage->set_numfeatures(m_numFeatures);
891 pFeaturesFileMessage->set_units(Odb::Lib::Text::ToUtf8(m_units));
892 for (const auto &pFeatureRecord : m_featureRecords)
893 {
894 pFeaturesFileMessage->add_featurerecords()->CopyFrom(*pFeatureRecord->to_protobuf());
895 }
896 for (const auto &kvSymbolName : m_symbolNamesByName)
897 {
898 (*pFeaturesFileMessage->mutable_symbolnamesbyname())[Odb::Lib::Text::ToUtf8(kvSymbolName.first)] = *kvSymbolName.second->to_protobuf();
899 }
900 for (const auto &symbolName : m_symbolNames)
901 {
902 pFeaturesFileMessage->add_symbolnames()->CopyFrom(*symbolName->to_protobuf());
903 }
904 return pFeaturesFileMessage;
905 }
906
907 void FeaturesFile::from_protobuf(const Odb::Lib::Protobuf::FeaturesFile &message)
908 {
909 m_id = message.id();
910 m_numFeatures = message.numfeatures();
911 m_units = message.units();
912 for (const auto &featureRecordMessage : message.featurerecords())
913 {
914 std::shared_ptr<FeatureRecord> pFeatureRecord(new FeatureRecord);
915 pFeatureRecord->from_protobuf(featureRecordMessage);
916 m_featureRecords.push_back(pFeatureRecord);
917 }
918 if (message.symbolnames().size() > 0)
919 {
920 // create both collections from symbolNames
921 for (const auto &symbolNameMessage : message.symbolnames())
922 {
923 auto pSymbolName = std::make_shared<SymbolName>();
924 pSymbolName->from_protobuf(symbolNameMessage);
925 m_symbolNames.push_back(pSymbolName);
926 m_symbolNamesByName[pSymbolName->GetName()] = pSymbolName;
927 }
928 }
929 else if (message.symbolnamesbyname().size() > 0)
930 {
931 // create both collections from symbolsByName
932 for (const auto &kvSymbolNameMessage : message.symbolnamesbyname())
933 {
934 auto pSymbolName = std::make_shared<SymbolName>();
935 pSymbolName->from_protobuf(kvSymbolNameMessage.second);
936 m_symbolNamesByName[kvSymbolNameMessage.first] = pSymbolName;
937 m_symbolNames.push_back(pSymbolName);
938 }
939 }
940 }
941
942 bool FeaturesFile::Save(std::ostream &os)
943 {
944 return true;
945 }
946
947 FeaturesFile::FeatureRecord::~FeatureRecord()
948 {
949 m_contourPolygons.clear();
950 }
951
952 const ContourPolygon::Vector &FeaturesFile::FeatureRecord::GetContourPolygons() const
953 {
954 return m_contourPolygons;
955 }
956
957 std::unique_ptr<Odb::Lib::Protobuf::FeaturesFile::FeatureRecord> Odb::Lib::FileModel::Design::FeaturesFile::FeatureRecord::to_protobuf() const
958 {
959 std::unique_ptr<Odb::Lib::Protobuf::FeaturesFile::FeatureRecord> pFeatureRecordMessage(new Odb::Lib::Protobuf::FeaturesFile::FeatureRecord);
960 pFeatureRecordMessage->set_apt_def_resize_factor(apt_def_resize_factor);
961 pFeatureRecordMessage->set_xc(xc);
962 pFeatureRecordMessage->set_yc(yc);
963 pFeatureRecordMessage->set_cw(cw);
964 pFeatureRecordMessage->set_font(Odb::Lib::Text::ToUtf8(font));
965 pFeatureRecordMessage->set_xsize(xsize);
966 pFeatureRecordMessage->set_ysize(ysize);
967 pFeatureRecordMessage->set_width_factor(width_factor);
968 pFeatureRecordMessage->set_text(Odb::Lib::Text::ToUtf8(text));
969 pFeatureRecordMessage->set_version(version);
970 if (sym_num >= 0)
971 {
972 pFeatureRecordMessage->set_sym_num(sym_num);
973 }
974 pFeatureRecordMessage->set_polarity(static_cast<Odb::Lib::Protobuf::Polarity>(polarity));
975 pFeatureRecordMessage->set_dcode(dcode);
976 pFeatureRecordMessage->set_id(id);
977 pFeatureRecordMessage->set_orient_def(orient_def);
978 pFeatureRecordMessage->set_orient_def_rotation(orient_def_rotation);
979 pFeatureRecordMessage->set_type(static_cast<Odb::Lib::Protobuf::FeaturesFile::FeatureRecord::Type>(type));
980 pFeatureRecordMessage->set_xs(xs);
981 pFeatureRecordMessage->set_ys(ys);
982 pFeatureRecordMessage->set_xe(xe);
983 pFeatureRecordMessage->set_ye(ye);
984 pFeatureRecordMessage->set_x(x);
985 pFeatureRecordMessage->set_y(y);
986 if (apt_def_symbol_num >= 0)
987 {
988 pFeatureRecordMessage->set_apt_def_symbol_num(apt_def_symbol_num);
989 }
990 for (const auto &pContourPolygon : m_contourPolygons)
991 {
992 pFeatureRecordMessage->add_contourpolygons()->CopyFrom(*pContourPolygon->to_protobuf());
993 }
994 for (const auto &kvAttributeAssignment : m_attributeLookupTable)
995 {
996 (*pFeatureRecordMessage->mutable_attributelookuptable())[Odb::Lib::Text::ToUtf8(kvAttributeAssignment.first)] = Odb::Lib::Text::ToUtf8(kvAttributeAssignment.second);
997 }
998 return pFeatureRecordMessage;
999 }
1000
1001 void Odb::Lib::FileModel::Design::FeaturesFile::FeatureRecord::from_protobuf(const Odb::Lib::Protobuf::FeaturesFile::FeatureRecord &message)
1002 {
1003 apt_def_resize_factor = message.apt_def_resize_factor();
1004 xc = message.xc();
1005 yc = message.yc();
1006 cw = message.cw();
1007 font = message.font();
1008 xsize = message.xsize();
1009 ysize = message.ysize();
1010 width_factor = message.width_factor();
1011 text = message.text();
1012 version = message.version();
1013 sym_num = message.has_sym_num() ? message.sym_num() : -1;
1014 polarity = static_cast<Polarity>(message.polarity());
1015 dcode = message.dcode();
1016 id = message.id();
1017 orient_def = message.orient_def();
1018 orient_def_rotation = message.orient_def_rotation();
1019 type = static_cast<Type>(message.type());
1020 xs = message.xs();
1021 ys = message.ys();
1022 xe = message.xe();
1023 ye = message.ye();
1024 x = message.x();
1025 y = message.y();
1026 apt_def_symbol_num = message.has_apt_def_symbol_num() ? message.apt_def_symbol_num() : -1;
1027 for (const auto &contourPolygonMessage : message.contourpolygons())
1028 {
1029 std::shared_ptr<ContourPolygon> pContourPolygon(new ContourPolygon);
1030 pContourPolygon->from_protobuf(contourPolygonMessage);
1031 m_contourPolygons.push_back(pContourPolygon);
1032 }
1033 for (const auto &kvAttributeAssignment : message.attributelookuptable())
1034 {
1035 m_attributeLookupTable[kvAttributeAssignment.first] = kvAttributeAssignment.second;
1036 }
1037 }
1038
1039 SymbolName::Vector collect_symbols(const FeaturesFile &featuresFile)
1040 {
1041 const auto &vec = featuresFile.GetSymbolNames();
1042 if (!vec.empty())
1043 return vec;
1044
1045 SymbolName::Vector collected;
1046 for (const auto &kv : featuresFile.GetSymbolNamesByName())
1047 {
1048 collected.push_back(kv.second);
1049 }
1050 return collected;
1051 }
1052
1053 UnitType inferred_unit_type_from_features_units(const std::string &rawUnits)
1054 {
1055 if (rawUnits.empty())
1056 return UnitType::None;
1057 std::string u = rawUnits;
1058 std::transform(u.begin(), u.end(), u.begin(), [](unsigned char c)
1059 { return static_cast<char>(std::tolower(c)); });
1060 if (u == "mm" || u == "millimeter" || u == "millimeters" || u == "micron" || u == "microns" || u == "um" || u == "µm")
1061 {
1062 return UnitType::Metric;
1063 }
1064 if (u == "inch" || u == "inches" || u == "in" || u == "mil" || u == "mils")
1065 {
1066 return UnitType::Imperial;
1067 }
1068 return UnitType::None;
1069 }
1070}
std::string ToUtf8(std::string_view input)
Converts input to valid UTF-8.