46 QString Parser::readLine() |
45 QString Parser::readLine() |
47 { |
46 { |
48 return QString::fromUtf8(this->device.readLine()).trimmed(); |
47 return QString::fromUtf8(this->device.readLine()).trimmed(); |
49 } |
48 } |
50 |
49 |
51 /* |
|
52 * Parses a single line of the header. |
|
53 * Possible parse results: |
|
54 * · ParseSuccess: the header line was parsed successfully. |
|
55 * · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise. |
|
56 * · StopParsing: the line does not belong in the header and header parsing needs to stop. |
|
57 */ |
|
58 Parser::HeaderParseResult Parser::parseHeaderLine( |
|
59 LDHeader& header, |
|
60 Winding& winding, |
|
61 const QString& line |
|
62 ) { |
|
63 if (line.isEmpty()) |
|
64 { |
|
65 return ParseSuccess; |
|
66 } |
|
67 else if (not line.startsWith("0") or line.startsWith("0 //")) |
|
68 { |
|
69 return StopParsing; |
|
70 } |
|
71 else if (line.startsWith("0 !LDRAW_ORG ")) |
|
72 { |
|
73 QStringList tokens = line |
|
74 .mid(strlen("0 !LDRAW_ORG ")) |
|
75 .split(" ", Qt::SkipEmptyParts); |
|
76 if (not tokens.isEmpty()) |
|
77 { |
|
78 QString partTypeString = tokens[0]; |
|
79 // Anything that enters LDForge becomes unofficial in any case if saved. |
|
80 // Therefore we don't need to give the Unofficial type any special |
|
81 // consideration. |
|
82 if (partTypeString.startsWith("Unofficial_")) |
|
83 partTypeString = partTypeString.mid(strlen("Unofficial_")); |
|
84 header.type = headerTypeFromString(partTypeString); |
|
85 header.qualfiers = {}; |
|
86 if (tokens.contains("Alias")) |
|
87 header.qualfiers |= LDHeader::Alias; |
|
88 if (tokens.contains("Physical_Color")) |
|
89 header.qualfiers |= LDHeader::PhysicalColour; |
|
90 if (tokens.contains("Flexible_Section")) |
|
91 header.qualfiers |= LDHeader::FlexibleSection; |
|
92 return ParseSuccess; |
|
93 } |
|
94 else |
|
95 { |
|
96 return ParseFailure; |
|
97 } |
|
98 } |
|
99 else if (line == "0 BFC CERTIFY CCW") |
|
100 { |
|
101 winding = Anticlockwise; |
|
102 return ParseSuccess; |
|
103 } |
|
104 else if (line == "0 BFC CERTIFY CW") |
|
105 { |
|
106 winding = Clockwise; |
|
107 return ParseSuccess; |
|
108 } |
|
109 else if (line == "0 BFC NOCERTIFY") |
|
110 { |
|
111 winding = NoWinding; |
|
112 return ParseSuccess; |
|
113 } |
|
114 else if (line.startsWith("0 !HISTORY ")) |
|
115 { |
|
116 static const QRegExp historyRegexp { |
|
117 R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)" |
|
118 R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))" |
|
119 }; |
|
120 if (historyRegexp.exactMatch(line)) |
|
121 { |
|
122 QString dateString = historyRegexp.capturedTexts().value(1); |
|
123 QString authorWithPrefix = historyRegexp.capturedTexts().value(2); |
|
124 QString description = historyRegexp.capturedTexts().value(3); |
|
125 LDHeader::HistoryEntry historyEntry; |
|
126 historyEntry.date = QDate::fromString(dateString, Qt::ISODate); |
|
127 historyEntry.description = description; |
|
128 |
|
129 if (authorWithPrefix[0] == '{') |
|
130 historyEntry.author = authorWithPrefix + "}"; |
|
131 else |
|
132 historyEntry.author = authorWithPrefix.mid(1); |
|
133 |
|
134 header.history.append(historyEntry); |
|
135 return ParseSuccess; |
|
136 } |
|
137 else |
|
138 { |
|
139 return ParseFailure; |
|
140 } |
|
141 } |
|
142 else if (line.startsWith("0 Author: ")) |
|
143 { |
|
144 header.author = line.mid(strlen("0 Author: ")); |
|
145 return ParseSuccess; |
|
146 } |
|
147 else if (line.startsWith("0 Name: ")) |
|
148 { |
|
149 header.name = line.mid(strlen("0 Name: ")); |
|
150 return ParseSuccess; |
|
151 } |
|
152 else if (line.startsWith("0 !HELP ")) |
|
153 { |
|
154 if (not header.help.isEmpty()) |
|
155 header.help += "\n"; |
|
156 header.help += line.mid(strlen("0 !HELP ")); |
|
157 return ParseSuccess; |
|
158 } |
|
159 else if (line.startsWith("0 !KEYWORDS ")) |
|
160 { |
|
161 if (not header.keywords.isEmpty()) |
|
162 header.keywords += "\n"; |
|
163 header.keywords += line.mid(strlen("0 !KEYWORDS ")); |
|
164 return ParseSuccess; |
|
165 } |
|
166 else if (line.startsWith("0 !CATEGORY ")) |
|
167 { |
|
168 header.category = line.mid(strlen("0 !CATEGORY ")); |
|
169 return ParseSuccess; |
|
170 } |
|
171 else if (line.startsWith("0 !CMDLINE ")) |
|
172 { |
|
173 header.cmdline = line.mid(strlen("0 !CMDLINE ")); |
|
174 return ParseSuccess; |
|
175 } |
|
176 else if (line.startsWith(LDHeader::caLicenseString)) |
|
177 { |
|
178 header.license = LDHeader::CaLicense; |
|
179 return ParseSuccess; |
|
180 } |
|
181 else if (line.startsWith(LDHeader::nonCaLicenseString)) |
|
182 { |
|
183 header.license = LDHeader::NonCaLicense; |
|
184 return ParseSuccess; |
|
185 } |
|
186 else |
|
187 { |
|
188 return ParseFailure; |
|
189 } |
|
190 } |
|
191 |
|
192 /* |
|
193 * Parses the header from the device given at construction and returns |
|
194 * the resulting header structure. |
|
195 */ |
|
196 LDHeader Parser::parseHeader(Winding& winding) |
|
197 { |
|
198 LDHeader header = {}; |
|
199 if (not this->device.atEnd()) |
|
200 { |
|
201 // Parse the description |
|
202 QString descriptionLine = this->readLine(); |
|
203 if (descriptionLine.startsWith("0 ")) |
|
204 { |
|
205 header.description = descriptionLine.mid(strlen("0 ")).trimmed(); |
|
206 // Parse the rest of the header |
|
207 while (not this->device.atEnd()) |
|
208 { |
|
209 const QString& line = this->readLine(); |
|
210 auto result = parseHeaderLine(header, winding, line); |
|
211 if (result == ParseFailure) |
|
212 { |
|
213 // Failed to parse this header line, add it as a comment into the body later. |
|
214 this->bag.append(line); |
|
215 } |
|
216 else if (result == StopParsing) |
|
217 { |
|
218 // Header parsing stops, add this line to the body. |
|
219 this->bag.append(line); |
|
220 break; |
|
221 } |
|
222 } |
|
223 } |
|
224 else |
|
225 { |
|
226 this->bag.append(descriptionLine); |
|
227 } |
|
228 } |
|
229 return header; |
|
230 } |
|
231 |
|
232 /** |
50 /** |
233 * @brief Parses the model body into the given model. |
51 * @brief Parses the model body into the given model. |
234 * @param editor Handle to model edit context |
52 * @param editor Handle to model edit context |
235 */ |
53 */ |
236 void Parser::parseBody(Model& model) |
54 void Parser::parseBody(Model& model) |
237 { |
55 { |
238 bool invertNext = false; |
56 bool invertNext = false; |
239 while (not this->device.atEnd()) |
57 while (not this->device.atEnd()) |
240 this->bag.append(this->readLine()); |
|
241 for (const QString& line : this->bag) |
|
242 { |
58 { |
243 // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. |
59 // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. |
244 // So we need to pass the string through QString::simplified to catch these cases. |
60 // So we need to pass the string through QString::simplified to catch these cases. |
245 const QString simplifiedLine = line.simplified(); |
61 const QString line = this->readLine().simplified(); |
246 if (simplifiedLine == "0 BFC INVERTNEXT" or simplifiedLine == "0 BFC CERTIFY INVERTNEXT") |
62 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT") |
247 { |
63 { |
248 invertNext = true; |
64 invertNext = true; |
249 continue; |
65 continue; |
250 } |
66 } |
251 std::unique_ptr<ldraw::Object> object = parseFromString(line); |
67 model.append(parseFromString(line)); |
252 model.append(std::move(object)); |
|
253 if (invertNext) |
68 if (invertNext) |
254 { |
69 { |
255 model[model.size() - 1]->invert(); |
70 model[model.size() - 1]->invert(); |
256 } |
71 } |
257 invertNext = false; |
72 invertNext = false; |
258 } |
73 } |
259 /* |
|
260 // Test quadrilateral splitting by splitting all the quadrilaterals |
|
261 QVector<ldraw::quadrilateralid_t> quadrilateral_ids; |
|
262 for (int i = 0; i < editor.model().size(); i += 1) |
|
263 { |
|
264 const ldraw::id_t id = editor.model().resolve(editor.model().index(i)); |
|
265 const ldraw::quadrilateralid_t quad_id = editor.model().checkType<ldraw::Quadrilateral>(id); |
|
266 if (not(quad_id == ldraw::NULL_ID)) |
|
267 { |
|
268 quadrilateral_ids.push_back(quad_id); |
|
269 } |
|
270 } |
|
271 for (const ldraw::quadrilateralid_t id : quadrilateral_ids) |
|
272 { |
|
273 ldraw::splitQuadrilateral(editor, id); |
|
274 } |
|
275 */ |
|
276 } |
74 } |
277 |
75 |
278 static ldraw::Color colorFromString(const QString& colorString) |
76 static ldraw::Color colorFromString(const QString& colorString) |
279 { |
77 { |
280 bool colorSucceeded; |
78 bool colorSucceeded; |