src/primitives.cpp

changeset 554
2478a6b3106d
parent 553
2418d5955421
equal deleted inserted replaced
553:2418d5955421 554:2478a6b3106d
14 * 14 *
15 * You should have received a copy of the GNU General Public License 15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */ 17 */
18 18
19 // TODO: I believe the multi-threading is unnecessary here. Get rid of it.
20
21 #include <QDir> 19 #include <QDir>
22 #include <QThread>
23 #include <QRegExp> 20 #include <QRegExp>
24 #include <QFileDialog> 21 #include <QFileDialog>
25 #include "document.h" 22 #include "document.h"
26 #include "gui.h" 23 #include "gui.h"
27 #include "primitives.h" 24 #include "primitives.h"
28 #include "ui_makeprim.h" 25 #include "ui_makeprim.h"
29 #include "misc.h" 26 #include "misc.h"
30 #include "colors.h" 27 #include "colors.h"
31 #include "moc_primitives.cpp" 28 #include "moc_primitives.cpp"
32 29
33 QList<PrimitiveCategory> g_PrimitiveCategories; 30 QList<PrimitiveCategory*> g_PrimitiveCategories;
34 QList<Primitive> g_primitives; 31 QList<Primitive> g_primitives;
35 static PrimitiveLister* g_activePrimLister = null; 32 static PrimitiveLister* g_activePrimLister = null;
36 static bool g_primListerMutex = false; 33 PrimitiveCategory* g_unmatched = null;
37 static const str g_Other = PrimitiveLister::tr ("Other"); 34
35 extern_cfg (String, ld_defaultname);
36 extern_cfg (String, ld_defaultuser);
37 extern_cfg (Int, ld_defaultlicense);
38 38
39 static const str g_radialNameRoots[] = 39 static const str g_radialNameRoots[] =
40 { "edge", 40 { "edge",
41 "cyli", 41 "cyli",
42 "disc", 42 "disc",
43 "ndis", 43 "ndis",
44 "ring", 44 "ring",
45 "con" 45 "con"
46 }; 46 };
47 47
48 static void populateCategories(); 48 PrimitiveLister* getPrimitiveLister()
49 static void loadPrimitiveCatgories(); 49 { return g_activePrimLister;
50 }
50 51
51 // ============================================================================= 52 // =============================================================================
52 // ----------------------------------------------------------------------------- 53 // -----------------------------------------------------------------------------
53 void loadPrimitives() 54 void loadPrimitives()
54 { log ("Loading primitives...\n"); 55 { log ("Loading primitives...\n");
55 loadPrimitiveCatgories(); 56 PrimitiveCategory::loadCategories();
56 57
57 // Try to load prims.cfg 58 // Try to load prims.cfg
58 File conf (Config::filepath ("prims.cfg"), File::Read); 59 File conf (Config::filepath ("prims.cfg"), File::Read);
59 60
60 if (!conf) 61 if (!conf)
61 { // No prims.cfg, build it 62 { // No prims.cfg, build it
62 PrimitiveLister::start(); 63 PrimitiveLister::start();
63 } 64 }
64 else 65 else
65 { // Read primitives from prims.cfg 66 { // Read primitives from prims.cfg
66 for (str line : conf) 67 for (str line : conf)
67 { int space = line.indexOf (" "); 68 { int space = line.indexOf (" ");
68 69
69 if (space == -1) 70 if (space == -1)
70 continue; 71 continue;
71 72
73 info.name = line.left (space); 74 info.name = line.left (space);
74 info.title = line.mid (space + 1); 75 info.title = line.mid (space + 1);
75 g_primitives << info; 76 g_primitives << info;
76 } 77 }
77 78
78 populateCategories(); 79 PrimitiveCategory::populateCategories();
79 } 80 }
80 } 81 }
81 82
82 // ============================================================================= 83 // =============================================================================
83 // ----------------------------------------------------------------------------- 84 // -----------------------------------------------------------------------------
84 static void recursiveGetFilenames (QDir dir, QList<str>& fnames) 85 static void recursiveGetFilenames (QDir dir, QList<str>& fnames)
85 { QFileInfoList flist = dir.entryInfoList(); 86 { QFileInfoList flist = dir.entryInfoList();
86 87
87 for (const QFileInfo & info : flist) 88 for (const QFileInfo & info : flist)
88 { if (info.fileName() == "." || info.fileName() == "..") 89 { if (info.fileName() == "." || info.fileName() == "..")
89 continue; // skip . and .. 90 continue; // skip . and ..
90 91
91 if (info.isDir()) 92 if (info.isDir())
92 recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames); 93 recursiveGetFilenames (QDir (info.absoluteFilePath()), fnames);
95 } 96 }
96 } 97 }
97 98
98 // ============================================================================= 99 // =============================================================================
99 // ----------------------------------------------------------------------------- 100 // -----------------------------------------------------------------------------
101 PrimitiveLister::PrimitiveLister (QObject* parent) :
102 QObject (parent),
103 m_i (0)
104 { g_activePrimLister = this;
105 QDir dir (LDPaths::prims());
106 assert (dir.exists());
107 m_baselen = dir.absolutePath().length();
108 recursiveGetFilenames (dir, m_files);
109 emit starting (m_files.size());
110 }
111
112 // =============================================================================
113 // -----------------------------------------------------------------------------
114 PrimitiveLister::~PrimitiveLister()
115 { g_activePrimLister = null;
116 }
117
118 // =============================================================================
119 // -----------------------------------------------------------------------------
100 void PrimitiveLister::work() 120 void PrimitiveLister::work()
101 { g_activePrimLister = this; 121 { int j = min (m_i + 300, m_files.size());
102 m_prims.clear(); 122 log ("PrimitiveLister::work: %1 -> %2\n", m_i, j);
103 123
104 QDir dir (LDPaths::prims()); 124 for (; m_i < j; ++m_i)
105 int baselen = dir.absolutePath().length(); 125 { str fname = m_files[m_i];
106 int i = 0; 126 File f (fname, File::Read);
107 QList<str> fnames;
108
109 assert (dir.exists());
110 recursiveGetFilenames (dir, fnames);
111 emit starting (fnames.size());
112
113 for (str fname : fnames)
114 { File f (fname, File::Read);
115
116 Primitive info; 127 Primitive info;
117 info.name = fname.mid (baselen + 1); // make full path relative 128 info.name = fname.mid (m_baselen + 1); // make full path relative
118 info.name.replace ('/', '\\'); // use DOS backslashes, they're expected 129 info.name.replace ('/', '\\'); // use DOS backslashes, they're expected
119 info.cat = null; 130 info.cat = null;
120 131
121 if (!f.readLine (info.title)) 132 if (!f.readLine (info.title))
122 info.title = ""; 133 info.title = "";
127 { info.title.remove (0, 1); // remove 0 138 { info.title.remove (0, 1); // remove 0
128 info.title = info.title.simplified(); 139 info.title = info.title.simplified();
129 } 140 }
130 141
131 m_prims << info; 142 m_prims << info;
132 emit update (++i); 143 }
133 } 144
134 145 if (m_i == m_files.size())
135 // Save to a config file 146 { // Done with primitives, now save to a config file
136 File conf (Config::filepath ("prims.cfg"), File::Write); 147 File conf (Config::filepath ("prims.cfg"), File::Write);
137 148
138 for (Primitive & info : m_prims) 149 for (Primitive& info : m_prims)
139 fprint (conf, "%1 %2\n", info.name, info.title); 150 fprint (conf, "%1 %2\n", info.name, info.title);
140 151
141 conf.close(); 152 conf.close();
142 153
143 g_primListerMutex = true; 154 g_primitives = m_prims;
144 g_primitives = m_prims; 155 PrimitiveCategory::populateCategories();
145 populateCategories(); 156 log ("%1 primitives listed", g_primitives.size());
146 g_primListerMutex = false; 157 g_activePrimLister = null;
147 g_activePrimLister = null; 158 emit workDone();
148 emit workDone(); 159 deleteLater();
160 }
161 else
162 { // Defer to event loop, pick up the work later
163 emit update (m_i);
164 QMetaObject::invokeMethod (this, "work", Qt::QueuedConnection);
165 }
149 } 166 }
150 167
151 // ============================================================================= 168 // =============================================================================
152 // ----------------------------------------------------------------------------- 169 // -----------------------------------------------------------------------------
153 void PrimitiveLister::start() 170 void PrimitiveLister::start()
154 { if (g_activePrimLister) 171 { if (g_activePrimLister)
155 return; 172 return;
156 173
157 PrimitiveLister* lister = new PrimitiveLister; 174 PrimitiveLister* lister = new PrimitiveLister;
158 QThread* listerThread = new QThread; 175 /*
159 lister->moveToThread (listerThread);
160 connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int))); 176 connect (lister, SIGNAL (starting (int)), g_win, SLOT (primitiveLoaderStart (int)));
161 connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int))); 177 connect (lister, SIGNAL (update (int)), g_win, SLOT (primitiveLoaderUpdate (int)));
162 connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd())); 178 connect (lister, SIGNAL (workDone()), g_win, SLOT (primitiveLoaderEnd()));
163 connect (listerThread, SIGNAL (started()), lister, SLOT (work())); 179 */
164 connect (listerThread, SIGNAL (finished()), lister, SLOT (deleteLater())); 180 lister->work();
165 listerThread->start(); 181 }
166 } 182
167 183 // =============================================================================
168 // ============================================================================= 184 // -----------------------------------------------------------------------------
169 // ----------------------------------------------------------------------------- 185 PrimitiveCategory::PrimitiveCategory (str name, QObject* parent) :
170 static PrimitiveCategory* findCategory (str name) 186 QObject (parent),
171 { for (PrimitiveCategory & cat : g_PrimitiveCategories) 187 m_Name (name) {}
172 if (cat.getName() == name) 188
173 return &cat; 189 // =============================================================================
174 190 // -----------------------------------------------------------------------------
175 return null; 191 void PrimitiveCategory::populateCategories()
176 } 192 { for (PrimitiveCategory* cat : g_PrimitiveCategories)
177 193 cat->prims.clear();
178 // ============================================================================= 194
179 // ----------------------------------------------------------------------------- 195
180 static void populateCategories() 196 for (Primitive& prim : g_primitives)
181 { for (PrimitiveCategory & cat : g_PrimitiveCategories)
182 cat.prims.clear();
183
184 PrimitiveCategory* unmatched = findCategory (g_Other);
185
186 if (!unmatched)
187 { // Shouldn't happen.. but catch it anyway.
188 PrimitiveCategory cat;
189 cat.setName (g_Other);
190 g_PrimitiveCategories << cat;
191 unmatched = &g_PrimitiveCategories.last();
192 }
193
194 for (Primitive & prim : g_primitives)
195 { bool matched = false; 197 { bool matched = false;
196 prim.cat = null; 198 prim.cat = null;
197 199
198 // Go over the categories and their regexes, if and when there's a match, 200 // Go over the categories and their regexes, if and when there's a match,
199 // the primitive's category is set to the category the regex beloings to. 201 // the primitive's category is set to the category the regex beloings to.
200 for (PrimitiveCategory& cat : g_PrimitiveCategories) 202 for (PrimitiveCategory* cat : g_PrimitiveCategories)
201 { for (PrimitiveCategory::RegexEntry& entry : cat.regexes) 203 { for (RegexEntry& entry : cat->regexes)
202 { switch (entry.type) 204 { switch (entry.type)
203 { case PrimitiveCategory::Filename: 205 { case EFilenameRegex:
204 // f-regex, check against filename 206 { // f-regex, check against filename
205 matched = entry.regex.exactMatch (prim.name); 207 matched = entry.regex.exactMatch (prim.name);
206 break; 208 } break;
207 209
208 case PrimitiveCategory::Title: 210 case ETitleRegex:
209 // t-regex, check against title 211 { // t-regex, check against title
210 matched = entry.regex.exactMatch (prim.title); 212 matched = entry.regex.exactMatch (prim.title);
211 break; 213 } break;
212 } 214 }
213 215
214 if (matched) 216 if (matched)
215 { prim.cat = &cat; 217 { prim.cat = cat;
216 break; 218 break;
217 } 219 }
218 } 220 }
219 221
220 // Drop out if a category was decided on. 222 // Drop out if a category was decided on.
221 if (prim.cat) 223 if (prim.cat != null)
222 break; 224 break;
223 } 225 }
224 226
225 // If there was a match, add the primitive to the category. 227 // If there was a match, add the primitive to the category.
226 // Otherwise, add it to the list of unmatched primitives. 228 // Otherwise, add it to the list of unmatched primitives.
227 if (prim.cat) 229 if (prim.cat != null)
228 prim.cat->prims << prim; 230 prim.cat->prims << prim;
229 else 231 else
230 unmatched->prims << prim; 232 g_unmatched->prims << prim;
231 } 233 }
232 } 234 }
233 235
234 // ============================================================================= 236 // =============================================================================
235 // ----------------------------------------------------------------------------- 237 // -----------------------------------------------------------------------------
236 static void loadPrimitiveCatgories() 238 void PrimitiveCategory::loadCategories()
237 { g_PrimitiveCategories.clear(); 239 { for (PrimitiveCategory* cat : g_PrimitiveCategories)
240 delete cat;
241
242 g_PrimitiveCategories.clear();
238 File f (Config::dirpath() + "primregexps.cfg", File::Read); 243 File f (Config::dirpath() + "primregexps.cfg", File::Read);
239 244
240 if (!f) 245 if (!f)
241 f.open (":/data/primitive-categories.cfg", File::Read); 246 f.open (":/data/primitive-categories.cfg", File::Read);
242 247
243 if (!f) 248 if (!f)
244 critical (QObject::tr ("Failed to open primitive categories!")); 249 { critical (QObject::tr ("Failed to open primitive categories!"));
250 return;
251 }
245 252
246 if (f) 253 if (f)
247 { PrimitiveCategory cat; 254 { PrimitiveCategory* cat = null;
248 255
249 for (str line : f) 256 for (str line : f)
250 { int colon; 257 { int colon;
251 258
252 if (line.length() == 0 || line[0] == '#') 259 if (line.length() == 0 || line[0] == '#')
253 continue; 260 continue;
254 261
255 if ( (colon = line.indexOf (":")) == -1) 262 if ((colon = line.indexOf (":")) == -1)
256 { if (cat.regexes.size() > 0) 263 { if (cat && cat->isValidToInclude())
257 g_PrimitiveCategories << cat; 264 g_PrimitiveCategories << cat;
258 265
259 cat.regexes.clear(); 266 cat = new PrimitiveCategory (line);
260 cat.prims.clear(); 267 }
261 cat.setName (line); 268 elif (cat != null)
269 { str cmd = line.left (colon);
270 ERegexType type = EFilenameRegex;
271
272 if (cmd == "f")
273 type = EFilenameRegex;
274 elif (cmd == "t")
275 type = ETitleRegex;
276 else
277 { log (tr ("Warning: unknown command \"%1\" on line \"%2\""), cmd, line);
278 continue;
279 }
280
281 QRegExp regex (line.mid (colon + 1));
282 RegexEntry entry = { regex, type };
283 cat->regexes << entry;
262 } 284 }
263 else 285 else
264 { str cmd = line.left (colon); 286 log ("Warning: Rules given before the first category name");
265 PrimitiveCategory::Type type = PrimitiveCategory::Filename;
266
267 if (cmd == "f")
268 type = PrimitiveCategory::Filename;
269
270 elif (cmd == "t")
271 type = PrimitiveCategory::Title;
272 else
273 continue;
274
275 QRegExp regex (line.mid (colon + 1));
276 PrimitiveCategory::RegexEntry entry = { regex, type };
277 cat.regexes << entry;
278 }
279 } 287 }
280 288
281 if (cat.regexes.size() > 0) 289 if (cat->isValidToInclude())
282 g_PrimitiveCategories << cat; 290 g_PrimitiveCategories << cat;
283 } 291 }
284 292
285 // Add a category for unmatched primitives 293 // Add a category for unmatched primitives.
286 PrimitiveCategory cat; 294 // Note: if this function is called the second time, g_unmatched has been
287 cat.setName (g_Other); 295 // deleted at the beginning of the function and is dangling at this point.
288 g_PrimitiveCategories << cat; 296 g_unmatched = new PrimitiveCategory (tr ("Other"));
297 g_PrimitiveCategories << g_unmatched;
298 }
299
300 // =============================================================================
301 // -----------------------------------------------------------------------------
302 bool PrimitiveCategory::isValidToInclude()
303 { if (regexes.size() == 0)
304 { log (tr ("Warning: category \"%1\" left without patterns"), getName());
305 deleteLater();
306 return false;
307 }
308
309 return true;
289 } 310 }
290 311
291 // ============================================================================= 312 // =============================================================================
292 // ----------------------------------------------------------------------------- 313 // -----------------------------------------------------------------------------
293 bool isPrimitiveLoaderBusy() 314 bool isPrimitiveLoaderBusy()
294 { return g_primListerMutex; 315 { return g_activePrimLister != null;
295 } 316 }
296 317
297 // ============================================================================= 318 // =============================================================================
298 // ----------------------------------------------------------------------------- 319 // -----------------------------------------------------------------------------
299 static double radialPoint (int i, int divs, double (*func) (double)) 320 static double radialPoint (int i, int divs, double (*func) (double))
300 { return (*func) ( (i * 2 * pi) / divs); 321 { return (*func) ((i * 2 * pi) / divs);
301 } 322 }
302 323
303 // ============================================================================= 324 // =============================================================================
304 // ----------------------------------------------------------------------------- 325 // -----------------------------------------------------------------------------
305 void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines) 326 void makeCircle (int segs, int divs, double radius, QList<QLineF>& lines)
306 { for (int i = 0; i < segs; ++i) 327 { for (int i = 0; i < segs; ++i)
307 { double x0 = radius * radialPoint (i, divs, cos), 328 { double x0 = radius * radialPoint (i, divs, cos),
308 x1 = radius * radialPoint (i + 1, divs, cos), 329 x1 = radius * radialPoint (i + 1, divs, cos),
309 z0 = radius * radialPoint (i, divs, sin), 330 z0 = radius * radialPoint (i, divs, sin),
310 z1 = radius * radialPoint (i + 1, divs, sin); 331 z1 = radius * radialPoint (i + 1, divs, sin);
311 332
312 lines << QLineF (QPointF (x0, z0), QPointF (x1, z1)); 333 lines << QLineF (QPointF (x0, z0), QPointF (x1, z1));
313 } 334 }
314 } 335 }
315 336
425 // If this is not a full circle, we need a conditional line at the other 446 // If this is not a full circle, we need a conditional line at the other
426 // end, too. 447 // end, too.
427 if (segs < divs && condLineSegs.size() != 0) 448 if (segs < divs && condLineSegs.size() != 0)
428 condLineSegs << segs; 449 condLineSegs << segs;
429 450
430 for (int i : condLineSegs) 451 for (int i : condLineSegs)
431 { vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)), 452 { vertex v0 (radialPoint (i, divs, cos), 0.0f, radialPoint (i, divs, sin)),
432 v1, 453 v1,
433 v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)), 454 v2 (radialPoint (i + 1, divs, cos), 0.0f, radialPoint (i + 1, divs, sin)),
434 v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin)); 455 v3 (radialPoint (i - 1, divs, cos), 0.0f, radialPoint (i - 1, divs, sin));
435 456
436 if (type == Cylinder) 457 if (type == Cylinder)
437 v1 = vertex (v0[X], 1.0f, v0[Z]); 458 v1 = vertex (v0[X], 1.0f, v0[Z]);
438
439 elif (type == Cone) 459 elif (type == Cone)
440 { v1 = vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1)); 460 { v1 = vertex (v0[X] * (num + 1), 0.0f, v0[Z] * (num + 1));
441 v0[X] *= num; 461 v0[X] *= num;
442 v0[Y] = 1.0f; 462 v0[Y] = 1.0f;
443 v0[Z] *= num; 463 v0[Z] *= num;
523 descr.insert (0, "Hi-Res "); 543 descr.insert (0, "Hi-Res ");
524 544
525 LDDocument* f = new LDDocument; 545 LDDocument* f = new LDDocument;
526 f->setDefaultName (name); 546 f->setDefaultName (name);
527 547
548 str author = APPNAME;
549 str license = "";
550
551 if (ld_defaultname.value.isEmpty() == false)
552 { license = getLicenseText (ld_defaultlicense);
553 author = fmt ("%1 [%2]", ld_defaultname, ld_defaultuser);
554 }
555
528 f->addObjects ( 556 f->addObjects (
529 { new LDComment (descr), 557 { new LDComment (descr),
530 new LDComment (fmt ("Name: %1", name)), 558 new LDComment (fmt ("Name: %1", name)),
531 new LDComment (fmt ("Author: LDForge")), 559 new LDComment (fmt ("Author: %1", author)),
532 new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")), 560 new LDComment (fmt ("!LDRAW_ORG Unofficial_%1Primitive", divs == hires ? "48_" : "")),
533 new LDComment (CALicense), 561 new LDComment (license),
534 new LDEmpty, 562 new LDEmpty,
535 new LDBFC (LDBFC::CertifyCCW), 563 new LDBFC (LDBFC::CertifyCCW),
536 new LDEmpty, 564 new LDEmpty,
537 }); 565 });
538 566

mercurial