Added support for regex-based primitive categorization

Sun, 16 Jun 2013 00:10:11 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Sun, 16 Jun 2013 00:10:11 +0300
changeset 293
a0ed563e14b2
parent 292
4779ca562d5e
child 294
2892deee4c1b

Added support for regex-based primitive categorization

changelog.txt file | annotate | diff | comparison | revisions
data/primitive-categories.cfg file | annotate | diff | comparison | revisions
ldforge.qrc file | annotate | diff | comparison | revisions
mkqrc.sh file | annotate | diff | comparison | revisions
src/addObjectDialog.cpp file | annotate | diff | comparison | revisions
src/file.cpp file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/primitives.h file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
--- a/changelog.txt	Sat Jun 15 19:14:42 2013 +0300
+++ b/changelog.txt	Sun Jun 16 00:10:11 2013 +0300
@@ -6,7 +6,8 @@
 - Added ability to snap to pre-existing vertices while drawing.
 - When drawing, drawn vertices now display coordinate labels.
 - Replaced parts list in subfile item list with a primitive listing. Listing is generated either if
-	it's not cached (prims.cfg in configuration directory) or on user demand.
+	it's not cached (prims.cfg in configuration directory) or on user demand. Primitives can be categorised
+	with the use of a regex-based configuration file, with a valid default which should cater to most users.
 - Added an export to file action, moved it + insert from to File menu
 - Parts are now zoomed to fit properly, making the initial view of the part clearer.
 - Replace coords: allow replacing all coords regardless of original value, plus relative moving (offset)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/primitive-categories.cfg	Sun Jun 16 00:10:11 2013 +0300
@@ -0,0 +1,103 @@
+# This file determines the rules based on which primitives are grouped into
+# categories.
+#
+# Categories are defined as lines with their names, followed by lines
+# containing rules. Rules are regular expressions prefixed with either:
+#   - f: for filename matching, or
+#   - t: for file title matching.
+#
+# Primitives are matched by the regular expressions in order as they are
+# defined, the first category whose regex matches the primitive's filename
+# gets the primitive.
+#
+# Any primitive which does not match any of the regexes default into an
+# "Other" category. Do not define this category explicitly!
+#
+# All lines starting with # are comments and are ignored.
+
+Rings
+f:[0-9]+\-[0-9]+ring[0-9]\.dat
+f:[0-9]+\-[0-9]+rin[0-9]+\.dat
+f:[0-9]+\-[0-9]+ri[0-9]+\.dat
+
+Cones
+f:[0-9]+\-[0-9]+cone[0-9]\.dat
+f:[0-9]+\-[0-9]+con[0-9]+\.dat
+f:[0-9]+\-[0-9]+ri[0-9]+\.dat
+
+Circles
+f:[0-9]+\-[0-9]+edge\.dat
+
+Cylinders
+f:[0-9]+\-[0-9]+cyli[0-9]*\.dat
+
+Discs
+f:[0-9]+\-[0-9]+disc\.dat
+
+Disc Negatives
+f:[0-9]+\-[0-9]+ndis\.dat
+
+Open/Closed Cylinders
+f:[0-9]+\-[0-9]+cylo\.dat
+f:[0-9]+\-[0-9]+cylc\.dat
+f:[0-9]+\-[0-9]+cylc2\.dat
+
+Sloped Cylinders
+f:[0-9]+\-[0-9]+cyls\.dat
+f:[0-9]+\-[0-9]+cys2\.dat
+f:[0-9]+\-[0-9]+cylse\.dat
+
+Chords
+f:[0-9]+\-[0-9]+chrd\.dat
+f:[0-9]+\-[0-9]+chr\.dat
+
+Spheres
+f:[0-9]+\-[0-9]+sphe\.dat
+
+Torii (Inside)
+f:t[0-9]+i[0-9]+\.dat
+
+Torii (Outside)
+f:r[0-9]+o[0-9]+\.dat
+f:t[0-9]+o[0-9]+\.dat
+
+Torii (Tube)
+f:t[0-9]+q[0-9]+\.dat
+
+Rings (48)
+f:48\\[0-9]+\-[0-9]+ring[0-9]\.dat
+f:48\\[0-9]+\-[0-9]+rin[0-9]+\.dat
+f:48\\[0-9]+\-[0-9]+ri[0-9]+\.dat
+
+Cones (48)
+f:48\\[0-9]+\-[0-9]+cone[0-9]\.dat
+f:48\\[0-9]+\-[0-9]+con[0-9]+\.dat
+f:48\\[0-9]+\-[0-9]+ri[0-9]+\.dat
+
+Other (48)
+f:48\\.*\.dat
+
+Rectangles
+f:rect.*\.dat
+
+Boxes
+f:box.*\.dat
+
+Studs
+f:stud.*\.dat
+
+Studs (Fast-Draw)
+f:stu2.*\.dat
+
+Stud Groups
+f:stug.*\.dat
+
+Gear Teeth
+f:tooth.*\.dat
+f:tootb.*\.dat
+
+Technic
+f:axle.*\.dat
+f:axl[0-9e]ho[le0-9]+\.dat
+f:peghol.*\.dat
+t:Technic .*
\ No newline at end of file
--- a/ldforge.qrc	Sat Jun 15 19:14:42 2013 +0300
+++ b/ldforge.qrc	Sun Jun 16 00:10:11 2013 +0300
@@ -100,6 +100,7 @@
 	<file>./icons/visibility.png</file>
 	<file>./icons/wireframe.png</file>
 	<file>./icons/ytruder.png</file>
+	<file>data/primitive-categories.cfg</file>
 	<file>LICENSE</file>
 	<file>LICENSE.icons</file>
 </qresource>
--- a/mkqrc.sh	Sat Jun 15 19:14:42 2013 +0300
+++ b/mkqrc.sh	Sun Jun 16 00:10:11 2013 +0300
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 QRCFILE=ldforge.qrc
-FILES=$(echo ./icons/*.* LICENSE LICENSE.icons)
+FILES=$(echo ./icons/*.* data/*.* LICENSE LICENSE.icons)
 
 printf "" > $QRCFILE
 
--- a/src/addObjectDialog.cpp	Sat Jun 15 19:14:42 2013 +0300
+++ b/src/addObjectDialog.cpp	Sun Jun 16 00:10:11 2013 +0300
@@ -96,17 +96,22 @@
 			coordCount = 3;
 			
 			tw_subfileList = new QTreeWidget ();
-			SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null);
-			parentItem->setText (0, "Primitives");
-			QList<QTreeWidgetItem*> subfileItems;
+			tw_subfileList->setHeaderLabel ("Primitives");
 			
-			for (Primitive& info : g_Primitives) {
-				SubfileListItem* item = new SubfileListItem (parentItem, &info);
-				item->setText (0, fmt ("%1 - %2", info.name, info.title));
-				subfileItems << item;
+			for (PrimitiveCategory& cat : g_PrimitiveCategories) {
+				SubfileListItem* parentItem = new SubfileListItem (tw_subfileList, null);
+				parentItem->setText (0, cat.name ());
+				QList<QTreeWidgetItem*> subfileItems;
+				
+				for (Primitive& prim : cat.prims) {
+					SubfileListItem* item = new SubfileListItem (parentItem, &prim);
+					item->setText (0, fmt ("%1 - %2", prim.name, prim.title));
+					subfileItems << item;
+				}
+				
+				tw_subfileList->addTopLevelItem (parentItem);
 			}
 			
-			tw_subfileList->addTopLevelItem (parentItem);
 			connect (tw_subfileList, SIGNAL (itemSelectionChanged ()), this, SLOT (slot_subfileTypeChanged ()));
 			lb_subfileName = new QLabel ("File:");
 			le_subfileName = new QLineEdit;
--- a/src/file.cpp	Sat Jun 15 19:14:42 2013 +0300
+++ b/src/file.cpp	Sun Jun 16 00:10:11 2013 +0300
@@ -38,7 +38,6 @@
 cfg (str, io_recentfiles, "");
 
 static bool g_loadingMainFile = false;
-bool g_primListerMutex = false;
 
 // =============================================================================
 namespace LDPaths {
--- a/src/primitives.cpp	Sat Jun 15 19:14:42 2013 +0300
+++ b/src/primitives.cpp	Sun Jun 16 00:10:11 2013 +0300
@@ -1,11 +1,46 @@
 #include <QDir>
 #include <QThread>
+#include <QRegExp>
 #include "file.h"
 #include "gui.h"
 #include "primitives.h"
 
-PrimitiveLister* g_activePrimLister = null;
-vector<Primitive> g_Primitives;
+vector<PrimitiveCategory> g_PrimitiveCategories;
+static PrimitiveLister* g_activePrimLister = null;
+static bool g_primListerMutex = false;
+vector<Primitive> g_primitives;
+
+static void populateCategories ();
+static void loadPrimitiveCatgories ();
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+void loadPrimitives () {
+	print ("Loading primitives...\n");
+	
+	loadPrimitiveCatgories ();
+	
+	// Try to load prims.cfg
+	File conf (config::dirpath () + "prims.cfg", File::Read);
+	if (!conf) {
+		// No prims.cfg, build it
+		PrimitiveLister::start ();
+	} else {
+		for (str line : conf) {
+			int space = line.indexOf (" ");
+			if (space == -1)
+				continue;
+			
+			Primitive info;
+			info.name = line.left (space);
+			info.title = line.mid (space + 1);
+			g_primitives << info;
+		}
+	}
+	
+	populateCategories ();
+}
 
 // =============================================================================
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -23,6 +58,9 @@
 	}
 }
 
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
 void PrimitiveLister::work () {
 	g_activePrimLister = this;
 	m_prims.clear ();
@@ -54,6 +92,9 @@
 			info.title = info.title.simplified ();
 		}
 		
+		// Figure which category to use
+		info.cat = null;
+		
 		m_prims << info;
 		emit update (++i);
 	}
@@ -66,13 +107,15 @@
 	conf.close ();
 	
 	g_primListerMutex = true;
-	g_Primitives = m_prims;
+	g_primitives = m_prims;
 	g_primListerMutex = false;
-	
 	g_activePrimLister = null;
 	emit workDone ();
 }
 
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
 void PrimitiveLister::start () {
 	if (g_activePrimLister)
 		return;
@@ -88,25 +131,122 @@
 	listerThread->start ();
 }
 
-void loadPrimitives () {
-	g_Primitives.clear ();
+static PrimitiveCategory* findCategory (str name) {
+	for (PrimitiveCategory& cat : g_PrimitiveCategories)
+		if (cat.name () == name)
+			return &cat;
 	
-	// Try to load prims.cfg
-	File conf (config::dirpath () + "prims.cfg", File::Read);
-	if (!conf) {
-		// No prims.cfg, build it
-		PrimitiveLister::start ();
-		return;
+	return null;
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void populateCategories () {
+	for (PrimitiveCategory& cat : g_PrimitiveCategories)
+		cat.prims.clear ();
+	
+	PrimitiveCategory* unmatched = findCategory ("Other");
+	
+	if (!unmatched) {
+		// Shouldn't happen.. but catch it anyway.
+		print ("No `Other` category found! Creating one...\n");
+		PrimitiveCategory cat;
+		cat.setName ("Other");
+		unmatched = &(g_PrimitiveCategories << cat);
 	}
 	
-	for (str line : conf) {
-		int space = line.indexOf (" ");
-		if (space == -1)
-			continue;
+	for (Primitive& prim : g_primitives) {
+		bool matched = false;
+		
+		// Go over the categories and their regexes, if and when there's a match,
+		// the primitive's category is set to the category the regex beloings to.
+		for (PrimitiveCategory& cat : g_PrimitiveCategories) {
+			for (PrimitiveCategory::RegexEntry& entry : cat.regexes) {
+				switch (entry.type) {
+				case PrimitiveCategory::Filename:
+					// f-regex, check against filename
+					matched = entry.regex.exactMatch (prim.name);
+					break;
+					
+				case PrimitiveCategory::Title:
+					// t-regex, check against title
+					matched = entry.regex.exactMatch (prim.title);
+					break;
+				}
+				
+				if (matched) {
+					prim.cat = &cat;
+					break;
+				}
+			}
+			
+			// Drop out if a category was decided on.
+			if (prim.cat)
+				break;
+		}
 		
-		Primitive info;
-		info.name = line.left (space);
-		info.title = line.mid (space + 1);
-		g_Primitives << info;
+		// If there was a match, add the primitive to the category.
+		// Otherwise, add it to the list of unmatched primitives.
+		if (prim.cat)
+			prim.cat->prims << prim;
+		else
+			unmatched->prims << prim;
 	}
+}
+
+// =============================================================================
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// =============================================================================
+static void loadPrimitiveCatgories () {
+	g_PrimitiveCategories.clear ();
+	
+	File f (config::dirpath () + "primregexps.cfg", File::Read);
+	
+	if (!f)
+		f.open (":/data/primitive-categories.cfg", File::Read);
+	
+	if (!f)
+		critical ("Failed to open primitive categories!");
+	
+	if (f) {
+		PrimitiveCategory cat;
+		
+		for (str line : f) {
+			int colon;
+			
+			if (line.length () == 0 || line[0] == '#')
+				continue;
+			
+			if ((colon = line.indexOf (":")) == -1) {
+				if (cat.regexes.size () > 0)
+					g_PrimitiveCategories << cat;
+				
+				cat.regexes.clear ();
+				cat.prims.clear ();
+				cat.setName (line);
+			} else {
+				str cmd = line.left (colon);
+				
+				PrimitiveCategory::Type type = PrimitiveCategory::Filename;
+				if (cmd == "f") {
+					type = PrimitiveCategory::Filename;
+				} else if (cmd == "t") {
+					type = PrimitiveCategory::Title;
+				}
+				
+				QRegExp regex (line.mid (colon + 1));
+				PrimitiveCategory::RegexEntry entry = { regex, type };
+				cat.regexes << entry;
+			}
+		}
+		
+		if (cat.regexes.size () > 0)
+			g_PrimitiveCategories << cat;
+	}
+	
+	// Add a category for unmatched primitives
+	PrimitiveCategory cat;
+	cat.setName ("Other");
+	g_PrimitiveCategories << cat;
 }
\ No newline at end of file
--- a/src/primitives.h	Sat Jun 15 19:14:42 2013 +0300
+++ b/src/primitives.h	Sun Jun 16 00:10:11 2013 +0300
@@ -21,9 +21,34 @@
 
 #include "common.h"
 #include "types.h"
+#include <QRegExp>
 
+class PrimitiveCategory;
 struct Primitive {
 	str name, title;
+	PrimitiveCategory* cat;
+};
+
+class PrimitiveCategory {
+	PROPERTY (str, name, setName)
+	
+public:
+	enum Type {
+		Filename,
+		Title
+	};
+	
+	struct RegexEntry {
+		QRegExp regex;
+		Type type;
+	};
+	
+	typedef vector<RegexEntry>::it it;
+	typedef vector<RegexEntry>::c_it c_it;
+	
+	vector<RegexEntry> regexes;
+	vector<Primitive> prims;
+	static vector<Primitive> uncat;
 };
 
 // =============================================================================
@@ -52,9 +77,7 @@
 	vector<Primitive> m_prims;
 };
 
-extern vector<Primitive> g_Primitives;
-extern PrimitiveLister* g_activePrimLister;
-extern bool g_primListerMutex;
+extern vector<PrimitiveCategory> g_PrimitiveCategories;
 
 void loadPrimitives ();
 
--- a/src/types.h	Sat Jun 15 19:14:42 2013 +0300
+++ b/src/types.h	Sun Jun 16 00:10:11 2013 +0300
@@ -52,6 +52,7 @@
 typedef uint64_t uint64;
 
 template<class T> using initlist = std::initializer_list<T>;
+template<class T, class R> using pair = std::pair<T, R>;
 using std::size_t;
 
 enum Axis { X, Y, Z };
@@ -187,8 +188,9 @@
 		m_vect.erase (m_vect.begin () + pos);
 	}
 	
-	void push_back (const T& value) {
+	T& push_back (const T& value) {
 		m_vect.push_back (value);
+		return m_vect[m_vect.size () - 1];
 	}
 	
 	void push_back (const vector<T>& vals) {
@@ -205,14 +207,12 @@
 		return true;
 	}
 	
-	vector<T>& operator<< (const T& value) {
-		push_back (value);
-		return *this;
+	T& operator<< (const T& value) {
+		return push_back (value);
 	}
 	
-	vector<T>& operator<< (const vector<T>& vals) {
+	void operator<< (const vector<T>& vals) {
 		push_back (vals);
-		return *this;
 	}
 	
 	bool operator>> (T& value) {

mercurial