diff options
author | Don Bright <hugh.m.bright@gmail.com> | 2012-05-28 16:48:46 (GMT) |
---|---|---|
committer | Don Bright <hugh.m.bright@gmail.com> | 2012-05-28 16:48:46 (GMT) |
commit | dd2002a81673b3875ce8c4e8a61cb10278c4eb03 (patch) | |
tree | 7aaadf1c9b12cd37a7a913d3e76256f6406fa939 /src | |
parent | 4381762f5aa2e6a56258618e585e1510ead88684 (diff) | |
parent | 67eb2ebe90447e966dc1e08b91c43d937c521583 (diff) |
Tidy up code. Generate proper test png images. Merge branch 'master' into caliston1.
Conflicts:
src/PolySetCGALEvaluator.cc
Diffstat (limited to 'src')
-rw-r--r-- | src/CGALEvaluator.cc | 63 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron.cc | 2 | ||||
-rw-r--r-- | src/MainWindow.h | 16 | ||||
-rw-r--r-- | src/MainWindow.ui | 6 | ||||
-rw-r--r-- | src/ModuleCache.cc | 106 | ||||
-rw-r--r-- | src/ModuleCache.h | 23 | ||||
-rw-r--r-- | src/OpenCSGWarningDialog.cc | 2 | ||||
-rw-r--r-- | src/PolySetCGALEvaluator.cc | 199 | ||||
-rw-r--r-- | src/PolySetCGALEvaluator.h | 2 | ||||
-rw-r--r-- | src/context.cc | 1 | ||||
-rw-r--r-- | src/csgtermnormalizer.cc | 5 | ||||
-rw-r--r-- | src/dxfdata.cc | 8 | ||||
-rw-r--r-- | src/dxftess-cgal.cc | 2 | ||||
-rw-r--r-- | src/expr.cc | 4 | ||||
-rw-r--r-- | src/func.cc | 140 | ||||
-rw-r--r-- | src/glview.cc | 27 | ||||
-rw-r--r-- | src/lexer.l | 19 | ||||
-rw-r--r-- | src/mainwin.cc | 354 | ||||
-rw-r--r-- | src/module.cc | 37 | ||||
-rw-r--r-- | src/module.h | 13 | ||||
-rw-r--r-- | src/openscad.cc | 45 | ||||
-rw-r--r-- | src/openscad.h | 2 | ||||
-rw-r--r-- | src/parser.y | 120 | ||||
-rw-r--r-- | src/primitives.cc | 2 | ||||
-rw-r--r-- | src/system-gl.h | 1 | ||||
-rw-r--r-- | src/transform.cc | 9 | ||||
-rw-r--r-- | src/value.cc | 67 | ||||
-rw-r--r-- | src/value.h | 2 |
28 files changed, 830 insertions, 447 deletions
diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 15fa746..46f4cfa 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -43,11 +43,8 @@ CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) Traverser evaluate(*this, node, Traverser::PRE_AND_POSTFIX); evaluate.execute(); return this->root; - assert(this->visitedchildren.empty()); - } - else { - return CGALCache::instance()->get(this->tree.getIdString(node)); } + return CGALCache::instance()->get(this->tree.getIdString(node)); } bool CGALEvaluator::isCached(const AbstractNode &node) const @@ -252,31 +249,45 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node) // objects. So we convert in to our internal 2d data format, transform it, // tesselate it and create a new CGAL_Nef_polyhedron2 from it.. What a hack! - CGAL_Aff_transformation2 t( - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3)); - - DxfData *dd = N.convertToDxfData(); - for (size_t i=0; i < dd->points.size(); i++) { - CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]); - p = t.transform(p); - dd->points[i][0] = to_double(p.x()); - dd->points[i][1] = to_double(p.y()); + Eigen::Matrix2f testmat; + testmat << node.matrix(0,0), node.matrix(0,1), node.matrix(1,0), node.matrix(1,1); + if (testmat.determinant() == 0) { + PRINT("Warning: Scaling a 2D object with 0 - removing object"); + N.p2.reset(); + } + else { + CGAL_Aff_transformation2 t( + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), node.matrix(3,3)); + + DxfData *dd = N.convertToDxfData(); + for (size_t i=0; i < dd->points.size(); i++) { + CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd->points[i][0], dd->points[i][1]); + p = t.transform(p); + dd->points[i][0] = to_double(p.x()); + dd->points[i][1] = to_double(p.y()); + } + + PolySet ps; + ps.is2d = true; + dxf_tesselate(&ps, *dd, 0, true, false, 0); + + N = evaluateCGALMesh(ps); + delete dd; } - - PolySet ps; - ps.is2d = true; - dxf_tesselate(&ps, *dd, 0, true, false, 0); - - N = evaluateCGALMesh(ps); - delete dd; } else if (N.dim == 3) { - CGAL_Aff_transformation t( - node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3), - node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3)); - N.p3->transform(t); + if (node.matrix.matrix().determinant() == 0) { + PRINT("Warning: Scaling a 3D object with 0 - removing object"); + N.p3.reset(); + } + else { + CGAL_Aff_transformation t( + node.matrix(0,0), node.matrix(0,1), node.matrix(0,2), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,2), node.matrix(1,3), + node.matrix(2,0), node.matrix(2,1), node.matrix(2,2), node.matrix(2,3), node.matrix(3,3)); + N.p3->transform(t); + } } } else { diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 54c02e7..046ed12 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -62,6 +62,8 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o int CGAL_Nef_polyhedron::weight() const { + if (this->empty()) return 0; + size_t memsize = sizeof(CGAL_Nef_polyhedron); if (this->dim == 2) { memsize += sizeof(CGAL_Nef_polyhedron2) + diff --git a/src/MainWindow.h b/src/MainWindow.h index 5546290..a4835c2 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -27,10 +27,10 @@ public: double tval, fps, fsteps; QTimer *autoReloadTimer; - QString autoReloadInfo; + std::string autoReloadId; Context root_ctx; - AbstractModule *root_module; // Result of parsing + Module *root_module; // Result of parsing ModuleInstantiation root_inst; // Top level instance AbstractNode *absolute_root_node; // Result of tree evaluation AbstractNode *root_node; // Root if the root modifier (!) is used @@ -73,12 +73,16 @@ private slots: private: void openFile(const QString &filename); - void load(); + void refreshDocument(); AbstractNode *find_root_tag(AbstractNode *n); - void compile(bool procevents); + void updateTemporalVariables(); + bool fileChangedOnDisk(); + bool includesChanged(); + bool compileTopLevelDocument(bool reload); + bool compile(bool reload, bool procevents); void compileCSG(bool procevents); bool maybeSave(); - bool checkModified(); + bool checkEditorModified(); QString dumpCSGTree(AbstractNode *root); static void consoleOutput(const std::string &msg, void *userdata); void loadViewSettings(); @@ -157,7 +161,7 @@ public slots: void helpAbout(); void helpHomepage(); void helpManual(); - void helpOpenGL(); + void helpLibrary(); void quit(); void actionReloadCompile(); void checkAutoReload(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index f89e0d4..13bb226 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -222,7 +222,7 @@ <addaction name="helpActionAbout"/> <addaction name="helpActionHomepage"/> <addaction name="helpActionManual"/> - <addaction name="helpActionOpenGLInfo"/> + <addaction name="helpActionLibraryInfo"/> </widget> <addaction name="menu_File"/> <addaction name="menu_Edit"/> @@ -671,9 +671,9 @@ <string>Export as CSG...</string> </property> </action> - <action name="helpActionOpenGLInfo"> + <action name="helpActionLibraryInfo"> <property name="text"> - <string>OpenGL info</string> + <string>Library info</string> </property> </action> </widget> diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc new file mode 100644 index 0000000..70b1fb9 --- /dev/null +++ b/src/ModuleCache.cc @@ -0,0 +1,106 @@ +#include "ModuleCache.h" +#include "module.h" +#include "printutils.h" +#include "openscad.h" + +#include "boosty.h" +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> + +#include <stdio.h> +#include <fstream> +#include <sstream> +#include <time.h> +#include <sys/stat.h> + +/*! + FIXME: Implement an LRU scheme to avoid having an ever-growing module cache +*/ + +ModuleCache *ModuleCache::inst = NULL; + +static bool is_modified(const std::string &filename, const time_t &mtime) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + return (st.st_mtime > mtime); +} + +Module *ModuleCache::evaluate(const std::string &filename) +{ + Module *lib_mod = NULL; + + // Create cache ID + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + + std::string cache_id = str(boost::format("%x.%x") % st.st_mtime % st.st_size); + + // Lookup in cache + if (this->entries.find(filename) != this->entries.end() && + this->entries[filename].cache_id == cache_id) { +#ifdef DEBUG +// Causes too much debug output +// PRINTB("Using cached library: %s (%s)", filename % cache_id); +#endif + lib_mod = &(*this->entries[filename].module); + + BOOST_FOREACH(const Module::IncludeContainer::value_type &item, lib_mod->includes) { + if (is_modified(item.first, item.second)) { + lib_mod = NULL; + break; + } + } + } + + // If cache lookup failed (non-existing or old timestamp), compile module + if (!lib_mod) { +#ifdef DEBUG + if (this->entries.find(filename) != this->entries.end()) { + PRINTB("Recompiling cached library: %s (%s)", filename % cache_id); + } + else { + PRINTB("Compiling library '%s'.", filename); + } +#endif + + std::ifstream ifs(filename.c_str()); + if (!ifs.is_open()) { + PRINTB("WARNING: Can't open library file '%s'\n", filename); + return NULL; + } + std::string text((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + + print_messages_push(); + + cache_entry e = { NULL, cache_id }; + if (this->entries.find(filename) != this->entries.end()) { + delete this->entries[filename].module; + } + this->entries[filename] = e; + + std::string pathname = boosty::stringy(fs::path(filename).parent_path()); + lib_mod = dynamic_cast<Module*>(parse(text.c_str(), pathname.c_str(), 0)); + + if (lib_mod) { + this->entries[filename].module = lib_mod; + } else { + this->entries.erase(filename); + } + + print_messages_pop(); + } + + if (lib_mod) lib_mod->handleDependencies(); + + return lib_mod; +} + +void ModuleCache::clear() +{ + this->entries.clear(); +} + diff --git a/src/ModuleCache.h b/src/ModuleCache.h new file mode 100644 index 0000000..1e6373d --- /dev/null +++ b/src/ModuleCache.h @@ -0,0 +1,23 @@ +#include <string> +#include <boost/unordered_map.hpp> + +class ModuleCache +{ +public: + static ModuleCache *instance() { if (!inst) inst = new ModuleCache; return inst; } + class Module *evaluate(const std::string &filename); + size_t size() { return this->entries.size(); } + void clear(); + +private: + ModuleCache() {} + ~ModuleCache() {} + + static ModuleCache *inst; + + struct cache_entry { + class Module *module; + std::string cache_id; + }; + boost::unordered_map<std::string, cache_entry> entries; +}; diff --git a/src/OpenCSGWarningDialog.cc b/src/OpenCSGWarningDialog.cc index fdaaa50..5648576 100644 --- a/src/OpenCSGWarningDialog.cc +++ b/src/OpenCSGWarningDialog.cc @@ -1,7 +1,7 @@ #include "OpenCSGWarningDialog.h" #include "Preferences.h" -OpenCSGWarningDialog::OpenCSGWarningDialog(QWidget *parent) +OpenCSGWarningDialog::OpenCSGWarningDialog(QWidget*) { setupUi(this); diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 9e30bc5..3b272d2 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -16,19 +16,45 @@ #include "openscad.h" // get_fragments_from_r() #include <boost/foreach.hpp> -// This object 'visits' the Nef Polyhedron 3d, extracting 2d information -// from it for the projection( cut = true ) command. -// http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html +/* + +This Visitor is used in the 'cut' process. Essentially, one or more of +our 3d nef polyhedrons have been 'cut' by the xy-plane, forming a number +of polygons that are essentially 'flat'. This Visitor object, when +called repeatedly, collects those flat polygons together and forms a new +2d nef polyhedron out of them. It keeps track of the 'holes' so that +in the final resulting polygon, they are preserved properly. + +The output polygon is stored in the output_nefpoly2d variable. + +For more information on 3d + 2d nef polyhedrons, facets, halffacets, +facet cycles, etc, please see these websites: + +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron_3-Traits---Halffacet.html + +The halffacet iteration 'circulator' code is based on OGL_helper.h + +Why do we throw out all 'down' half-facets? Imagine a triangle in 3d +space - it has one 'half face' for one 'side' and another 'half face' for the +other 'side'. If we iterated over both half-faces we would get the same vertex +coordinates twice. Instead, we only need one side, in our case, we +chose the 'up' side. +*/ class NefShellVisitor_for_cut { public: std::stringstream out; CGAL_Nef_polyhedron2::Boundary boundary; - shared_ptr<CGAL_Nef_polyhedron2> tmpnef; - shared_ptr<CGAL_Nef_polyhedron2> nefpoly2d; - NefShellVisitor_for_cut() + shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d; + shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d; + CGAL::Direction_3<CGAL_Kernel3> up; + bool debug; + NefShellVisitor_for_cut(bool debug=false) { - nefpoly2d.reset( new CGAL_Nef_polyhedron2() ); + output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() ); boundary = CGAL_Nef_polyhedron2::INCLUDED; + up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1); + this->debug = debug; } std::string dump() { @@ -40,59 +66,38 @@ public: void visit( CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {} void visit( CGAL_Nef_polyhedron3::SFace_const_handle ) {} void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) { - // This method is fed each 'half-facet' of the Nef_polyhedron3 that's been intersected - // with the flat x-y plane. I.e. it's fed a bunch of flat 3d polygons with z==0 at all vertexes. - // It is fed both the 'up' pointing half-facets and the 'down' pointing half-facets. - // We only need one set of vertexes, so we skip all of the 'down' facets. - // - // The vertexes are recorded in lists called 'contours'. Those are 'joined' or 'intersected' - // with the main result polyhedron, 'nefpoly2d', depending on whether it's a "hole" contour - // or a "body" contour. - CGAL::Direction_3<CGAL_Kernel3> up(0,0,1); - CGAL::Plane_3<CGAL_Kernel3> plane = hfacet->plane(); - // out << " direction == up? " << ( plane.orthogonal_direction() == up ) << "\n"; - if ( plane.orthogonal_direction() != up ) { - // out << "direction == down. skipping"; + if ( hfacet->plane().orthogonal_direction() != this->up ) { + if (debug) out << "down facing half-facet. skipping\n"; return; } int numcontours = 0; - CGAL_Nef_polyhedron2::Point point; - CGAL_Nef_polyhedron3::Vertex_const_handle vertex; - CGAL_Nef_polyhedron3::Halfedge_const_handle halfedge; CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator i; - CGAL_Nef_polyhedron3::SHalfedge_const_handle first_halfedge, j; - for ( i = hfacet->facet_cycles_begin(); i != hfacet->facet_cycles_end(); ++i ) { - j = CGAL_Nef_polyhedron3::SHalfedge_const_handle( i ); - first_halfedge = j; + CGAL_forall_facet_cycles_of( i, hfacet ) { + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(i), c2(c1); std::list<CGAL_Nef_polyhedron2::Point> contour; - do { - // j->source() is a CGAL_Nef_polyhedron3::Nef_polyhedron_S2::SVertex, - // but SVertex is the same thing as CGAL_Nef_polyhedron3::Halfedge - // and Halfedge can give us an actual point. - halfedge = CGAL_Nef_polyhedron3::Halfedge_const_handle( j->source() ); - vertex = CGAL_Nef_polyhedron3::Vertex_const_handle( halfedge->source() ); - point = CGAL_Nef_polyhedron2::Point( vertex->point().x(), vertex->point().y() ); - contour.push_back( point ); - //out << " add xyz " << x << " "<< y << " " <<z << endl; - j = j->next(); - } while ( j != first_halfedge ); - tmpnef.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); + CGAL_For_all( c1, c2 ) { + CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->source()->point(); + CGAL_Nef_polyhedron2::Point point2d( point3d.x(), point3d.y() ); + contour.push_back( point2d ); + } + tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); if ( numcontours == 0 ) { - //out << " contour is a body. joining. " << contour.size() << " points.\n" ; - *nefpoly2d += *tmpnef; + if (debug) out << " contour is a body. make union(). " << contour.size() << " points.\n" ; + *output_nefpoly2d += *tmpnef2d; } else { - //out << " contour is a hole. intersecting. " << contour.size() << " points.\n"; - *nefpoly2d *= *tmpnef; + if (debug) out << " contour is a hole. make intersection(). " << contour.size() << " points.\n"; + *output_nefpoly2d *= *tmpnef2d; } numcontours++; - } // next facet cycle + } // next facet cycle (i.e. next contour) } // visit() }; PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator) : PolySetEvaluator(cgalevaluator.getTree()), cgalevaluator(cgalevaluator) { + this->debug = false; } PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) @@ -108,19 +113,24 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) } } if (sum.empty()) return NULL; + if (!sum.p3->is_simple()) { + if (!node.cut_mode) { + PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); + return new PolySet(); + } + } - PolySet *ps = new PolySet(); - PolySet *ps3 = NULL; - DxfData *dxf = NULL; - CGAL_Nef_polyhedron np; - ps->convexity = node.convexity; - ps->is2d = true; + CGAL_Nef_polyhedron nef_poly; if (node.cut_mode) { - CGAL_Nef_polyhedron3::Plane_3 plane = CGAL_Nef_polyhedron3::Plane_3( 0,0,1,0 ); - *sum.p3 = sum.p3->intersection( plane, CGAL_Nef_polyhedron3::PLANE_ONLY); + // intersect 'sum' with the x-y plane + CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3( 0,0,1,0 ); + *sum.p3 = sum.p3->intersection( xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY); + // Visit each polygon in sum.p3 and union/intersect into a 2d polygon (with holes) + // For info on Volumes, Shells, Facets, and the 'visitor' pattern, please see + // http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html NefShellVisitor_for_cut shell_visitor; CGAL_Nef_polyhedron3::Volume_const_iterator i; CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; @@ -131,20 +141,11 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) sum.p3->visit_shell_objects( sface_handle , shell_visitor ); } } - // std::cout << "shell visitor\n" << shell_visitor.dump() << "\n"; + if (debug) std::cout << "shell visitor\n" << shell_visitor.dump() << "\nshell visitor end\n"; - /*if (!sum.p3->is_simple()) { - PRINT("WARNING: Body of projection(cut = true) isn't valid 2-manifold! Modify your design.."); - goto cant_project_non_simple_polyhedron; - }*/ - - np.p2 = shell_visitor.nefpoly2d; - // std::cout << np.dump_p2() << "\n"; - np.dim = 2; - - ps3 = np.convertToPolyset(); - // std::cout << "----------\n" << ps3->dump() << "\n"; - if (!ps3) return NULL; + nef_poly.p2 = shell_visitor.output_nefpoly2d; + nef_poly.dim = 2; + if (debug) std::cout << "--\n" << nef_poly.dump_p2() << "\n"; // Extract polygons in the XY plane, ignoring all other polygons // FIXME: If the polyhedron is really thin, there might be unwanted polygons @@ -152,7 +153,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) // and cause a crash in CGALEvaluator::PolyReducer. The right solution is to // filter these polygons here. kintel 20120203. /* - Grid2d<int> conversion_grid(GRID_COARSE); + Grid2d<unsigned int> conversion_grid(GRID_COARSE); for (size_t i = 0; i < ps3->polygons.size(); i++) { for (size_t j = 0; j < ps3->polygons[i].size(); j++) { double x = ps3->polygons[i][j][0]; @@ -178,12 +179,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) // In projection mode all the triangles are projected manually into the XY plane else { - if (!sum.p3->is_simple()) { - PRINT("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); - goto cant_project_non_simple_polyhedron; - } - - ps3 = sum.convertToPolyset(); + PolySet *ps3 = sum.convertToPolyset(); if (!ps3) return NULL; for (size_t i = 0; i < ps3->polygons.size(); i++) { @@ -223,22 +219,22 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) plist.push_back(p); } // FIXME: Should the CGAL_Nef_polyhedron2 be cached? - if (np.empty()) { - np.dim = 2; - np.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); + if (nef_poly.empty()) { + nef_poly.dim = 2; + nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); } else { - (*np.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } } + delete ps3; } - delete ps3; - dxf = np.convertToDxfData(); - dxf_tesselate(ps, *dxf, 0, true, false, 0); - dxf_border_to_ps(ps, *dxf); - delete dxf; -cant_project_non_simple_polyhedron: + PolySet *ps = nef_poly.convertToPolyset(); + assert( ps != NULL ); + ps->convexity = node.convexity; + if (debug) std::cout << "--\n" << ps->dump() << "\n"; + return ps; } @@ -324,12 +320,14 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const LinearExtrudeNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (N.dim != 2) { - PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); - } - else { - if (sum.empty()) sum = N.copy(); - else sum += N; + if (!N.empty()) { + if (N.dim != 2) { + PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + } + else { + if (sum.empty()) sum = N.copy(); + else sum += N; + } } } @@ -422,12 +420,14 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const RotateExtrudeNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (N.dim != 2) { - PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); - } - else { - if (sum.empty()) sum = N.copy(); - else sum += N; + if (!N.empty()) { + if (N.dim != 2) { + PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); + } + else { + if (sum.empty()) sum = N.copy(); + else sum += N; + } } } @@ -493,15 +493,10 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD } for (int j = 0; j < fragments; j++) { - double a = (j*2*M_PI) / fragments; + double a = (j*2*M_PI) / fragments - M_PI/2; // start on the X axis for (size_t k = 0; k < dxf.paths[i].indices.size(); k++) { - if (dxf.points[dxf.paths[i].indices[k]][0] == 0) { - points[j][k][0] = 0; - points[j][k][1] = 0; - } else { - points[j][k][0] = dxf.points[dxf.paths[i].indices[k]][0] * sin(a); - points[j][k][1] = dxf.points[dxf.paths[i].indices[k]][0] * cos(a); - } + points[j][k][0] = dxf.points[dxf.paths[i].indices[k]][0] * sin(a); + points[j][k][1] = dxf.points[dxf.paths[i].indices[k]][0] * cos(a); points[j][k][2] = dxf.points[dxf.paths[i].indices[k]][1]; } } diff --git a/src/PolySetCGALEvaluator.h b/src/PolySetCGALEvaluator.h index dddcfc5..00e79f1 100644 --- a/src/PolySetCGALEvaluator.h +++ b/src/PolySetCGALEvaluator.h @@ -17,7 +17,7 @@ public: virtual PolySet *evaluatePolySet(const RotateExtrudeNode &node); virtual PolySet *evaluatePolySet(const CgaladvNode &node); virtual PolySet *evaluatePolySet(const RenderNode &node); - + bool debug; protected: PolySet *extrudeDxfData(const LinearExtrudeNode &node, class DxfData &dxf); PolySet *rotateDxfData(const RotateExtrudeNode &node, class DxfData &dxf); diff --git a/src/context.cc b/src/context.cc index b9e685c..f96a45b 100644 --- a/src/context.cc +++ b/src/context.cc @@ -162,6 +162,7 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation &inst) const } if (this->usedlibs_p) { BOOST_FOREACH(const ModuleContainer::value_type &m, *this->usedlibs_p) { + assert(m.second); if (m.second->modules.find(inst.name()) != m.second->modules.end()) { Context ctx(this->parent, m.second); return m.second->modules[inst.name()]->evaluate(&ctx, &inst); diff --git a/src/csgtermnormalizer.cc b/src/csgtermnormalizer.cc index 0e7a759..6600758 100644 --- a/src/csgtermnormalizer.cc +++ b/src/csgtermnormalizer.cc @@ -44,9 +44,10 @@ shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term) do { while (term && normalize_tail(term)) { } if (!term || term->type == CSGTerm::TYPE_PRIMITIVE) return term; - term->left = normalizePass(term->left); + if (term->left) term->left = normalizePass(term->left); } while (term->type != CSGTerm::TYPE_UNION && - (term->right->type != CSGTerm::TYPE_PRIMITIVE || term->left->type == CSGTerm::TYPE_UNION)); + ((term->right && term->right->type != CSGTerm::TYPE_PRIMITIVE) || + (term->left && term->left->type == CSGTerm::TYPE_UNION))); term->right = normalizePass(term->right); // FIXME: Do we need to take into account any transformation of item here? diff --git a/src/dxfdata.cc b/src/dxfdata.cc index 20fcc71..00b246f 100644 --- a/src/dxfdata.cc +++ b/src/dxfdata.cc @@ -519,7 +519,7 @@ void DxfData::fixup_path_direction() break; this->paths[i].is_inner = true; double min_x = this->points[this->paths[i].indices[0]][0]; - int min_x_point = 0; + size_t min_x_point = 0; for (size_t j = 1; j < this->paths[i].indices.size(); j++) { if (this->points[this->paths[i].indices[j]][0] < min_x) { min_x = this->points[this->paths[i].indices[j]][0]; @@ -527,9 +527,9 @@ void DxfData::fixup_path_direction() } } // rotate points if the path is in non-standard rotation - int b = min_x_point; - int a = b == 0 ? this->paths[i].indices.size() - 2 : b - 1; - int c = b == this->paths[i].indices.size() - 1 ? 1 : b + 1; + size_t b = min_x_point; + size_t a = b == 0 ? this->paths[i].indices.size() - 2 : b - 1; + size_t c = b == this->paths[i].indices.size() - 1 ? 1 : b + 1; double ax = this->points[this->paths[i].indices[a]][0] - this->points[this->paths[i].indices[b]][0]; double ay = this->points[this->paths[i].indices[a]][1] - this->points[this->paths[i].indices[b]][1]; double cx = this->points[this->paths[i].indices[c]][0] - this->points[this->paths[i].indices[b]][0]; diff --git a/src/dxftess-cgal.cc b/src/dxftess-cgal.cc index 15859bd..f221e3a 100644 --- a/src/dxftess-cgal.cc +++ b/src/dxftess-cgal.cc @@ -131,7 +131,7 @@ void dxf_tesselate(PolySet *ps, DxfData &dxf, double rot, bool up, bool /* do_tr // ..maybe it would be better to assert here. But this would // break compatibility with the glu tesselator that handled such // cases just fine. - PRINT( "WARNING: Duplicate vertex during Tessellation. Render may be incorrect." ); + PRINT( "WARNING: Duplicate vertex found during Tessellation. Render may be incorrect." ); continue; } diff --git a/src/expr.cc b/src/expr.cc index 66a0d11..671553c 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -86,8 +86,8 @@ Value Expression::evaluate(const Context *context) const return *v1.vec[i]; } if (v1.type == Value::STRING && v2.type == Value::NUMBER) { - int i = int(v2.num); - if (i >= 0 && i < v1.text.size()) + unsigned int i = int(v2.num); + if (i < v1.text.size()) return Value(v1.text.substr(i, 1)); } return Value(); diff --git a/src/func.cc b/src/func.cc index 6686cb9..0c9b450 100644 --- a/src/func.cc +++ b/src/func.cc @@ -33,6 +33,7 @@ #include "mathc99.h" #include <algorithm> #include "stl-utils.h" +#include "printutils.h" AbstractFunction::~AbstractFunction() { @@ -345,6 +346,144 @@ Value builtin_lookup(const Context *, const std::vector<std::string>&, const std return Value(high_v * f + low_v * (1-f)); } +/* + Pattern: + + "search" "(" ( match_value | list_of_match_values ) "," vector_of_vectors + ("," num_returns_per_match + ("," index_col_num )? )? + ")"; + match_value : ( Value::NUMBER | Value::STRING ); + list_of_values : "[" match_value ("," match_value)* "]"; + vector_of_vectors : "[" ("[" Value ("," Value)* "]")+ "]"; + num_returns_per_match : int; + index_col_num : int; + + Examples: + Index values return as list: + search("a","abcdabcd"); + - returns [0,4] + search("a","abcdabcd",1); + - returns [0] + search("e","abcdabcd",1); + - returns [] + search("a",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ]); + - returns [0,4] + + Search on different column; return Index values: + search(3,[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",3] ], 0, 1); + - returns [0,8] + + Search on list of values: + Return all matches per search vector element: + search("abc",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 0); + - returns [[0,4],[1,5],[2,6]] + + Return first match per search vector element; special case return vector: + search("abc",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 1); + - returns [0,1,2] + + Return first two matches per search vector element; vector of vectors: + search("abce",[ ["a",1],["b",2],["c",3],["d",4],["a",5],["b",6],["c",7],["d",8],["e",9] ], 2); + - returns [[0,4],[1,5],[2,6],[8]] + +*/ +Value builtin_search(const Context *, const std::vector<std::string>&, const std::vector<Value> &args) +{ + if (args.size() < 2) return Value(); + + const Value &findThis = args[0]; + const Value &searchTable = args[1]; + unsigned int num_returns_per_match = (args.size() > 2) ? args[2].num : 1; + unsigned int index_col_num = (args.size() > 3) ? args[3].num : 0; + + Value returnVector; + returnVector.type = Value::VECTOR; + + if (findThis.type == Value::NUMBER) { + unsigned int matchCount = 0; + Value *resultVector = new Value(); + resultVector->type = Value::VECTOR; + for (size_t j = 0; j < searchTable.vec.size(); j++) { + if (searchTable.vec[j]->vec[index_col_num]->type == Value::NUMBER && + findThis.num == searchTable.vec[j]->vec[index_col_num]->num) { + returnVector.append(new Value(double(j))); + matchCount++; + if (num_returns_per_match != 0 && matchCount >= num_returns_per_match) break; + } + } + } else if (findThis.type == Value::STRING) { + unsigned int searchTableSize; + if (searchTable.type == Value::STRING) searchTableSize = searchTable.text.size(); + else searchTableSize = searchTable.vec.size(); + for (size_t i = 0; i < findThis.text.size(); i++) { + unsigned int matchCount = 0; + Value *resultVector = new Value(); + resultVector->type = Value::VECTOR; + for (size_t j = 0; j < searchTableSize; j++) { + if ((searchTable.type == Value::VECTOR && + findThis.text[i] == searchTable.vec[j]->vec[index_col_num]->text[0]) || + (searchTable.type == Value::STRING && + findThis.text[i] == searchTable.text[j])) { + Value *resultValue = new Value(double(j)); + matchCount++; + if (num_returns_per_match==1) { + returnVector.append(resultValue); + break; + } else { + resultVector->append(resultValue); + } + if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break; + } + } + if (matchCount == 0) PRINTB(" search term not found: \"%s\"", findThis.text[i]); + if (num_returns_per_match == 0 || num_returns_per_match > 1) { + returnVector.append(resultVector); + } + } + } else if (findThis.type == Value::VECTOR) { + for (size_t i = 0; i < findThis.vec.size(); i++) { + unsigned int matchCount = 0; + Value *resultVector = new Value(); + resultVector->type = Value::VECTOR; + for (size_t j = 0; j < searchTable.vec.size(); j++) { + if ((findThis.vec[i]->type == Value::NUMBER && + searchTable.vec[j]->vec[index_col_num]->type == Value::NUMBER && + findThis.vec[i]->num == searchTable.vec[j]->vec[index_col_num]->num) || + (findThis.vec[i]->type == Value::STRING && + searchTable.vec[j]->vec[index_col_num]->type == Value::STRING && + findThis.vec[i]->text == searchTable.vec[j]->vec[index_col_num]->text)) { + Value *resultValue = new Value(double(j)); + matchCount++; + if (num_returns_per_match==1) { + returnVector.append(resultValue); + break; + } else { + resultVector->append(resultValue); + } + if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break; + } + } + if (num_returns_per_match == 1 && matchCount == 0) { + if (findThis.vec[i]->type == Value::NUMBER) { + PRINTB(" search term not found: %s",findThis.vec[i]->num); + } + else if (findThis.vec[i]->type == Value::STRING) { + PRINTB(" search term not found: \"%s\"",findThis.vec[i]->text); + } + returnVector.append(resultVector); + } + if (num_returns_per_match == 0 || num_returns_per_match > 1) { + returnVector.append(resultVector); + } + } + } else { + PRINTB(" search: none performed on input %s", findThis); + return Value(); + } + return returnVector; +} + #define QUOTE(x__) # x__ #define QUOTED(x__) QUOTE(x__) @@ -397,6 +536,7 @@ void register_builtin_functions() Builtins::init("ln", new BuiltinFunction(&builtin_ln)); Builtins::init("str", new BuiltinFunction(&builtin_str)); Builtins::init("lookup", new BuiltinFunction(&builtin_lookup)); + Builtins::init("search", new BuiltinFunction(&builtin_search)); Builtins::init("version", new BuiltinFunction(&builtin_version)); Builtins::init("version_num", new BuiltinFunction(&builtin_version_num)); } diff --git a/src/glview.cc b/src/glview.cc index 0f9ec5b..aa2e746 100644 --- a/src/glview.cc +++ b/src/glview.cc @@ -393,8 +393,6 @@ void GLView::paintGL() glRotated(object_rot_y, 0.0, 1.0, 0.0); glRotated(object_rot_z, 0.0, 0.0, 1.0); - glTranslated(object_trans_x, object_trans_y, object_trans_z); - // FIXME: Crosshairs and axes are lighted, this doesn't make sense and causes them // to change color based on view orientation. if (showcrosshairs) @@ -412,6 +410,8 @@ void GLView::paintGL() glEnd(); } + glTranslated(object_trans_x, object_trans_y, object_trans_z); + // Large gray axis cross inline with the model // FIXME: This is always gray - adjust color to keep contrast with background if (showaxes) @@ -589,14 +589,24 @@ void GLView::mouseMoveEvent(QMouseEvent *event) normalizeAngle(object_rot_y); normalizeAngle(object_rot_z); } else { - // Right button pans - // Shift-right zooms + // Right button pans in the xz plane + // Middle button pans in the xy plane + // Shift-right and Shift-middle zooms if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) { viewer_distance += (GLdouble)dy; } else { double mx = +(dx) * viewer_distance/1000; - double my = -(dy) * viewer_distance/1000; + double mz = -(dy) * viewer_distance/1000; + + double my = 0; + if (event->buttons() & Qt::MiddleButton) { + my = mz; + mz = 0; + // actually lock the x-position + // (turns out to be easier to use than xy panning) + mx = 0; + } Matrix3d aax, aay, aaz, tm3; aax = Eigen::AngleAxisd(-(object_rot_x/180) * M_PI, Vector3d::UnitX()); @@ -612,15 +622,10 @@ void GLView::mouseMoveEvent(QMouseEvent *event) Matrix4d vec; vec << 0, 0, 0, mx, - 0, 0, 0, 0, 0, 0, 0, my, + 0, 0, 0, mz, 0, 0, 0, 1 ; - if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) { - vec(0,3) = 0; - vec(1,3) = my; - vec(2,3) = 0; - } tm = tm * vec; object_trans_x += tm(0,3); object_trans_y += tm(1,3); diff --git a/src/lexer.l b/src/lexer.l index 5644ded..188046f 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -30,6 +30,7 @@ #include "printutils.h" #include "parsersettings.h" #include "parser_yacc.h" +#include "module.h" #include <assert.h> #include <boost/foreach.hpp> #include <boost/lexical_cast.hpp> @@ -52,7 +53,8 @@ int lexerget_lineno(void); static void yyunput(int, char*) __attribute__((unused)); #endif extern const char *parser_input_buffer; -extern const char *parser_source_path; +extern std::string parser_source_path; +extern Module *currmodule; #define YY_INPUT(buf,result,max_size) { \ if (yyin && yyin != stdin) { \ @@ -119,9 +121,12 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); } usepath = boosty::absolute(fs::path(get_librarydir()) / filename); } } - handle_dep(usepath.string()); - parserlval.text = strdup(usepath.string().c_str()); - return TOK_USE; + /* Only accept regular files which exists */ + if (usepath.has_parent_path() && fs::exists(usepath)) { + handle_dep(usepath.string()); + parserlval.text = strdup(usepath.string().c_str()); + return TOK_USE; + } } } @@ -215,8 +220,10 @@ void includefile() filepath.clear(); path_stack.push_back(finfo.parent_path()); - handle_dep(boosty::absolute(finfo).string()); - yyin = fopen(boosty::absolute(finfo).string().c_str(), "r"); + std::string fullname = boosty::absolute(finfo).string(); + handle_dep(fullname); + currmodule->registerInclude(fullname); + yyin = fopen(fullname.c_str(), "r"); if (!yyin) { PRINTB("WARNING: Can't open input file '%s'.", filename); path_stack.pop_back(); diff --git a/src/mainwin.cc b/src/mainwin.cc index 2fd75c9..087cb30 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -25,6 +25,7 @@ */ #include "PolySetCache.h" +#include "ModuleCache.h" #include "MainWindow.h" #include "openscad.h" // examplesdir #include "parsersettings.h" @@ -77,10 +78,10 @@ #include <fstream> #include <algorithm> +#include <boost/version.hpp> #include <boost/foreach.hpp> -#include <boost/lambda/lambda.hpp> -#include <boost/lambda/bind.hpp> -using namespace boost::lambda; +#include <boost/version.hpp> +#include <sys/stat.h> #ifdef ENABLE_CGAL @@ -94,6 +95,10 @@ using namespace boost::lambda; #endif // ENABLE_CGAL +#ifndef OPENCSG_VERSION_STRING +#define OPENCSG_VERSION_STRING "unknown, <1.3.2" +#endif + // Global application state unsigned int GuiLocker::gui_locked = 0; @@ -199,8 +204,8 @@ MainWindow::MainWindow(const QString &filename) autoReloadTimer->setSingleShot(false); connect(autoReloadTimer, SIGNAL(timeout()), this, SLOT(checkAutoReload())); - connect(e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); - connect(e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); + connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); + connect(this->e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); animate_panel->hide(); @@ -323,7 +328,7 @@ MainWindow::MainWindow(const QString &filename) connect(this->helpActionAbout, SIGNAL(triggered()), this, SLOT(helpAbout())); connect(this->helpActionHomepage, SIGNAL(triggered()), this, SLOT(helpHomepage())); connect(this->helpActionManual, SIGNAL(triggered()), this, SLOT(helpManual())); - connect(this->helpActionOpenGLInfo, SIGNAL(triggered()), this, SLOT(helpOpenGL())); + connect(this->helpActionLibraryInfo, SIGNAL(triggered()), this, SLOT(helpLibrary())); setCurrentOutput(); @@ -488,7 +493,7 @@ MainWindow::openFile(const QString &new_filename) #endif setFileName(new_filename); - load(); + refreshDocument(); updateRecentFiles(); } @@ -544,11 +549,11 @@ void MainWindow::updateRecentFiles() void MainWindow::updatedFps() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); animate_timer->stop(); if (fps_ok && fps > 0) { animate_timer->setSingleShot(false); - animate_timer->setInterval(int(1000 / e_fps->text().toDouble())); + animate_timer->setInterval(int(1000 / this->e_fps->text().toDouble())); animate_timer->start(); } } @@ -556,27 +561,28 @@ void MainWindow::updatedFps() void MainWindow::updateTVal() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok) { if (fps <= 0) { actionCompile(); } else { - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble() + 1/s; + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble() + 1/s; QString txt; txt.sprintf("%.5f", t >= 1.0 ? 0.0 : t); - e_tval->setText(txt); + this->e_tval->setText(txt); } } } -void MainWindow::load() +void MainWindow::refreshDocument() { setCurrentOutput(); if (!this->fileName.isEmpty()) { QFile file(this->fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - PRINTB("Failed to open file: %s (%s)", this->fileName.toStdString() % file.errorString().toStdString()); + PRINTB("Failed to open file %s: %s", + this->fileName.toStdString() % file.errorString().toStdString()); } else { QString text = QTextStream(&file).readAll(); @@ -598,42 +604,30 @@ AbstractNode *MainWindow::find_root_tag(AbstractNode *n) /*! Parse and evaluate the design => this->root_node + + Returns true if something was compiled, false if nothing was changed + and the root_node was left untouched. */ -void MainWindow::compile(bool procevents) +bool MainWindow::compile(bool reload, bool procevents) { - PRINT("Parsing design (AST generation)..."); - if (procevents) - QApplication::processEvents(); + if (!compileTopLevelDocument(reload)) return false; // Invalidate renderers before we kill the CSG tree this->glview->setRenderer(NULL); - if (this->opencsgRenderer) { - delete this->opencsgRenderer; - this->opencsgRenderer = NULL; - } - if (this->thrownTogetherRenderer) { - delete this->thrownTogetherRenderer; - this->thrownTogetherRenderer = NULL; - } + delete this->opencsgRenderer; + this->opencsgRenderer = NULL; + delete this->thrownTogetherRenderer; + this->thrownTogetherRenderer = NULL; // Remove previous CSG tree - if (this->root_module) { - delete this->root_module; - this->root_module = NULL; - } - - if (this->absolute_root_node) { - delete this->absolute_root_node; - this->absolute_root_node = NULL; - } + delete this->absolute_root_node; + this->absolute_root_node = NULL; this->root_raw_term.reset(); this->root_norm_term.reset(); - if (this->root_chain) { - delete this->root_chain; - this->root_chain = NULL; - } + delete this->root_chain; + this->root_chain = NULL; this->highlight_terms.clear(); delete this->highlights_chain; @@ -646,96 +640,43 @@ void MainWindow::compile(bool procevents) this->root_node = NULL; this->tree.setRoot(NULL); - // Initialize special variables - this->root_ctx.set_variable("$t", Value(e_tval->text().toDouble())); - - Value vpt; - vpt.type = Value::VECTOR; - vpt.append(new Value(-this->glview->object_trans_x)); - vpt.append(new Value(-this->glview->object_trans_y)); - vpt.append(new Value(-this->glview->object_trans_z)); - this->root_ctx.set_variable("$vpt", vpt); - - Value vpr; - vpr.type = Value::VECTOR; - vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); - root_ctx.set_variable("$vpr", vpr); - - // Parse - this->last_compiled_doc = editor->toPlainText(); - this->root_module = parse((this->last_compiled_doc + "\n" + - QString::fromStdString(commandline_commands)).toAscii().data(), - this->fileName.isEmpty() ? - "" : - QFileInfo(this->fileName).absolutePath().toLocal8Bit(), - false); - - // Error highlighting - if (this->highlighter) { - delete this->highlighter; - this->highlighter = NULL; - } - if (parser_error_pos >= 0) { - this->highlighter = new Highlighter(editor->document()); - } - - if (!this->root_module) { - if (!animate_panel->isVisible()) { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); - cursor.setPosition(parser_error_pos); -#else - QTextCursor cursor = editor->textCursor(); - cursor.setPosition(parser_error_pos); - editor->setTextCursor(cursor); -#endif + if (this->root_module) { + // Evaluate CSG tree + PRINT("Compiling design (CSG Tree generation)..."); + if (procevents) QApplication::processEvents(); + + AbstractNode::resetIndexCounter(); + this->root_inst = ModuleInstantiation(); + this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); + + if (this->absolute_root_node) { + // Do we have an explicit root node (! modifier)? + if (!(this->root_node = find_root_tag(this->absolute_root_node))) { + this->root_node = this->absolute_root_node; + } + // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers + this->tree.setRoot(this->root_node); + // Dump the tree (to initialize caches). + // FIXME: We shouldn't really need to do this explicitly.. + this->tree.getString(*this->root_node); } - goto fail; } - // Evaluate CSG tree - PRINT("Compiling design (CSG Tree generation)..."); - if (procevents) - QApplication::processEvents(); - - AbstractNode::resetIndexCounter(); - this->root_inst = ModuleInstantiation(); - this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); - - if (!this->absolute_root_node) - goto fail; - - // Do we have an explicit root node (! modifier)? - if (!(this->root_node = find_root_tag(this->absolute_root_node))) { - this->root_node = this->absolute_root_node; - } - // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers - this->tree.setRoot(this->root_node); - // Dump the tree (to initialize caches). - // FIXME: We shouldn't really need to do this explicitly.. - this->tree.getString(*this->root_node); - - if (1) { - PRINT("Compilation finished."); - if (procevents) - QApplication::processEvents(); - } else { -fail: + if (!this->root_node) { if (parser_error_pos < 0) { PRINT("ERROR: Compilation failed! (no top level object found)"); } else { PRINT("ERROR: Compilation failed!"); } - if (procevents) - QApplication::processEvents(); + if (procevents) QApplication::processEvents(); } + + return true; } /*! Generates CSG tree for OpenCSG evaluation. - Assumes that the design has been parsed and evaluated + Assumes that the design has been parsed and evaluated (this->root_node is set) */ void MainWindow::compileCSG(bool procevents) { @@ -991,7 +932,7 @@ void MainWindow::actionSaveAs() void MainWindow::actionReload() { - if (checkModified()) load(); + if (checkEditorModified()) refreshDocument(); } void MainWindow::hideEditor() @@ -1031,16 +972,127 @@ void MainWindow::pasteViewportRotation() cursor.insertText(txt); } -void MainWindow::checkAutoReload() +void MainWindow::updateTemporalVariables() +{ + this->root_ctx.set_variable("$t", Value(this->e_tval->text().toDouble())); + + Value vpt; + vpt.type = Value::VECTOR; + vpt.append(new Value(-this->glview->object_trans_x)); + vpt.append(new Value(-this->glview->object_trans_y)); + vpt.append(new Value(-this->glview->object_trans_z)); + this->root_ctx.set_variable("$vpt", vpt); + + Value vpr; + vpr.type = Value::VECTOR; + vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); + root_ctx.set_variable("$vpr", vpr); +} + +bool MainWindow::fileChangedOnDisk() { if (!this->fileName.isEmpty()) { - QString new_stinfo; - QFileInfo finfo(this->fileName); - new_stinfo = QString::number(finfo.size()) + QString::number(finfo.lastModified().toTime_t()); - if (new_stinfo != autoReloadInfo) - actionReloadCompile(); - autoReloadInfo = new_stinfo; + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(this->fileName.toLocal8Bit(), &st); + std::string newid = str(boost::format("%x.%x") % st.st_mtime % st.st_size); + + if (newid != this->autoReloadId) { + this->autoReloadId = newid; + return true; + } + } + return false; +} + +// FIXME: The following two methods are duplicated in ModuleCache.cc - refactor +static bool is_modified(const std::string &filename, const time_t &mtime) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + return (st.st_mtime > mtime); +} + +bool MainWindow::includesChanged() +{ + if (this->root_module) { + BOOST_FOREACH(const Module::IncludeContainer::value_type &item, this->root_module->includes) { + if (is_modified(item.first, item.second)) return true; + } } + return false; +} + +/*! + If reload is true, does a timestamp check on the document and tries to reload it. + Otherwise, just reparses the current document and any dependencies, updates the + GUI accordingly and populates this->root_module. + + Returns true if anything was compiled. +*/ +bool MainWindow::compileTopLevelDocument(bool reload) +{ + bool shouldcompiletoplevel = !reload; + + if ((reload && fileChangedOnDisk() && checkEditorModified()) || + includesChanged()) { + shouldcompiletoplevel = true; + refreshDocument(); + } + + if (shouldcompiletoplevel) { + console->clear(); + + updateTemporalVariables(); + + this->last_compiled_doc = editor->toPlainText(); + std::string fulltext = + this->last_compiled_doc.toStdString() + "\n" + commandline_commands; + + delete this->root_module; + this->root_module = NULL; + + this->root_module = parse(fulltext.c_str(), + this->fileName.isEmpty() ? + "" : + QFileInfo(this->fileName).absolutePath().toLocal8Bit(), + false); + + // Error highlighting + delete this->highlighter; + this->highlighter = NULL; + + if (!this->root_module) { + this->highlighter = new Highlighter(editor->document()); + + if (!animate_panel->isVisible()) { +#ifdef _QCODE_EDIT_ + QDocumentCursor cursor = editor->cursor(); + cursor.setPosition(parser_error_pos); +#else + QTextCursor cursor = editor->textCursor(); + cursor.setPosition(parser_error_pos); + editor->setTextCursor(cursor); +#endif + } + } + } + + bool changed = shouldcompiletoplevel; + if (this->root_module) { + changed |= this->root_module->handleDependencies(); + if (changed) PRINTB("Module cache size: %d modules", ModuleCache::instance()->size()); + } + + return changed; +} + +void MainWindow::checkAutoReload() +{ + if (!this->fileName.isEmpty()) actionReloadCompile(); } void MainWindow::autoReloadSet(bool on) @@ -1048,14 +1100,14 @@ void MainWindow::autoReloadSet(bool on) QSettings settings; settings.setValue("design/autoReload",designActionAutoReload->isChecked()); if (on) { - autoReloadInfo = QString(); + autoReloadId = ""; autoReloadTimer->start(200); } else { autoReloadTimer->stop(); } } -bool MainWindow::checkModified() +bool MainWindow::checkEditorModified() { if (editor->isContentModified()) { QMessageBox::StandardButton ret; @@ -1075,15 +1127,11 @@ void MainWindow::actionReloadCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - - if (!checkModified()) return; - - console->clear(); - - load(); - setCurrentOutput(); - compile(true); + + // PRINT("Parsing design (AST generation)..."); + // QApplication::processEvents(); + if (!compile(true, true)) return; if (this->root_node) compileCSG(true); // Go to non-CGAL view mode @@ -1105,11 +1153,12 @@ void MainWindow::actionCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - setCurrentOutput(); console->clear(); - compile(!viewActionAnimate->isChecked()); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, !viewActionAnimate->isChecked()); if (this->root_node) compileCSG(!viewActionAnimate->isChecked()); // Go to non-CGAL view mode @@ -1127,8 +1176,8 @@ void MainWindow::actionCompile() if (viewActionAnimate->isChecked() && e_dump->isChecked()) { QImage img = this->glview->grabFrameBuffer(); QString filename; - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble(); + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble(); filename.sprintf("frame%05d.png", int(round(s*t))); img.save(filename, "PNG"); } @@ -1146,7 +1195,9 @@ void MainWindow::actionRenderCGAL() setCurrentOutput(); console->clear(); - compile(true); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, true); if (!this->root_module || !this->root_node) { return; @@ -1449,7 +1500,7 @@ void MainWindow::actionFlushCaches() #endif dxf_dim_cache.clear(); dxf_cross_cache.clear(); - Module::clear_library_cache(); + ModuleCache::instance()->clear(); } void MainWindow::viewModeActionsUncheck() @@ -1556,7 +1607,7 @@ void MainWindow::animateUpdate() { if (animate_panel->isVisible()) { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok && fps <= 0 && !animate_timer->isActive()) { animate_timer->stop(); animate_timer->setSingleShot(true); @@ -1699,15 +1750,28 @@ MainWindow::helpManual() QDesktopServices::openUrl(QUrl("http://en.wikibooks.org/wiki/OpenSCAD_User_Manual")); } -void MainWindow::helpOpenGL() +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +void MainWindow::helpLibrary() { + QString libinfo; + libinfo.sprintf("Boost version: %s\n" + "Eigen version: %d.%d.%d\n" + "CGAL version: %s\n" + "OpenCSG version: %s\n\n", + BOOST_LIB_VERSION, + EIGEN_WORLD_VERSION, EIGEN_MAJOR_VERSION, EIGEN_MINOR_VERSION, + TOSTRING(CGAL_VERSION), + OPENCSG_VERSION_STRING); + if (!this->openglbox) { this->openglbox = new QMessageBox(QMessageBox::Information, - "OpenGL Info", "Detailed OpenGL Info", + "OpenGL Info", "Detailed Library Info", QMessageBox::Ok, this); } - this->openglbox->setDetailedText(this->glview->getRendererInfo()); + + this->openglbox->setDetailedText(libinfo + this->glview->getRendererInfo()); this->openglbox->show(); } diff --git a/src/module.cc b/src/module.cc index 6641ff7..fc849ff 100644 --- a/src/module.cc +++ b/src/module.cc @@ -25,13 +25,16 @@ */ #include "module.h" +#include "ModuleCache.h" #include "node.h" #include "context.h" #include "expression.h" #include "function.h" #include "printutils.h" + #include <boost/foreach.hpp> #include <sstream> +#include <sys/stat.h> AbstractModule::~AbstractModule() { @@ -201,7 +204,37 @@ std::string Module::dump(const std::string &indent, const std::string &name) con return dump.str(); } -void Module::clear_library_cache() +void Module::registerInclude(const std::string &filename) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + this->includes[filename] = st.st_mtime; +} + +/*! + Check if any dependencies have been modified and recompile them. + Returns true if anything was recompiled. +*/ +bool Module::handleDependencies() { - Module::libs_cache.clear(); + bool changed = false; + // Iterating manually since we want to modify the container while iterating + Module::ModuleContainer::iterator iter = this->usedlibs.begin(); + while (iter != this->usedlibs.end()) { + Module::ModuleContainer::iterator curr = iter++; + Module *oldmodule = curr->second; + curr->second = ModuleCache::instance()->evaluate(curr->first); + if (curr->second != oldmodule) { + changed = true; +#ifdef DEBUG + PRINTB_NOCACHE(" %s: %p", curr->first % curr->second); +#endif + } + if (!curr->second) { + PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", curr->first); + this->usedlibs.erase(curr); + } + } + return changed; } diff --git a/src/module.h b/src/module.h index 6c6529b..cd25287 100644 --- a/src/module.h +++ b/src/module.h @@ -3,6 +3,7 @@ #include <string> #include <vector> +#include <list> #include <boost/unordered_map.hpp> #include "value.h" @@ -65,11 +66,12 @@ public: void addChild(ModuleInstantiation *ch) { this->children.push_back(ch); } - static Module *compile_library(const std::string &filename); - static void clear_library_cache(); - typedef boost::unordered_map<std::string, class Module*> ModuleContainer; ModuleContainer usedlibs; + void registerInclude(const std::string &filename); + typedef boost::unordered_map<std::string, time_t> IncludeContainer; + IncludeContainer includes; + bool handleDependencies(); std::vector<std::string> assignments_var; std::vector<Expression*> assignments_expr; @@ -87,11 +89,6 @@ public: protected: private: - struct libs_cache_ent { - Module *mod; - std::string cache_id, msg; - }; - static boost::unordered_map<std::string, libs_cache_ent> libs_cache; }; #endif diff --git a/src/openscad.cc b/src/openscad.cc index 980e2af..7fe054f 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -149,9 +149,14 @@ int main(int argc, char **argv) all_options.add(desc).add(hidden); po::variables_map vm; - po::store(po::command_line_parser(argc, argv).options(all_options).positional(p).run(), vm); -// po::notify(vm); - + try { + po::store(po::command_line_parser(argc, argv).options(all_options).positional(p).run(), vm); + } + catch(std::exception &e) { // Catches e.g. unknown options + fprintf(stderr, "%s\n", e.what()); + help(argv[0]); + } + if (vm.count("help")) help(argv[0]); if (vm.count("version")) version(); @@ -255,34 +260,28 @@ int main(int argc, char **argv) Context root_ctx; register_builtin(root_ctx); - AbstractModule *root_module; + Module *root_module; ModuleInstantiation root_inst; AbstractNode *root_node; handle_dep(filename); - FILE *fp = fopen(filename, "rt"); - if (!fp) { - fprintf(stderr, "Can't open input file `%s'!\n", filename); + + std::ifstream ifs(filename); + if (!ifs.is_open()) { + fprintf(stderr, "Can't open input file '%s'!\n", filename); exit(1); - } else { - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - text << "\n" << commandline_commands; - fs::path abspath = boosty::absolute( filename ); - std::string fname = boosty::stringy( abspath ); - root_module = parse(text.str().c_str(), fname.c_str(), false); - if (!root_module) exit(1); } + std::string text((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + text += "\n" + commandline_commands; + fs::path abspath = boosty::absolute(filename); + std::string parentpath = boosty::stringy(abspath.parent_path()); + root_module = parse(text.c_str(), parentpath.c_str(), false); + if (!root_module) exit(1); + root_module->handleDependencies(); - fs::path fpath = boosty::absolute( fs::path(filename) ); + fs::path fpath = boosty::absolute(fs::path(filename)); fs::path fparent = fpath.parent_path(); - fs::current_path( fparent ); + fs::current_path(fparent); AbstractNode::resetIndexCounter(); root_node = root_module->evaluate(&root_ctx, &root_inst); diff --git a/src/openscad.h b/src/openscad.h index dab14cd..8b49ba2 100644 --- a/src/openscad.h +++ b/src/openscad.h @@ -27,7 +27,7 @@ #ifndef OPENSCAD_H #define OPENSCAD_H -extern class AbstractModule *parse(const char *text, const char *path, int debug); +extern class Module *parse(const char *text, const char *path, int debug); extern int get_fragments_from_r(double r, double fn, double fs, double fa); #include <string> diff --git a/src/parser.y b/src/parser.y index 15a754b..195e7a8 100644 --- a/src/parser.y +++ b/src/parser.y @@ -43,7 +43,7 @@ #include <boost/foreach.hpp> #include <boost/filesystem.hpp> -using namespace boost::filesystem; +namespace fs = boost::filesystem; #include "boosty.h" int parser_error_pos = -1; @@ -56,7 +56,7 @@ int lexerlex_destroy(void); int lexerlex(void); std::vector<Module*> module_stack; -Module *module; +Module *currmodule; class ArgContainer { public: @@ -133,7 +133,7 @@ public: input: /* empty */ | - TOK_USE { module->usedlibs[$1] = NULL; } input | + TOK_USE { currmodule->usedlibs[$1] = NULL; } input | statement input ; inner_input: @@ -145,37 +145,37 @@ statement: '{' inner_input '}' | module_instantiation { if ($1) { - module->addChild($1); + currmodule->addChild($1); } else { delete $1; } } | TOK_ID '=' expr ';' { bool add_new_assignment = true; - for (size_t i = 0; i < module->assignments_var.size(); i++) { - if (module->assignments_var[i] != $1) + for (size_t i = 0; i < currmodule->assignments_var.size(); i++) { + if (currmodule->assignments_var[i] != $1) continue; - delete module->assignments_expr[i]; - module->assignments_expr[i] = $3; + delete currmodule->assignments_expr[i]; + currmodule->assignments_expr[i] = $3; add_new_assignment = false; } if (add_new_assignment) { - module->assignments_var.push_back($1); - module->assignments_expr.push_back($3); + currmodule->assignments_var.push_back($1); + currmodule->assignments_expr.push_back($3); free($1); } } | TOK_MODULE TOK_ID '(' arguments_decl optional_commas ')' { - Module *p = module; - module_stack.push_back(module); - module = new Module(); - p->modules[$2] = module; - module->argnames = $4->argnames; - module->argexpr = $4->argexpr; + Module *p = currmodule; + module_stack.push_back(currmodule); + currmodule = new Module(); + p->modules[$2] = currmodule; + currmodule->argnames = $4->argnames; + currmodule->argexpr = $4->argexpr; free($2); delete $4; } statement { - module = module_stack.back(); + currmodule = module_stack.back(); module_stack.pop_back(); } | TOK_FUNCTION TOK_ID '(' arguments_decl optional_commas ')' '=' expr { @@ -183,7 +183,7 @@ statement: func->argnames = $4->argnames; func->argexpr = $4->argexpr; func->expr = $8; - module->functions[$2] = func; + currmodule->functions[$2] = func; free($2); delete $4; } ';' ; @@ -560,101 +560,33 @@ void yyerror (char const *s) { // FIXME: We leak memory on parser errors... PRINTB("Parser error in line %d: %s\n", lexerget_lineno() % s); - module = NULL; + currmodule = NULL; } extern void lexerdestroy(); extern FILE *lexerin; extern const char *parser_input_buffer; const char *parser_input_buffer; -const char *parser_source_path; +std::string parser_source_path; -AbstractModule *parse(const char *text, const char *path, int debug) +Module *parse(const char *text, const char *path, int debug) { lexerin = NULL; parser_error_pos = -1; parser_input_buffer = text; - parser_source_path = path; + parser_source_path = std::string(path); module_stack.clear(); - module = new Module(); + Module *rootmodule = currmodule = new Module(); + // PRINTB_NOCACHE("New module: %s %p", "root" % rootmodule); parserdebug = debug; parserparse(); lexerdestroy(); lexerlex_destroy(); - if (!module) - return NULL; - - // Iterating manually since we want to modify the container while iterating - Module::ModuleContainer::iterator iter = module->usedlibs.begin(); - while (iter != module->usedlibs.end()) { - Module::ModuleContainer::iterator curr = iter++; - curr->second = Module::compile_library(curr->first); - if (!curr->second) { - PRINTB("WARNING: Failed to compile library '%s'.", curr->first); - module->usedlibs.erase(curr); - } - } + if (!rootmodule) return NULL; parser_error_pos = -1; - return module; -} - -boost::unordered_map<std::string, Module::libs_cache_ent> Module::libs_cache; - -Module *Module::compile_library(const std::string &filename) -{ - struct stat st; - memset(&st, 0, sizeof(struct stat)); - stat(filename.c_str(), &st); - - std::stringstream idstream; - idstream << std::hex << st.st_mtime << "." << st.st_size; - std::string cache_id = idstream.str(); - - if (libs_cache.find(filename) != libs_cache.end() && libs_cache[filename].cache_id == cache_id) { - PRINTB("%s", libs_cache[filename].msg); - return &(*libs_cache[filename].mod); - } - - FILE *fp = fopen(filename.c_str(), "rt"); - if (!fp) { - fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str()); - return NULL; - } - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - - print_messages_push(); - - PRINTB("Compiling library '%s'.", filename); - libs_cache_ent e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" }; - if (libs_cache.find(filename) != libs_cache.end()) - delete libs_cache[filename].mod; - libs_cache[filename] = e; - - Module *backup_mod = module; - std::string pathname = boosty::stringy( fs::path(filename).parent_path() ); - Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0)); - module = backup_mod; - - if (lib_mod) { - libs_cache[filename].mod = lib_mod; - libs_cache[filename].msg = print_messages_stack.back(); - } else { - libs_cache.erase(filename); - } - - print_messages_pop(); - - return lib_mod; + return rootmodule; } - diff --git a/src/primitives.cc b/src/primitives.cc index feaa1a4..ce52550 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -530,7 +530,7 @@ sphere_next_r2: { dd.paths.push_back(DxfData::Path()); for (size_t j=0; j<this->paths.vec[i]->vec.size(); j++) { - int idx = this->paths.vec[i]->vec[j]->num; + unsigned int idx = this->paths.vec[i]->vec[j]->num; if (idx < dd.points.size()) { dd.paths.back().indices.push_back(idx); } diff --git a/src/system-gl.h b/src/system-gl.h index 0377b72..d7de3c6 100644 --- a/src/system-gl.h +++ b/src/system-gl.h @@ -7,6 +7,7 @@ #include <OpenGL/OpenGL.h> #else #include <GL/gl.h> + #include <GL/glu.h> #ifdef _WIN32 #include <windows.h> // For the CALLBACK macro #endif diff --git a/src/transform.cc b/src/transform.cc index f5038b1..c2ac194 100644 --- a/src/transform.cc +++ b/src/transform.cc @@ -87,11 +87,10 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti { Vector3d scalevec(1,1,1); Value v = c.lookup_variable("v"); - v.getnum(scalevec[0]); - v.getnum(scalevec[1]); - v.getnum(scalevec[2]); - v.getv3(scalevec[0], scalevec[1], scalevec[2]); - if (scalevec[2] == 0) scalevec[2] = 1; + if (!v.getv3(scalevec[0], scalevec[1], scalevec[2], 1.0)) { + double num; + if (v.getnum(num)) scalevec.setConstant(num); + } node->matrix.scale(scalevec); } else if (this->type == ROTATE) diff --git a/src/value.cc b/src/value.cc index 47fac1e..93c4d5e 100644 --- a/src/value.cc +++ b/src/value.cc @@ -30,6 +30,7 @@ #include <sstream> #include <QDir> #include <boost/foreach.hpp> +#include "printutils.h" Value::Value() { @@ -157,6 +158,64 @@ Value Value::operator * (const Value &v) const if (this->type == NUMBER && v.type == NUMBER) { return Value(this->num * v.num); } + if (this->type == VECTOR && v.type == VECTOR && this->vec.size() == v.vec.size() ) { + if ( this->vec[0]->type == NUMBER && v.vec[0]->type == NUMBER ) { + // Vector dot product. + double r=0.0; + for (size_t i=0; i <this->vec.size(); i++) { + if ( this->vec[i]->type != NUMBER || v.vec[i]->type != NUMBER ) return Value(); + r = r + (this->vec[i]->num * v.vec[i]->num); + } + return Value(r); + } else if ( this->vec[0]->type == VECTOR && v.vec[0]->type == NUMBER ) { + // Matrix * Vector + Value r; + r.type = VECTOR; + for ( size_t i=0; i < this->vec.size(); i++) { + double r_e=0.0; + if ( this->vec[i]->vec.size() != v.vec.size() ) return Value(); + for ( size_t j=0; j < this->vec[i]->vec.size(); j++) { + if ( this->vec[i]->vec[j]->type != NUMBER || v.vec[i]->type != NUMBER ) return Value(); + r_e = r_e + (this->vec[i]->vec[j]->num * v.vec[j]->num); + } + r.vec.push_back(new Value(r_e)); + } + return r; + } else if (this->vec[0]->type == NUMBER && v.vec[0]->type == VECTOR ) { + // Vector * Matrix + Value r; + r.type = VECTOR; + for ( size_t i=0; i < v.vec[0]->vec.size(); i++) { + double r_e=0.0; + for ( size_t j=0; j < v.vec.size(); j++) { + if ( v.vec[j]->vec.size() != v.vec[0]->vec.size() ) return Value(); + if ( this->vec[j]->type != NUMBER || v.vec[j]->vec[i]->type != NUMBER ) return Value(); + r_e = r_e + (this->vec[j]->num * v.vec[j]->vec[i]->num); + } + r.vec.push_back(new Value(r_e)); + } + return r; + } + } + if (this->type == VECTOR && v.type == VECTOR && this->vec[0]->type == VECTOR && v.vec[0]->type == VECTOR && this->vec[0]->vec.size() == v.vec.size() ) { + // Matrix * Matrix + Value rrow; + rrow.type = VECTOR; + for ( size_t i=0; i < this->vec.size(); i++ ) { + Value * rcol=new Value(); + rcol->type = VECTOR; + for ( size_t j=0; j < this->vec.size(); j++ ) { + double r_e=0.0; + for ( size_t k=0; k < v.vec.size(); k++ ) { + r_e = r_e + (this->vec[i]->vec[k]->num * v.vec[k]->vec[j]->num); + } + // PRINTB(" r_e = %s",r_e); + rcol->vec.push_back(new Value(r_e)); + } + rrow.vec.push_back(rcol); + } + return rrow; + } return Value(); } @@ -300,11 +359,11 @@ bool Value::getv2(double &x, double &y) const return true; } -bool Value::getv3(double &x, double &y, double &z) const +bool Value::getv3(double &x, double &y, double &z, double defaultval) const { if (this->type == VECTOR && this->vec.size() == 2) { if (getv2(x, y)) { - z = 0; + z = defaultval; return true; } return false; @@ -367,6 +426,10 @@ std::string Value::toString() const // Quick and dirty hack to work around floating point rounding differences // across platforms for testing purposes. { + if (this->num != this->num) { // Fix for avoiding nan vs. -nan across platforms + stream << "nan"; + break; + } std::stringstream tmp; tmp.precision(12); tmp.setf(std::ios_base::fixed); diff --git a/src/value.h b/src/value.h index a2cfbdf..4a67fbc 100644 --- a/src/value.h +++ b/src/value.h @@ -73,7 +73,7 @@ public: bool getnum(double &v) const; bool getv2(double &x, double &y) const; - bool getv3(double &x, double &y, double &z) const; + bool getv3(double &x, double &y, double &z, double defaultval = 0.0) const; std::string toString() const; |