diff options
274 files changed, 6413 insertions, 2388 deletions
diff --git a/doc/OpenSCAD-compile.graffle b/doc/OpenSCAD-compile.graffle Binary files differindex 14a2ccc..bb505c3 100644 --- a/doc/OpenSCAD-compile.graffle +++ b/doc/OpenSCAD-compile.graffle diff --git a/doc/OpenSCAD-csg.graffle b/doc/OpenSCAD-csg.graffle Binary files differnew file mode 100644 index 0000000..ca0a0ff --- /dev/null +++ b/doc/OpenSCAD-csg.graffle diff --git a/doc/OpenSCAD-polygons.graffle b/doc/OpenSCAD-polygons.graffle Binary files differindex 3e8b5bd..40df7ab 100644 --- a/doc/OpenSCAD-polygons.graffle +++ b/doc/OpenSCAD-polygons.graffle diff --git a/doc/TODO.txt b/doc/TODO.txt index 49cacd7..74e1f3f 100644 --- a/doc/TODO.txt +++ b/doc/TODO.txt @@ -4,9 +4,6 @@ BUGS o Some invalid DXF data gets pass the import checks and breaks the tessing code o Tesselation via GLU sometimes produces strange results o Export STL: Exports existing CGAL model even though the current model is changed, but not CGAL rendered -o It's now possible to start a new rendering while one is already running (which call processEvents()) - -> turn off most (or all) interaction while rendering - -> Lock all or only one MainWindow (MDI)? o Look into the polygon winding and rotate_extrude() problem reported by Britton o CGAL Aff_transformation_3 doesn't support non-affine transformations (non-aff-matrix.scad) @@ -60,7 +57,6 @@ o Preferences - OpenGL params - Default language feature settings - Make the library search path configurable? -o Export etc.: automatically add missing extension as in SaveAs o MDI - Think about how to do MDI the right way - Ctrl-W should close the current dialog, not the current main window @@ -178,6 +174,14 @@ o Grammar - A random(seed) function - import_*() -> *_import() (consistent prefix vs. postfix) - linear_extrude()/rotate_extrude(): Cumbersome names? -> (extrude, revolve, lathe, sweep ?) +o Hollow donut problem + When extruding a 2D CSG tree (e.g. a polygon with a hole), the hole + information is lost when performing the extrusion. For linear + extrusions, this has only a minor visual impact, but for rotate + extrusion, the resulting CGAL models will lose the hole. The OpenCSG + rendering keeps the hole, but renders slightly incorrect. + + IDEAS FOR LANGUAGE CHANGES -------------------------- @@ -200,6 +204,9 @@ o dxflinextrude and dxfrotextrude could share code o Consider decoupling DXF-specific functionality from the 2D subsystem o Visitation refactoring - Make AbstractNode members private/protected? +o Consider evaluating all referenced files relative to the document path instead + of being absolute. This would e.g. make regression testing of dumpcaching easier. + This would require us to pass a document contect to all traversal methods though. BUILD SYSTEM ------------ @@ -219,6 +226,8 @@ o Write a simple test script that collects verified and current STL renderings o Write simple driver scripts for comparing output of above command o Collect "all" available OpenSCAD scripts from the internets and run the integration tests on them all +o dumptest tests: + - filename are dumped as absolute filenames - this will fail on other systems o Write a regression test for the hexagonal cylinder orientation issue INFRASTRUCTURE @@ -230,6 +239,5 @@ MISC ---- o Streamline the cmd-line interface a bit - Implicit output file format - o Write checklists for typical extension work (add new module, add new function) -> make sure new test files are added diff --git a/doc/testing.txt b/doc/testing.txt new file mode 100644 index 0000000..417a3b9 --- /dev/null +++ b/doc/testing.txt @@ -0,0 +1,28 @@ +Running regression tests: +------------------------- + +Prerequisites: cmake, python + +cd tests +mkdir build +cd build +cmake .. +make +make test + + +Adding a new regression test: +------------------------------ + +1) create a test file at an appropriate location under testdata/ +2) if the test is non-obvious, create a human readable description of the test in the same directory (e.g testdata/scad/mytest.txt) +3) if a new test app was written, this must be added to tests/CMakeLists +4) run the test with the environment variable TEST_GENERATE=1, e.g.: + $ TEST_GENERATE=1 ctest -R mytest + (this will generate a mytest-expected.txt file which is used for regression testing) +5) manually verify that the output is correct (test-data/scad/mytest-expected.txt) +6) run the test normally and verify that it passes: + $ ctest -R mytest + +Note that test files which don't have an *-expected.<suffix> file will +be ignored for the test apps in question. @@ -1 +1 @@ -set environment DYLD_LIBRARY_PATH=/Users/kintel/code/metalab/checkout/OpenSCAD/libraries/install/lib +set environment DYLD_LIBRARY_PATH=/Users/kintel/code/OpenSCAD/libraries/install/lib diff --git a/openscad.pro b/openscad.pro index 61000f1..a4381be 100644 --- a/openscad.pro +++ b/openscad.pro @@ -140,12 +140,30 @@ HEADERS += src/renderer.h \ src/highlighter.h \ src/module.h \ src/node.h \ + src/csgnode.h \ + src/dxflinextrudenode.h \ + src/dxfrotextrudenode.h \ + src/projectionnode.h \ + src/importnode.h \ + src/transformnode.h \ + src/rendernode.h \ src/openscad.h \ src/polyset.h \ src/printutils.h \ src/value.h \ src/progress.h \ src/editor.h \ + src/visitor.h \ + src/state.h \ + src/traverser.h \ + src/nodecache.h \ + src/nodedumper.h \ + src/CGALEvaluator.h \ + src/PolySetEvaluator.h \ + src/PolySetCGALEvaluator.h \ + src/CSGTermEvaluator.h \ + src/myqhash.h \ + src/Tree.h \ src/mathc99.h SOURCES += src/openscad.cc \ @@ -187,6 +205,14 @@ SOURCES += src/openscad.cc \ src/Preferences.cc \ src/progress.cc \ src/editor.cc \ + src/traverser.cc \ + src/nodedumper.cc \ + src/CGALEvaluator.cc \ + src/PolySetEvaluator.cc \ + src/PolySetCGALEvaluator.cc \ + src/CSGTermEvaluator.cc \ + src/qhash.cc \ + src/Tree.cc \ src/mathc99.cc macx { diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh index 6de5331..d06c90e 100755 --- a/scripts/macosx-build-dependencies.sh +++ b/scripts/macosx-build-dependencies.sh @@ -135,6 +135,7 @@ build_glew() version=$1 echo "Building GLEW" $version "..." cd $BASEDIR/src + rm -r glew-* curl -LO http://downloads.sourceforge.net/project/glew/glew/$version/glew-$version.tgz tar xzf glew-$version.tgz cd glew-$version @@ -152,6 +153,7 @@ build_opencsg() curl -O http://www.opencsg.org/OpenCSG-$version.tar.gz tar xzf OpenCSG-$version.tar.gz cd OpenCSG-$version + patch -p1 < $OPENSCADDIR/patches/OpenCSG-$version-FBO.patch patch -p1 < $OPENSCADDIR/patches/OpenCSG-$version-MacOSX-port.patch MACOSX_DEPLOY_DIR=$DEPLOYDIR qmake -r CONFIG+="x86 x86_64" make install diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc new file mode 100644 index 0000000..021537c --- /dev/null +++ b/src/CGALEvaluator.cc @@ -0,0 +1,655 @@ +#include "CGALEvaluator.h" +#include "visitor.h" +#include "state.h" +#include "module.h" // FIXME: Temporarily for ModuleInstantiation +#include "printutils.h" + +#include "csgnode.h" +#include "transformnode.h" +#include "polyset.h" +#include "dxfdata.h" +#include "dxftess.h" + +#include <CGAL/assertions_behaviour.h> +#include <CGAL/exceptions.h> + +#include <string> +#include <map> +#include <list> +#include <sstream> +#include <iostream> +#include <assert.h> +#include <QRegExp> + +CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const AbstractNode &node) +{ + if (!isCached(node)) { + Traverser evaluate(*this, node, Traverser::PRE_AND_POSTFIX); + evaluate.execute(); + assert(isCached(node)); + } + return this->cache[this->tree.getString(node)]; +} + +bool CGALEvaluator::isCached(const AbstractNode &node) const +{ + return this->cache.contains(this->tree.getString(node)); +} + +/*! + Modifies target by applying op to target and src: + target = target [op] src + */ +void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CsgOp op) +{ + if (target.dim != 2 && target.dim != 3) { + assert(false && "Dimension of Nef polyhedron must be 2 or 3"); + } + + if (target.dim == 2) { + switch (op) { + case UNION: + target.p2 += src.p2; + break; + case INTERSECTION: + target.p2 *= src.p2; + break; + case DIFFERENCE: + target.p2 -= src.p2; + break; + case MINKOWSKI: + target.p2 = minkowski2(target.p2, src.p2); + break; + case HULL: + //FIXME: Port convex hull to a binary operator or process it all in the end somehow + // target.p2 = convexhull2(target.p2, src.p2); + // target.p2 = convexhull2(polys); + break; + } + } + else if (target.dim == 3) { + switch (op) { + case UNION: + target.p3 += src.p3; + break; + case INTERSECTION: + target.p3 *= src.p3; + break; + case DIFFERENCE: + target.p3 -= src.p3; + break; + case MINKOWSKI: + target.p3 = minkowski3(target.p3, src.p3); + break; + case HULL: + // FIXME: Print warning: hull() not supported in 3D + break; + } + } +} + +/*! + FIXME: Let caller insert into the cache since caller might modify the result + (e.g. transform) +*/ +void CGALEvaluator::applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op) +{ + CGAL_Nef_polyhedron N; + if (this->visitedchildren[node.index()].size() > 0) { + bool first = true; + for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); + iter != this->visitedchildren[node.index()].end(); + iter++) { + const AbstractNode *chnode = iter->first; + const string &chcacheid = iter->second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->tag_background) continue; + assert(isCached(*chnode)); + if (first) { + N = this->cache[chcacheid]; + // If the first child(ren) are empty (e.g. echo) nodes, + // ignore them (reset first flag) + if (N.dim != 0) first = false; + } else { + process(N, this->cache[chcacheid], op); + } + chnode->progress_report(); + } + } + this->cache.insert(this->tree.getString(node), N); +} + +/* + Typical visitor behavior: + o In prefix: Check if we're cached -> prune + o In postfix: Check if we're cached -> don't apply operator to children + o In postfix: addToParent() + */ + +Response CGALEvaluator::visit(State &state, const AbstractNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) applyToChildren(node, UNION); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) applyToChildren(node, INTERSECTION); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CGALEvaluator::visit(State &state, const CsgNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + CsgOp op; + switch (node.type) { + case CSG_TYPE_UNION: + op = UNION; + break; + case CSG_TYPE_DIFFERENCE: + op = DIFFERENCE; + break; + case CSG_TYPE_INTERSECTION: + op = INTERSECTION; + break; + } + applyToChildren(node, op); + } + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CGALEvaluator::visit(State &state, const TransformNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + // First union all children + applyToChildren(node, UNION); + + // Then apply transform + CGAL_Nef_polyhedron N = this->cache[this->tree.getString(node)]; + // If there is no geometry under the transform, N will be empty and of dim 0, + // just just silently ignore such nodes + if (N.dim == 2) { + // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 + // 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], node.matrix[4], node.matrix[12], + node.matrix[1], node.matrix[5], node.matrix[13], node.matrix[15]); + + DxfData dd(N); + for (int i=0; i < dd.points.size(); i++) { + CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd.points[i].x, dd.points[i].y); + p = t.transform(p); + dd.points[i].x = to_double(p.x()); + dd.points[i].y = to_double(p.y()); + } + + PolySet ps; + ps.is2d = true; + dxf_tesselate(&ps, &dd, 0, true, false, 0); + + N = evaluateCGALMesh(ps); + ps.refcount = 0; + } + else if (N.dim == 3) { + CGAL_Aff_transformation t( + node.matrix[0], node.matrix[4], node.matrix[ 8], node.matrix[12], + node.matrix[1], node.matrix[5], node.matrix[ 9], node.matrix[13], + node.matrix[2], node.matrix[6], node.matrix[10], node.matrix[14], node.matrix[15]); + N.p3.transform(t); + } + this->cache.insert(this->tree.getString(node), N); + } + addToParent(state, node); + } + return ContinueTraversal; +} + +// FIXME: EvaluateNode: Union over children + some magic +// FIXME: CgaladvNode: Iterate over children. Special operation + +// FIXME: Subtypes of AbstractPolyNode: +// ProjectionNode +// DxfLinearExtrudeNode +// DxfRotateExtrudeNode +// (SurfaceNode) +// (PrimitiveNode) +Response CGALEvaluator::visit(State &state, const AbstractPolyNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + // First union all children + applyToChildren(node, UNION); + + // Then apply polyset operation + PolySet *ps = node.evaluate_polyset(AbstractPolyNode::RENDER_CGAL, &this->psevaluator); + if (ps) { + try { + CGAL_Nef_polyhedron N = evaluateCGALMesh(*ps); +// print_messages_pop(); + node.progress_report(); + + ps->unlink(); + this->cache.insert(this->tree.getString(node), N); + } + catch (...) { // Don't leak the PolySet on ProgressCancelException + ps->unlink(); + throw; + } + } + } + addToParent(state, node); + } + return ContinueTraversal; +} + +/*! + Adds ourself to out parent's list of traversed children. + Call this for _every_ node which affects output during the postfix traversal. +*/ +void CGALEvaluator::addToParent(const State &state, const AbstractNode &node) +{ + assert(state.isPostfix()); + this->visitedchildren.erase(node.index()); + if (state.parent()) { + this->visitedchildren[state.parent()->index()].push_back(std::make_pair(&node, this->tree.getString(node))); + } +} + +#if 0 +/*! + Static function to evaluate CGAL meshes. + NB! This is just a support function used for development and debugging +*/ +CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const AbstractPolyNode &node) +{ + // FIXME: Lookup Nef polyhedron in cache. + + // print_messages_push(); + + PolySet *ps = node.evaluate_polyset(AbstractPolyNode::RENDER_CGAL); + if (ps) { + try { + CGAL_Nef_polyhedron N = ps->evaluateCSGMesh(); + // FIXME: Insert into cache + // print_messages_pop(); + node.progress_report(); + + ps->unlink(); + return N; + } + catch (...) { // Don't leak the PolySet on ProgressCancelException + ps->unlink(); + throw; + } + } +} +#endif + +#ifdef ENABLE_CGAL + +#undef GEN_SURFACE_DEBUG + +class CGAL_Build_PolySet : public CGAL::Modifier_base<CGAL_HDS> +{ +public: + typedef CGAL_HDS::Vertex::Point Point; + + const PolySet &ps; + CGAL_Build_PolySet(const PolySet &ps) : ps(ps) { } + + void operator()(CGAL_HDS& hds) + { + CGAL_Polybuilder B(hds, true); + + QList<PolySet::Point> vertices; + Grid3d<int> vertices_idx(GRID_FINE); + + for (int i = 0; i < ps.polygons.size(); i++) { + const PolySet::Polygon *poly = &ps.polygons[i]; + for (int j = 0; j < poly->size(); j++) { + const PolySet::Point *p = &poly->at(j); + if (!vertices_idx.has(p->x, p->y, p->z)) { + vertices_idx.data(p->x, p->y, p->z) = vertices.size(); + vertices.append(*p); + } + } + } + + B.begin_surface(vertices.size(), ps.polygons.size()); +#ifdef GEN_SURFACE_DEBUG + printf("=== CGAL Surface ===\n"); +#endif + + for (int i = 0; i < vertices.size(); i++) { + const PolySet::Point *p = &vertices[i]; + B.add_vertex(Point(p->x, p->y, p->z)); +#ifdef GEN_SURFACE_DEBUG + printf("%d: %f %f %f\n", i, p->x, p->y, p->z); +#endif + } + + for (int i = 0; i < ps.polygons.size(); i++) { + const PolySet::Polygon *poly = &ps.polygons[i]; + QHash<int,int> fc; + bool facet_is_degenerated = false; + for (int j = 0; j < poly->size(); j++) { + const PolySet::Point *p = &poly->at(j); + int v = vertices_idx.data(p->x, p->y, p->z); + if (fc[v]++ > 0) + facet_is_degenerated = true; + } + + if (!facet_is_degenerated) + B.begin_facet(); +#ifdef GEN_SURFACE_DEBUG + printf("F:"); +#endif + for (int j = 0; j < poly->size(); j++) { + const PolySet::Point *p = &poly->at(j); +#ifdef GEN_SURFACE_DEBUG + printf(" %d (%f,%f,%f)", vertices_idx.data(p->x, p->y, p->z), p->x, p->y, p->z); +#endif + if (!facet_is_degenerated) + B.add_vertex_to_facet(vertices_idx.data(p->x, p->y, p->z)); + } +#ifdef GEN_SURFACE_DEBUG + if (facet_is_degenerated) + printf(" (degenerated)"); + printf("\n"); +#endif + if (!facet_is_degenerated) + B.end_facet(); + } + +#ifdef GEN_SURFACE_DEBUG + printf("====================\n"); +#endif + B.end_surface(); + + #undef PointKey + } +}; + +#endif /* ENABLE_CGAL */ + +CGAL_Nef_polyhedron CGALEvaluator::evaluateCGALMesh(const PolySet &ps) +{ + if (ps.is2d) + { +#if 0 + // This version of the code causes problems in some cases. + // Example testcase: import_dxf("testdata/polygon8.dxf"); + // + typedef std::list<CGAL_Nef_polyhedron2::Point> point_list_t; + typedef point_list_t::iterator point_list_it; + std::list< point_list_t > pdata_point_lists; + std::list < std::pair < point_list_it, point_list_it > > pdata; + Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); + + for (int i = 0; i < ps.polygons.size(); i++) { + pdata_point_lists.push_back(point_list_t()); + for (int j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j].x; + double y = ps.polygons[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + pdata_point_lists.back().push_back(p); + } + pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), + pdata_point_lists.back().end())); + } + + CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); + return CGAL_Nef_polyhedron(N); +#endif +#if 0 + // This version of the code works fine but is pretty slow. + // + CGAL_Nef_polyhedron2 N; + Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); + + for (int i = 0; i < ps.polygons.size(); i++) { + std::list<CGAL_Nef_polyhedron2::Point> plist; + for (int j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j].x; + double y = ps.polygons[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + plist.push_back(p); + } + N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return CGAL_Nef_polyhedron(N); +#endif +#if 1 + // This version of the code does essentially the same thing as the 2nd + // version but merges some triangles before sending them to CGAL. This adds + // complexity but speeds up things.. + // + struct PolyReducer + { + Grid2d<int> grid; + QHash< QPair<int,int>, QPair<int,int> > egde_to_poly; + QHash< int, CGAL_Nef_polyhedron2::Point > points; + QHash< int, QList<int> > polygons; + int poly_n; + + void add_edges(int pn) + { + for (int j = 1; j <= this->polygons[pn].size(); j++) { + int a = this->polygons[pn][j-1]; + int b = this->polygons[pn][j % this->polygons[pn].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->egde_to_poly[QPair<int,int>(a, b)].first == 0) + this->egde_to_poly[QPair<int,int>(a, b)].first = pn; + else if (this->egde_to_poly[QPair<int,int>(a, b)].second == 0) + this->egde_to_poly[QPair<int,int>(a, b)].second = pn; + else + abort(); + } + } + + void del_poly(int pn) + { + for (int j = 1; j <= this->polygons[pn].size(); j++) { + int a = this->polygons[pn][j-1]; + int b = this->polygons[pn][j % this->polygons[pn].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->egde_to_poly[QPair<int,int>(a, b)].first == pn) + this->egde_to_poly[QPair<int,int>(a, b)].first = 0; + if (this->egde_to_poly[QPair<int,int>(a, b)].second == pn) + this->egde_to_poly[QPair<int,int>(a, b)].second = 0; + } + this->polygons.remove(pn); + } + + PolyReducer(const PolySet &ps) : grid(GRID_COARSE), poly_n(1) + { + int point_n = 1; + for (int i = 0; i < ps.polygons.size(); i++) { + for (int j = 0; j < ps.polygons[i].size(); j++) { + double x = ps.polygons[i][j].x; + double y = ps.polygons[i][j].y; + if (this->grid.has(x, y)) { + this->polygons[this->poly_n].append(this->grid.data(x, y)); + } else { + this->grid.align(x, y) = point_n; + this->polygons[this->poly_n].append(point_n); + this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); + point_n++; + } + } + add_edges(this->poly_n); + this->poly_n++; + } + } + + int merge(int p1, int p1e, int p2, int p2e) + { + for (int i = 1; i < this->polygons[p1].size(); i++) { + int j = (p1e + i) % this->polygons[p1].size(); + this->polygons[this->poly_n].append(this->polygons[p1][j]); + } + for (int i = 1; i < this->polygons[p2].size(); i++) { + int j = (p2e + i) % this->polygons[p2].size(); + this->polygons[this->poly_n].append(this->polygons[p2][j]); + } + del_poly(p1); + del_poly(p2); + add_edges(this->poly_n); + return this->poly_n++; + } + + void reduce() + { + QList<int> work_queue; + QHashIterator< int, QList<int> > it(polygons); + while (it.hasNext()) { + it.next(); + work_queue.append(it.key()); + } + while (!work_queue.isEmpty()) { + int poly1_n = work_queue.first(); + work_queue.removeFirst(); + if (!this->polygons.contains(poly1_n)) + continue; + for (int j = 1; j <= this->polygons[poly1_n].size(); j++) { + int a = this->polygons[poly1_n][j-1]; + int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; + if (a > b) { a = a^b; b = a^b; a = a^b; } + if (this->egde_to_poly[QPair<int,int>(a, b)].first != 0 && + this->egde_to_poly[QPair<int,int>(a, b)].second != 0) { + int poly2_n = this->egde_to_poly[QPair<int,int>(a, b)].first + + this->egde_to_poly[QPair<int,int>(a, b)].second - poly1_n; + int poly2_edge = -1; + for (int k = 1; k <= this->polygons[poly2_n].size(); k++) { + int c = this->polygons[poly2_n][k-1]; + int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; + if (c > d) { c = c^d; d = c^d; c = c^d; } + if (a == c && b == d) { + poly2_edge = k-1; + continue; + } + int poly3_n = this->egde_to_poly[QPair<int,int>(c, d)].first + + this->egde_to_poly[QPair<int,int>(c, d)].second - poly2_n; + if (poly3_n < 0) + continue; + if (poly3_n == poly1_n) + goto next_poly1_edge; + } + work_queue.append(merge(poly1_n, j-1, poly2_n, poly2_edge)); + goto next_poly1; + } + next_poly1_edge:; + } + next_poly1:; + } + } + + CGAL_Nef_polyhedron2 toNef() + { + CGAL_Nef_polyhedron2 N; + + QHashIterator< int, QList<int> > it(polygons); + while (it.hasNext()) { + it.next(); + std::list<CGAL_Nef_polyhedron2::Point> plist; + for (int j = 0; j < it.value().size(); j++) { + int p = it.value()[j]; + plist.push_back(points[p]); + } + N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return N; + } + }; + + PolyReducer pr(ps); + // printf("Number of polygons before reduction: %d\n", pr.polygons.size()); + pr.reduce(); + // printf("Number of polygons after reduction: %d\n", pr.polygons.size()); + return CGAL_Nef_polyhedron(pr.toNef()); +#endif +#if 0 + // This is another experimental version. I should run faster than the above, + // is a lot simpler and has only one known weakness: Degenerate polygons, which + // get repaired by GLUTess, might trigger a CGAL crash here. The only + // known case for this is triangle-with-duplicate-vertex.dxf + // FIXME: If we just did a projection, we need to recreate the border! + if (ps.polygons.size() > 0) assert(ps.borders.size() > 0); + CGAL_Nef_polyhedron2 N; + Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); + + for (int i = 0; i < ps.borders.size(); i++) { + std::list<CGAL_Nef_polyhedron2::Point> plist; + for (int j = 0; j < ps.borders[i].size(); j++) { + double x = ps.borders[i][j].x; + double y = ps.borders[i][j].y; + CGAL_Nef_polyhedron2::Point p; + if (grid.has(x, y)) { + p = grid.data(x, y); + } else { + p = CGAL_Nef_polyhedron2::Point(x, y); + grid.data(x, y) = p; + } + plist.push_back(p); + } + // FIXME: If a border (path) has a duplicate vertex in dxf, + // the CGAL_Nef_polyhedron2 constructor will crash. + N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + + return CGAL_Nef_polyhedron(N); + +#endif + } + else // not (this->is2d) + { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Polyhedron P; + CGAL_Build_PolySet builder(ps); + P.delegate(builder); +#if 0 + std::cout << P; +#endif + CGAL_Nef_polyhedron3 N(P); + return CGAL_Nef_polyhedron(N); + } + catch (CGAL::Assertion_exception e) { + PRINTF("ERROR: Illegal polygonal object - make sure all polygons are defined with the same winding order. Skipping affected object."); + CGAL::set_error_behaviour(old_behaviour); + return CGAL_Nef_polyhedron(); + } + } + return CGAL_Nef_polyhedron(); +} diff --git a/src/CGALEvaluator.h b/src/CGALEvaluator.h new file mode 100644 index 0000000..282d6ab --- /dev/null +++ b/src/CGALEvaluator.h @@ -0,0 +1,56 @@ +#ifndef CGALEVALUATOR_H_ +#define CGALEVALUATOR_H_ + +#include "myqhash.h" +#include "visitor.h" +#include "Tree.h" +#include "cgal.h" +#include "PolySetCGALEvaluator.h" + +#include <string> +#include <map> +#include <list> + +extern CGAL_Nef_polyhedron3 minkowski3(CGAL_Nef_polyhedron3 a, CGAL_Nef_polyhedron3 b); +extern CGAL_Nef_polyhedron2 minkowski2(CGAL_Nef_polyhedron2 a, CGAL_Nef_polyhedron2 b); + +using std::string; +using std::map; +using std::list; +using std::pair; + +class CGALEvaluator : public Visitor +{ +public: + enum CsgOp {UNION, INTERSECTION, DIFFERENCE, MINKOWSKI, HULL}; + // FIXME: If a cache is not given, we need to fix this ourselves + CGALEvaluator(QHash<string, CGAL_Nef_polyhedron> &cache, const Tree &tree) : cache(cache), tree(tree), psevaluator(*this) {} + virtual ~CGALEvaluator() {} + + virtual Response visit(State &state, const AbstractNode &node); + virtual Response visit(State &state, const AbstractIntersectionNode &node); + virtual Response visit(State &state, const CsgNode &node); + virtual Response visit(State &state, const TransformNode &node); + virtual Response visit(State &state, const AbstractPolyNode &node); + + CGAL_Nef_polyhedron evaluateCGALMesh(const AbstractNode &node); + CGAL_Nef_polyhedron evaluateCGALMesh(const PolySet &polyset); + + const Tree &getTree() const { return this->tree; } + +private: + void addToParent(const State &state, const AbstractNode &node); + bool isCached(const AbstractNode &node) const; + void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op); + void applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op); + + string currindent; + typedef list<pair<const AbstractNode *, string> > ChildList; + map<int, ChildList> visitedchildren; + + QHash<string, CGAL_Nef_polyhedron> &cache; + const Tree &tree; + PolySetCGALEvaluator psevaluator; +}; + +#endif diff --git a/src/CSGTermEvaluator.cc b/src/CSGTermEvaluator.cc new file mode 100644 index 0000000..b4b4e70 --- /dev/null +++ b/src/CSGTermEvaluator.cc @@ -0,0 +1,351 @@ +#include "CSGTermEvaluator.h" +#include "visitor.h" +#include "state.h" +#include "csgterm.h" +#include "module.h" +#include "csgnode.h" +#include "transformnode.h" +#include "rendernode.h" +#include "printutils.h" + +#include <string> +#include <map> +#include <list> +#include <sstream> +#include <iostream> +#include <assert.h> + +/*! + \class CSGTermEvaluator + + A visitor responsible for creating a tree of CSGTerm nodes used for rendering + with OpenCSG. +*/ + +CSGTerm *CSGTermEvaluator::evaluateCSGTerm(const AbstractNode &node, + vector<CSGTerm*> *highlights, + vector<CSGTerm*> *background) +{ + Traverser evaluate(*this, node, Traverser::PRE_AND_POSTFIX); + evaluate.execute(); + return this->stored_term[node.index()]; +} + +void CSGTermEvaluator::applyToChildren(const AbstractNode &node, CSGTermEvaluator::CsgOp op) +{ + CSGTerm *t1 = NULL; + for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); + iter != this->visitedchildren[node.index()].end(); + iter++) { + const AbstractNode *chnode = *iter; + CSGTerm *t2 = this->stored_term[chnode->index()]; + this->stored_term.erase(chnode->index()); + if (t2 && !t1) { + t1 = t2; + } else if (t2 && t1) { + if (op == UNION) { + t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); + } else if (op == DIFFERENCE) { + t1 = new CSGTerm(CSGTerm::TYPE_DIFFERENCE, t1, t2); + } else if (op == INTERSECTION) { + t1 = new CSGTerm(CSGTerm::TYPE_INTERSECTION, t1, t2); + } + } + } + if (t1 && node.modinst->tag_highlight && this->highlights) { + this->highlights->push_back(t1->link()); + } + if (t1 && node.modinst->tag_background && this->background) { + this->background->push_back(t1); + t1 = NULL; // don't propagate background tagged nodes + } + this->stored_term[node.index()] = t1; +} + +Response CSGTermEvaluator::visit(State &state, const AbstractNode &node) +{ + if (state.isPostfix()) { + applyToChildren(node, UNION); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTermEvaluator::visit(State &state, const AbstractIntersectionNode &node) +{ + if (state.isPostfix()) { + applyToChildren(node, INTERSECTION); + addToParent(state, node); + } + return ContinueTraversal; +} + +static CSGTerm *evaluate_csg_term_from_ps(const double m[20], + vector<CSGTerm*> *highlights, + vector<CSGTerm*> *background, + PolySet *ps, + const ModuleInstantiation *modinst, + const AbstractPolyNode &node) +{ + CSGTerm *t = new CSGTerm(ps, m, QString("%1%2").arg(node.name().c_str()).arg(node.index())); + if (modinst->tag_highlight && highlights) + highlights->push_back(t->link()); + if (modinst->tag_background && background) { + background->push_back(t); + return NULL; + } + return t; +} + +Response CSGTermEvaluator::visit(State &state, const AbstractPolyNode &node) +{ + if (state.isPostfix()) { + CSGTerm *t1 = NULL; + PolySet *ps = node.evaluate_polyset(AbstractPolyNode::RENDER_OPENCSG, this->psevaluator); + if (ps) { + t1 = evaluate_csg_term_from_ps(state.matrix(), this->highlights, this->background, + ps, node.modinst, node); + } + this->stored_term[node.index()] = t1; + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTermEvaluator::visit(State &state, const CsgNode &node) +{ + if (state.isPostfix()) { + CsgOp op; + switch (node.type) { + case CSG_TYPE_UNION: + op = UNION; + break; + case CSG_TYPE_DIFFERENCE: + op = DIFFERENCE; + break; + case CSG_TYPE_INTERSECTION: + op = INTERSECTION; + break; + } + applyToChildren(node, op); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTermEvaluator::visit(State &state, const TransformNode &node) +{ + if (state.isPrefix()) { + double m[20]; + + for (int i = 0; i < 16; i++) + { + int c_row = i%4; + int m_col = i/4; + m[i] = 0; + for (int j = 0; j < 4; j++) { + m[i] += state.matrix()[c_row + j*4] * node.matrix[m_col*4 + j]; + } + } + + for (int i = 16; i < 20; i++) { + m[i] = node.matrix[i] < 0 ? state.matrix()[i] : node.matrix[i]; + } + + state.setMatrix(m); + } + if (state.isPostfix()) { + applyToChildren(node, UNION); + addToParent(state, node); + } + return ContinueTraversal; +} + +// FIXME: Find out how to best call into CGAL from this visitor +Response CSGTermEvaluator::visit(State &state, const RenderNode &node) +{ + PRINT("WARNING: Found render() statement but compiled without CGAL support!"); + if (state.isPostfix()) { + applyToChildren(node, UNION); + addToParent(state, node); + } + return ContinueTraversal; +} + +/*! + Adds ourself to out parent's list of traversed children. + Call this for _every_ node which affects output during the postfix traversal. +*/ +void CSGTermEvaluator::addToParent(const State &state, const AbstractNode &node) +{ + assert(state.isPostfix()); + this->visitedchildren.erase(node.index()); + if (state.parent()) { + this->visitedchildren[state.parent()->index()].push_back(&node); + } +} + + +#if 0 + +// FIXME: #ifdef ENABLE_CGAL +#if 0 +CSGTerm *CgaladvNode::evaluate_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +{ + if (type == MINKOWSKI) + return evaluate_csg_term_from_nef(m, highlights, background, "minkowski", this->convexity); + + if (type == GLIDE) + return evaluate_csg_term_from_nef(m, highlights, background, "glide", this->convexity); + + if (type == SUBDIV) + return evaluate_csg_term_from_nef(m, highlights, background, "subdiv", this->convexity); + + if (type == HULL) + return evaluate_csg_term_from_nef(m, highlights, background, "hull", this->convexity); + + return NULL; +} + +#else // ENABLE_CGAL + +CSGTerm *CgaladvNode::evaluate_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +{ + PRINT("WARNING: Found minkowski(), glide(), subdiv() or hull() statement but compiled without CGAL support!"); + return NULL; +} + +#endif // ENABLE_CGAL + + + +// FIXME: #ifdef ENABLE_CGAL +#if 0 +CSGTerm *AbstractNode::evaluate_csg_term_from_nef(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background, const char *statement, int convexity) const +{ + QString key = mk_cache_id(); + if (PolySet::ps_cache.contains(key)) { + PRINT(PolySet::ps_cache[key]->msg); + return AbstractPolyNode::evaluate_csg_term_from_ps(m, highlights, background, + PolySet::ps_cache[key]->ps->link(), modinst, idx); + } + + print_messages_push(); + CGAL_Nef_polyhedron N; + + QString cache_id = mk_cache_id(); + if (cgal_nef_cache.contains(cache_id)) + { + PRINT(cgal_nef_cache[cache_id]->msg); + N = cgal_nef_cache[cache_id]->N; + } + else + { + PRINTF_NOCACHE("Processing uncached %s statement...", statement); + // PRINTA("Cache ID: %1", cache_id); + QApplication::processEvents(); + + QTime t; + t.start(); + + N = this->evaluateCSGMesh(); + + int s = t.elapsed() / 1000; + PRINTF_NOCACHE("..processing time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60); + } + + PolySet *ps = NULL; + + if (N.dim == 2) + { + DxfData dd(N); + ps = new PolySet(); + ps->is2d = true; + dxf_tesselate(ps, &dd, 0, true, false, 0); + dxf_border_to_ps(ps, &dd); + } + + if (N.dim == 3) + { + if (!N.p3.is_simple()) { + PRINTF("WARNING: Result of %s() isn't valid 2-manifold! Modify your design..", statement); + return NULL; + } + + ps = new PolySet(); + + CGAL_Polyhedron P; + N.p3.convert_to_Polyhedron(P); + + typedef CGAL_Polyhedron::Vertex Vertex; + typedef CGAL_Polyhedron::Vertex_const_iterator VCI; + typedef CGAL_Polyhedron::Facet_const_iterator FCI; + typedef CGAL_Polyhedron::Halfedge_around_facet_const_circulator HFCC; + + for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) { + HFCC hc = fi->facet_begin(); + HFCC hc_end = hc; + ps->append_poly(); + do { + Vertex v = *VCI((hc++)->vertex()); + double x = CGAL::to_double(v.point().x()); + double y = CGAL::to_double(v.point().y()); + double z = CGAL::to_double(v.point().z()); + ps->append_vertex(x, y, z); + } while (hc != hc_end); + } + } + + if (ps) + { + ps->convexity = convexity; + PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link())); + + CSGTerm *term = new CSGTerm(ps, m, QString("n%1").arg(idx)); + if (modinst->tag_highlight && highlights) + highlights->push_back(term->link()); + if (modinst->tag_background && background) { + background->push_back(term); + return NULL; + } + return term; + } + print_messages_pop(); + + return NULL; +} + +CSGTerm *RenderNode::evaluate_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +{ + return evaluate_csg_term_from_nef(m, highlights, background, "render", this->convexity); +} + +#else + +CSGTerm *RenderNode::evaluate_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +{ + CSGTerm *t1 = NULL; + PRINT("WARNING: Found render() statement but compiled without CGAL support!"); + foreach(AbstractNode * v, children) { + CSGTerm *t2 = v->evaluate_csg_term(m, highlights, background); + if (t2 && !t1) { + t1 = t2; + } else if (t2 && t1) { + t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); + } + } + if (modinst->tag_highlight && highlights) + highlights->push_back(t1->link()); + if (t1 && modinst->tag_background && background) { + background->push_back(t1); + return NULL; + } + return t1; +} + +#endif + + + +#endif + diff --git a/src/CSGTermEvaluator.h b/src/CSGTermEvaluator.h new file mode 100644 index 0000000..d1ea28e --- /dev/null +++ b/src/CSGTermEvaluator.h @@ -0,0 +1,53 @@ +#ifndef CSGTERMEVALUATOR_H_ +#define CSGTERMEVALUATOR_H_ + +#include <string> +#include <map> +#include <list> +#include <vector> +#include "Tree.h" +#include "visitor.h" +#include "node.h" + +using std::string; +using std::map; +using std::list; +using std::vector; + +class CSGTermEvaluator : public Visitor +{ +public: + CSGTermEvaluator(const Tree &tree, class PolySetEvaluator *psevaluator = NULL) + : highlights(NULL), background(NULL), tree(tree), psevaluator(psevaluator) { + } + virtual ~CSGTermEvaluator() {} + + virtual Response visit(State &state, const AbstractNode &node); + virtual Response visit(State &state, const AbstractIntersectionNode &node); + virtual Response visit(State &state, const AbstractPolyNode &node); + virtual Response visit(State &state, const CsgNode &node); + virtual Response visit(State &state, const TransformNode &node); + virtual Response visit(State &state, const RenderNode &node); + + class CSGTerm *evaluateCSGTerm(const AbstractNode &node, + vector<CSGTerm*> *highlights, vector<CSGTerm*> *background); + +private: + enum CsgOp {UNION, INTERSECTION, DIFFERENCE, MINKOWSKI}; + void addToParent(const State &state, const AbstractNode &node); + void applyToChildren(const AbstractNode &node, CSGTermEvaluator::CsgOp op); + + const AbstractNode *root; + typedef list<const AbstractNode *> ChildList; + map<int, ChildList> visitedchildren; + +public: + map<int, class CSGTerm*> stored_term; // The term evaluated from each node index + + vector<CSGTerm*> *highlights; + vector<CSGTerm*> *background; + const Tree &tree; + class PolySetEvaluator *psevaluator; +}; + +#endif diff --git a/src/GLView.h b/src/GLView.h index 764b23b..1001754 100644 --- a/src/GLView.h +++ b/src/GLView.h @@ -20,6 +20,7 @@ class GLView : public QGLWidget public: GLView(QWidget *parent = NULL); + GLView(const QGLFormat & format, QWidget *parent = NULL); void setRenderer(class Renderer* r); #ifdef ENABLE_OPENCSG bool hasOpenCSGSupport() { return this->opencsg_support; } @@ -52,6 +53,7 @@ public: #endif private: + void init(); Renderer *renderer; bool showfaces; diff --git a/src/MainWindow.h b/src/MainWindow.h index 243a5ad..0745935 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -7,7 +7,9 @@ #include "context.h" #include "module.h" #include "polyset.h" +#include "Tree.h" #include <QPointer> +#include <vector> class MainWindow : public QMainWindow, public Ui::MainWindow { @@ -32,6 +34,7 @@ public: ModuleInstantiation root_inst; // Top level instance AbstractNode *absolute_root_node; // Result of tree evaluation AbstractNode *root_node; // Root if the root modifier (!) is used + Tree tree; class CSGTerm *root_raw_term; // Result of CSG term rendering CSGTerm *root_norm_term; // Normalized CSG products @@ -45,9 +48,9 @@ public: #endif class ThrownTogetherRenderer *thrownTogetherRenderer; - QVector<CSGTerm*> highlight_terms; + std::vector<CSGTerm*> highlight_terms; CSGChain *highlights_chain; - QVector<CSGTerm*> background_terms; + std::vector<CSGTerm*> background_terms; CSGChain *background_chain; QString last_compiled_doc; @@ -77,6 +80,7 @@ private: void compileCSG(bool procevents); bool maybeSave(); bool checkModified(); + QString dumpCSGTree(AbstractNode *root); static void consoleOutput(const QString &msg, void *userdata) { static_cast<MainWindow*>(userdata)->console->append(msg); } @@ -157,4 +161,19 @@ public slots: void autoReloadSet(bool); }; +class GuiLocker +{ +public: + GuiLocker() { + gui_locked++; + } + ~GuiLocker() { + gui_locked--; + } + static bool isLocked() { return gui_locked > 0; } + +private: + static unsigned int gui_locked; +}; + #endif diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 6548c8e..1741557 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -38,7 +38,7 @@ <property name="orientation"> <enum>Qt::Vertical</enum> </property> - <widget class="GLView" name="screen" native="true"/> + <widget class="GLView" name="glview" native="true"/> <widget class="QTextEdit" name="console"/> </widget> </item> diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc new file mode 100644 index 0000000..e769730 --- /dev/null +++ b/src/PolySetCGALEvaluator.cc @@ -0,0 +1,426 @@ +#include "PolySetCGALEvaluator.h" +#include "polyset.h" +#include "CGALEvaluator.h" +#include "projectionnode.h" +#include "dxflinextrudenode.h" +#include "dxfrotextrudenode.h" +#include "dxfdata.h" +#include "dxftess.h" +#include "module.h" + +#include "printutils.h" +#include "export.h" // void cgal_nef3_to_polyset() +#include "openscad.h" // get_fragments_from_r() + +PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node, AbstractPolyNode::render_mode_e) +{ + const string &cacheid = this->cgalevaluator.getTree().getString(node); + if (this->cache.contains(cacheid)) return this->cache[cacheid]->ps->link(); + + CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); + + PolySet *ps = new PolySet(); + ps->convexity = node.convexity; + ps->is2d = true; + + if (node.cut_mode) + { + PolySet *cube = new PolySet(); + double infval = 1e8, eps = 0.1; + double x1 = -infval, x2 = +infval, y1 = -infval, y2 = +infval, z1 = 0, z2 = eps; + + cube->append_poly(); // top + cube->append_vertex(x1, y1, z2); + cube->append_vertex(x2, y1, z2); + cube->append_vertex(x2, y2, z2); + cube->append_vertex(x1, y2, z2); + + cube->append_poly(); // bottom + cube->append_vertex(x1, y2, z1); + cube->append_vertex(x2, y2, z1); + cube->append_vertex(x2, y1, z1); + cube->append_vertex(x1, y1, z1); + + cube->append_poly(); // side1 + cube->append_vertex(x1, y1, z1); + cube->append_vertex(x2, y1, z1); + cube->append_vertex(x2, y1, z2); + cube->append_vertex(x1, y1, z2); + + cube->append_poly(); // side2 + cube->append_vertex(x2, y1, z1); + cube->append_vertex(x2, y2, z1); + cube->append_vertex(x2, y2, z2); + cube->append_vertex(x2, y1, z2); + + cube->append_poly(); // side3 + cube->append_vertex(x2, y2, z1); + cube->append_vertex(x1, y2, z1); + cube->append_vertex(x1, y2, z2); + cube->append_vertex(x2, y2, z2); + + cube->append_poly(); // side4 + cube->append_vertex(x1, y2, z1); + cube->append_vertex(x1, y1, z1); + cube->append_vertex(x1, y1, z2); + cube->append_vertex(x1, y2, z2); + CGAL_Nef_polyhedron Ncube = this->cgalevaluator.evaluateCGALMesh(*cube); + cube->unlink(); + + // N.p3 *= CGAL_Nef_polyhedron3(CGAL_Plane(0, 0, 1, 0), CGAL_Nef_polyhedron3::INCLUDED); + N.p3 *= Ncube.p3; + if (!N.p3.is_simple()) { + PRINTF("WARNING: Body of projection(cut = true) isn't valid 2-manifold! Modify your design.."); + goto cant_project_non_simple_polyhedron; + } + + PolySet *ps3 = new PolySet(); + cgal_nef3_to_polyset(ps3, &N); + Grid2d<int> conversion_grid(GRID_COARSE); + for (int i = 0; i < ps3->polygons.size(); i++) { + for (int j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j].x; + double y = ps3->polygons[i][j].y; + double z = ps3->polygons[i][j].z; + if (z != 0) + goto next_ps3_polygon_cut_mode; + if (conversion_grid.align(x, y) == i+1) + goto next_ps3_polygon_cut_mode; + conversion_grid.data(x, y) = i+1; + } + ps->append_poly(); + for (int j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j].x; + double y = ps3->polygons[i][j].y; + conversion_grid.align(x, y); + ps->insert_vertex(x, y); + } + next_ps3_polygon_cut_mode:; + } + ps3->unlink(); + } + else + { + if (!N.p3.is_simple()) { + PRINTF("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); + goto cant_project_non_simple_polyhedron; + } + + PolySet *ps3 = new PolySet(); + cgal_nef3_to_polyset(ps3, &N); + CGAL_Nef_polyhedron np; + np.dim = 2; + for (int i = 0; i < ps3->polygons.size(); i++) + { + int min_x_p = -1; + double min_x_val = 0; + for (int j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j].x; + if (min_x_p < 0 || x < min_x_val) { + min_x_p = j; + min_x_val = x; + } + } + int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); + int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); + double ax = ps3->polygons[i][min_x_p1].x - ps3->polygons[i][min_x_p].x; + double ay = ps3->polygons[i][min_x_p1].y - ps3->polygons[i][min_x_p].y; + double at = atan2(ay, ax); + double bx = ps3->polygons[i][min_x_p2].x - ps3->polygons[i][min_x_p].x; + double by = ps3->polygons[i][min_x_p2].y - ps3->polygons[i][min_x_p].y; + double bt = atan2(by, bx); + + double eps = 0.000001; + if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || + (fabs(bx) < eps && fabs(by) < eps)) { + // this triangle is degenerated in projection + continue; + } + + std::list<CGAL_Nef_polyhedron2::Point> plist; + for (int j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j].x; + double y = ps3->polygons[i][j].y; + CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); + if (at > bt) + plist.push_front(p); + else + plist.push_back(p); + } + np.p2 += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), + CGAL_Nef_polyhedron2::INCLUDED); + } + DxfData dxf(np); + dxf_tesselate(ps, &dxf, 0, true, false, 0); + dxf_border_to_ps(ps, &dxf); + ps3->unlink(); + } + +cant_project_non_simple_polyhedron: + + this->cache.insert(cacheid, new cache_entry(ps->link())); + return ps; +} + +static void add_slice(PolySet *ps, DxfData::Path *pt, double rot1, double rot2, double h1, double h2) +{ + for (int j = 1; j < pt->points.count(); j++) + { + int k = j - 1; + + double jx1 = pt->points[j]->x * cos(rot1*M_PI/180) + pt->points[j]->y * sin(rot1*M_PI/180); + double jy1 = pt->points[j]->x * -sin(rot1*M_PI/180) + pt->points[j]->y * cos(rot1*M_PI/180); + + double jx2 = pt->points[j]->x * cos(rot2*M_PI/180) + pt->points[j]->y * sin(rot2*M_PI/180); + double jy2 = pt->points[j]->x * -sin(rot2*M_PI/180) + pt->points[j]->y * cos(rot2*M_PI/180); + + double kx1 = pt->points[k]->x * cos(rot1*M_PI/180) + pt->points[k]->y * sin(rot1*M_PI/180); + double ky1 = pt->points[k]->x * -sin(rot1*M_PI/180) + pt->points[k]->y * cos(rot1*M_PI/180); + + double kx2 = pt->points[k]->x * cos(rot2*M_PI/180) + pt->points[k]->y * sin(rot2*M_PI/180); + double ky2 = pt->points[k]->x * -sin(rot2*M_PI/180) + pt->points[k]->y * cos(rot2*M_PI/180); + + double dia1_len_sq = (jy1-ky2)*(jy1-ky2) + (jx1-kx2)*(jx1-kx2); + double dia2_len_sq = (jy2-ky1)*(jy2-ky1) + (jx2-kx1)*(jx2-kx1); + + if (dia1_len_sq > dia2_len_sq) + { + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(kx1, ky1, h1); + ps->append_vertex(jx1, jy1, h1); + ps->append_vertex(jx2, jy2, h2); + } else { + ps->insert_vertex(kx1, ky1, h1); + ps->insert_vertex(jx1, jy1, h1); + ps->insert_vertex(jx2, jy2, h2); + } + + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(kx2, ky2, h2); + ps->append_vertex(kx1, ky1, h1); + ps->append_vertex(jx2, jy2, h2); + } else { + ps->insert_vertex(kx2, ky2, h2); + ps->insert_vertex(kx1, ky1, h1); + ps->insert_vertex(jx2, jy2, h2); + } + } + else + { + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(kx1, ky1, h1); + ps->append_vertex(jx1, jy1, h1); + ps->append_vertex(kx2, ky2, h2); + } else { + ps->insert_vertex(kx1, ky1, h1); + ps->insert_vertex(jx1, jy1, h1); + ps->insert_vertex(kx2, ky2, h2); + } + + ps->append_poly(); + if (pt->is_inner) { + ps->append_vertex(jx2, jy2, h2); + ps->append_vertex(kx2, ky2, h2); + ps->append_vertex(jx1, jy1, h1); + } else { + ps->insert_vertex(jx2, jy2, h2); + ps->insert_vertex(kx2, ky2, h2); + ps->insert_vertex(jx1, jy1, h1); + } + } + } +} + +PolySet *PolySetCGALEvaluator::evaluatePolySet(const DxfLinearExtrudeNode &node, AbstractPolyNode::render_mode_e) +{ + const string &cacheid = this->cgalevaluator.getTree().getString(node); + if (this->cache.contains(cacheid)) return this->cache[cacheid]->ps->link(); + + DxfData *dxf; + + if (node.filename.isEmpty()) + { + // Before extruding, union all (2D) children nodes + // to a single DxfData, then tesselate this into a PolySet + CGAL_Nef_polyhedron N; + N.dim = 2; + foreach (AbstractNode * v, node.getChildren()) { + if (v->modinst->tag_background) continue; + N.p2 += this->cgalevaluator.evaluateCGALMesh(*v).p2; + } + + dxf = new DxfData(N); + } else { + dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); + } + + PolySet *ps = new PolySet(); + ps->convexity = node.convexity; + + double h1, h2; + + if (node.center) { + h1 = -node.height/2.0; + h2 = +node.height/2.0; + } else { + h1 = 0; + h2 = node.height; + } + + bool first_open_path = true; + for (int i = 0; i < dxf->paths.count(); i++) + { + if (dxf->paths[i].is_closed) + continue; + if (first_open_path) { + PRINTF("WARNING: Open paths in dxf_linear_extrude(file = \"%s\", layer = \"%s\"):", + node.filename.toAscii().data(), node.layername.toAscii().data()); + first_open_path = false; + } + PRINTF(" %9.5f %10.5f ... %10.5f %10.5f", + dxf->paths[i].points.first()->x / node.scale + node.origin_x, + dxf->paths[i].points.first()->y / node.scale + node.origin_y, + dxf->paths[i].points.last()->x / node.scale + node.origin_x, + dxf->paths[i].points.last()->y / node.scale + node.origin_y); + } + + + if (node.has_twist) + { + dxf_tesselate(ps, dxf, 0, false, true, h1); + dxf_tesselate(ps, dxf, node.twist, true, true, h2); + for (int j = 0; j < node.slices; j++) + { + double t1 = node.twist*j / node.slices; + double t2 = node.twist*(j+1) / node.slices; + double g1 = h1 + (h2-h1)*j / node.slices; + double g2 = h1 + (h2-h1)*(j+1) / node.slices; + for (int i = 0; i < dxf->paths.count(); i++) + { + if (!dxf->paths[i].is_closed) + continue; + add_slice(ps, &dxf->paths[i], t1, t2, g1, g2); + } + } + } + else + { + dxf_tesselate(ps, dxf, 0, false, true, h1); + dxf_tesselate(ps, dxf, 0, true, true, h2); + for (int i = 0; i < dxf->paths.count(); i++) + { + if (!dxf->paths[i].is_closed) + continue; + add_slice(ps, &dxf->paths[i], 0, 0, h1, h2); + } + } + + delete dxf; + + this->cache.insert(cacheid, new cache_entry(ps->link())); + return ps; +} + +PolySet *PolySetCGALEvaluator::evaluatePolySet(const DxfRotateExtrudeNode &node, + AbstractPolyNode::render_mode_e) +{ + const string &cacheid = this->cgalevaluator.getTree().getString(node); + if (this->cache.contains(cacheid)) return this->cache[cacheid]->ps->link(); + + DxfData *dxf; + + if (node.filename.isEmpty()) + { + // Before extruding, union all (2D) children nodes + // to a single DxfData, then tesselate this into a PolySet + CGAL_Nef_polyhedron N; + N.dim = 2; + foreach (AbstractNode * v, node.getChildren()) { + if (v->modinst->tag_background) continue; + N.p2 += this->cgalevaluator.evaluateCGALMesh(*v).p2; + } + + dxf = new DxfData(N); + } else { + dxf = new DxfData(node.fn, node.fs, node.fa, node.filename, node.layername, node.origin_x, node.origin_y, node.scale); + } + + PolySet *ps = new PolySet(); + ps->convexity = node.convexity; + + for (int i = 0; i < dxf->paths.count(); i++) + { + double max_x = 0; + for (int j = 0; j < dxf->paths[i].points.count(); j++) { + max_x = fmax(max_x, dxf->paths[i].points[j]->x); + } + + int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa); + + double ***points; + points = new double**[fragments]; + for (int j=0; j < fragments; j++) { + points[j] = new double*[dxf->paths[i].points.count()]; + for (int k=0; k < dxf->paths[i].points.count(); k++) + points[j][k] = new double[3]; + } + + for (int j = 0; j < fragments; j++) { + double a = (j*2*M_PI) / fragments; + for (int k = 0; k < dxf->paths[i].points.count(); k++) { + if (dxf->paths[i].points[k]->x == 0) { + points[j][k][0] = 0; + points[j][k][1] = 0; + } else { + points[j][k][0] = dxf->paths[i].points[k]->x * sin(a); + points[j][k][1] = dxf->paths[i].points[k]->x * cos(a); + } + points[j][k][2] = dxf->paths[i].points[k]->y; + } + } + + for (int j = 0; j < fragments; j++) { + int j1 = j + 1 < fragments ? j + 1 : 0; + for (int k = 0; k < dxf->paths[i].points.count(); k++) { + int k1 = k + 1 < dxf->paths[i].points.count() ? k + 1 : 0; + if (points[j][k][0] != points[j1][k][0] || + points[j][k][1] != points[j1][k][1] || + points[j][k][2] != points[j1][k][2]) { + ps->append_poly(); + ps->append_vertex(points[j ][k ][0], + points[j ][k ][1], points[j ][k ][2]); + ps->append_vertex(points[j1][k ][0], + points[j1][k ][1], points[j1][k ][2]); + ps->append_vertex(points[j ][k1][0], + points[j ][k1][1], points[j ][k1][2]); + } + if (points[j][k1][0] != points[j1][k1][0] || + points[j][k1][1] != points[j1][k1][1] || + points[j][k1][2] != points[j1][k1][2]) { + ps->append_poly(); + ps->append_vertex(points[j ][k1][0], + points[j ][k1][1], points[j ][k1][2]); + ps->append_vertex(points[j1][k ][0], + points[j1][k ][1], points[j1][k ][2]); + ps->append_vertex(points[j1][k1][0], + points[j1][k1][1], points[j1][k1][2]); + } + } + } + + for (int j=0; j < fragments; j++) { + for (int k=0; k < dxf->paths[i].points.count(); k++) + delete[] points[j][k]; + delete[] points[j]; + } + delete[] points; + } + + delete dxf; + + this->cache.insert(cacheid, new cache_entry(ps->link())); + return ps; +} diff --git a/src/PolySetCGALEvaluator.h b/src/PolySetCGALEvaluator.h new file mode 100644 index 0000000..5089e0e --- /dev/null +++ b/src/PolySetCGALEvaluator.h @@ -0,0 +1,24 @@ +#ifndef POLYSETCGALEVALUATOR_H_ +#define POLYSETCGALEVALUATOR_H_ + +#include "PolySetEvaluator.h" + +/*! + This is a PolySet evaluator which uses the CGALEvaluator to support building + polysets. +*/ +class PolySetCGALEvaluator : public PolySetEvaluator +{ +public: + PolySetCGALEvaluator(class CGALEvaluator &CGALEvaluator) : + PolySetEvaluator(), cgalevaluator(CGALEvaluator) { } + virtual ~PolySetCGALEvaluator() { } + virtual PolySet *evaluatePolySet(const ProjectionNode &node, AbstractPolyNode::render_mode_e); + virtual PolySet *evaluatePolySet(const DxfLinearExtrudeNode &node, AbstractPolyNode::render_mode_e); + virtual PolySet *evaluatePolySet(const DxfRotateExtrudeNode &node, AbstractPolyNode::render_mode_e); + +private: + CGALEvaluator &cgalevaluator; +}; + +#endif diff --git a/src/PolySetEvaluator.cc b/src/PolySetEvaluator.cc new file mode 100644 index 0000000..56acb1d --- /dev/null +++ b/src/PolySetEvaluator.cc @@ -0,0 +1,15 @@ +#include "PolySetEvaluator.h" +#include "printutils.h" +#include "polyset.h" + +PolySetEvaluator *PolySetEvaluator::global_evaluator = NULL; + +PolySetEvaluator::cache_entry::cache_entry(PolySet *ps) : + ps(ps), msg(print_messages_stack.last()) +{ +} + +PolySetEvaluator::cache_entry::~cache_entry() +{ + ps->unlink(); +} diff --git a/src/PolySetEvaluator.h b/src/PolySetEvaluator.h new file mode 100644 index 0000000..dcc67db --- /dev/null +++ b/src/PolySetEvaluator.h @@ -0,0 +1,39 @@ +#ifndef POLYSETEVALUATOR_H_ +#define POLYSETEVALUATOR_H_ + +#include "myqhash.h" +#include "node.h" +#include <QCache> + +class PolySetEvaluator +{ +public: + enum EvaluateMode { EVALUATE_CGAL, EVALUATE_OPENCSG }; + PolySetEvaluator() : cache(100) {} + + virtual ~PolySetEvaluator() {} + + virtual PolySet *evaluatePolySet(const class ProjectionNode &, AbstractPolyNode::render_mode_e) = 0; + virtual PolySet *evaluatePolySet(const class DxfLinearExtrudeNode &, AbstractPolyNode::render_mode_e) = 0; + virtual PolySet *evaluatePolySet(const class DxfRotateExtrudeNode &, AbstractPolyNode::render_mode_e) = 0; + + void clearCache() { + this->cache.clear(); + } + +protected: + + struct cache_entry { + class PolySet *ps; + QString msg; + cache_entry(PolySet *ps); + ~cache_entry(); + }; + + QCache<std::string, cache_entry> cache; + +private: + static PolySetEvaluator *global_evaluator; +}; + +#endif diff --git a/src/Tree.cc b/src/Tree.cc new file mode 100644 index 0000000..a451f24 --- /dev/null +++ b/src/Tree.cc @@ -0,0 +1,30 @@ +#include "Tree.h" +#include "nodedumper.h" + +#include <assert.h> + +/*! + Returns the cached string representation of the subtree rootet by \a node. + If node is not cached, the cache will be rebuilt. +*/ +const std::string &Tree::getString(const AbstractNode &node) const +{ + assert(this->root_node); + if (!this->nodecache.contains(node)) { + NodeDumper dumper(this->nodecache, false); + Traverser trav(dumper, *this->root_node, Traverser::PRE_AND_POSTFIX); + trav.execute(); + assert(this->nodecache.contains(*this->root_node) && + "NodeDumper failed to create a cache"); + } + return this->nodecache[node]; +} + +/*! + Sets a new root. Will clear the existing cache. + */ +void Tree::setRoot(const AbstractNode *root) +{ + this->root_node = root; + this->nodecache.clear(); +} diff --git a/src/Tree.h b/src/Tree.h new file mode 100644 index 0000000..2c3f0b8 --- /dev/null +++ b/src/Tree.h @@ -0,0 +1,31 @@ +#ifndef TREE_H_ +#define TREE_H_ + +#include "nodecache.h" + +using std::string; + +/*! + For now, just an abstraction of the node tree which keeps a dump + cache based on node indices around. + + Note that since node trees don't survive a recompilation, the tree cannot either. + */ +class Tree +{ +public: + Tree(const AbstractNode *root = NULL) : root_node(root) {} + ~Tree() {} + + void setRoot(const AbstractNode *root); + const AbstractNode *root() const { return this->root_node; } + + // FIXME: Really return a reference? + const string &getString(const AbstractNode &node) const; + +private: + const AbstractNode *root_node; + mutable NodeCache nodecache; +}; + +#endif diff --git a/src/cgaladv.cc b/src/cgaladv.cc index dd797fd..f3f2cbd 100644 --- a/src/cgaladv.cc +++ b/src/cgaladv.cc @@ -30,6 +30,9 @@ #include "builtin.h" #include "printutils.h" #include "cgal.h" +#include "visitor.h" +#include <sstream> +#include <assert.h> #ifdef ENABLE_CGAL extern CGAL_Nef_polyhedron3 minkowski3(CGAL_Nef_polyhedron3 a, CGAL_Nef_polyhedron3 b); @@ -55,18 +58,37 @@ public: class CgaladvNode : public AbstractNode { public: - Value path; - QString subdiv_type; - int convexity, level; - cgaladv_type_e type; CgaladvNode(const ModuleInstantiation *mi, cgaladv_type_e type) : AbstractNode(mi), type(type) { convexity = 1; } -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - virtual CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; + virtual ~CgaladvNode() { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { + switch (this->type) { + case MINKOWSKI: + return "minkowski"; + break; + case GLIDE: + return "glide"; + break; + case SUBDIV: + return "subdiv"; + break; + case HULL: + return "hull"; + break; + default: + assert(false); + } + } + + Value path; + std::string subdiv_type; + int convexity, level; + cgaladv_type_e type; }; AbstractNode *CgaladvModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const @@ -130,129 +152,27 @@ void register_builtin_cgaladv() builtin_modules["hull"] = new CgaladvModule(HULL); } -#ifdef ENABLE_CGAL - -CGAL_Nef_polyhedron CgaladvNode::render_cgal_nef_polyhedron() const +std::string CgaladvNode::toString() const { - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) { - progress_report(); - PRINT(cgal_nef_cache[cache_id]->msg); - return cgal_nef_cache[cache_id]->N; + std::stringstream stream; + + stream << this->name(); + switch (type) { + case MINKOWSKI: + stream << "(convexity = " << this->convexity << ")"; + break; + case GLIDE: + stream << "(path = " << this->path << ", convexity = " << this->convexity << ")"; + break; + case SUBDIV: + stream << "(level = " << this->level << ", convexity = " << this->convexity << ")"; + break; + case HULL: + stream << "()"; + break; + default: + assert(false); } - print_messages_push(); - CGAL_Nef_polyhedron N; - - if (type == MINKOWSKI) - { - bool first = true; - foreach(AbstractNode * v, children) { - if (v->modinst->tag_background) - continue; - if (first) { - N = v->render_cgal_nef_polyhedron(); - if (N.dim != 0) - first = false; - } else { - CGAL_Nef_polyhedron tmp = v->render_cgal_nef_polyhedron(); - if (N.dim == 3 && tmp.dim == 3) { - N.p3 = minkowski3(N.p3, tmp.p3); - } - if (N.dim == 2 && tmp.dim == 2) { - N.p2 = minkowski2(N.p2, tmp.p2); - } - } - v->progress_report(); - } - } - - if (type == GLIDE) - { - PRINT("WARNING: subdiv() is not implemented yet!"); - } - - if (type == SUBDIV) - { - PRINT("WARNING: subdiv() is not implemented yet!"); - } - - if (type == HULL) - { - std::list<CGAL_Nef_polyhedron2> polys; - bool all2d = true; - foreach(AbstractNode * v, children) { - if (v->modinst->tag_background) - continue; - N = v->render_cgal_nef_polyhedron(); - if (N.dim == 3) { - //polys.push_back(tmp.p3); - PRINT("WARNING: hull() is not implemented yet for 3D objects!"); - all2d=false; - } - if (N.dim == 2) { - polys.push_back(N.p2); - } - v->progress_report(); - } - - if (all2d) - N.p2 = convexhull2(polys); - } - - cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight()); - print_messages_pop(); - progress_report(); - - return N; + return stream.str(); } - -CSGTerm *CgaladvNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - if (type == MINKOWSKI) - return render_csg_term_from_nef(m, highlights, background, "minkowski", this->convexity); - - if (type == GLIDE) - return render_csg_term_from_nef(m, highlights, background, "glide", this->convexity); - - if (type == SUBDIV) - return render_csg_term_from_nef(m, highlights, background, "subdiv", this->convexity); - - if (type == HULL) - return render_csg_term_from_nef(m, highlights, background, "hull", this->convexity); - - return NULL; -} - -#else // ENABLE_CGAL - -CSGTerm *CgaladvNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - PRINT("WARNING: Found minkowski(), glide(), subdiv() or hull() statement but compiled without CGAL support!"); - return NULL; -} - -#endif // ENABLE_CGAL - -QString CgaladvNode::dump(QString indent) const -{ - if (dump_cache.isEmpty()) { - QString text; - if (type == MINKOWSKI) - text.sprintf("minkowski(convexity = %d) {\n", this->convexity); - if (type == GLIDE) { - text.sprintf(", convexity = %d) {\n", this->convexity); - text = QString("glide(path = ") + this->path.dump() + text; - } - if (type == SUBDIV) - text.sprintf("subdiv(level = %d, convexity = %d) {\n", this->level, this->convexity); - if (type == HULL) - text.sprintf("hull() {\n"); - foreach (AbstractNode *v, this->children) - text += v->dump(indent + QString("\t")); - text += indent + "}\n"; - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; - } - return dump_cache; -} - diff --git a/src/cgaladv_minkowski3.cc b/src/cgaladv_minkowski3.cc index f270de2..f12ce21 100644 --- a/src/cgaladv_minkowski3.cc +++ b/src/cgaladv_minkowski3.cc @@ -26,10 +26,7 @@ #ifdef ENABLE_CGAL -#include "node.h" -#include "printutils.h" #include "cgal.h" - #include <CGAL/minkowski_sum_3.h> extern CGAL_Nef_polyhedron3 minkowski3(CGAL_Nef_polyhedron3 a, CGAL_Nef_polyhedron3 b); diff --git a/src/context.cc b/src/context.cc index bfe9eb6..ba2690c 100644 --- a/src/context.cc +++ b/src/context.cc @@ -85,6 +85,8 @@ Value Context::lookup_variable(QString name, bool silent) const } return Value(); } + if (!parent && constants.contains(name)) + return constants[name]; if (variables.contains(name)) return variables[name]; if (parent) @@ -94,6 +96,14 @@ Value Context::lookup_variable(QString name, bool silent) const return Value(); } +void Context::set_constant(QString name, Value value) +{ + if (constants.contains(name)) + PRINTA("WARNING: Attempt to modify constant '%1'.",name); + else + constants.insert(name,value); +} + Value Context::evaluate_function(QString name, const QVector<QString> &argnames, const QVector<Value> &argvalues) const { if (functions_p && functions_p->contains(name)) @@ -148,8 +158,16 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation *inst) const return NULL; } +/*! + Returns the absolute path to the given filename, unless it's empty. + */ QString Context::get_absolute_path(const QString &filename) const { - return QFileInfo(QDir(this->document_path), filename).absoluteFilePath(); + if (!filename.isEmpty()) { + return QFileInfo(QDir(this->document_path), filename).absoluteFilePath(); + } + else { + return filename; + } } diff --git a/src/context.h b/src/context.h index d5be745..cbb1c4f 100644 --- a/src/context.h +++ b/src/context.h @@ -9,6 +9,7 @@ class Context { public: const Context *parent; + QHash<QString, Value> constants; QHash<QString, Value> variables; QHash<QString, Value> config_variables; const QHash<QString, class AbstractFunction*> *functions_p; @@ -27,6 +28,8 @@ public: void set_variable(QString name, Value value); Value lookup_variable(QString name, bool silent = false) const; + void set_constant(QString name, Value value); + QString get_absolute_path(const QString &filename) const; Value evaluate_function(QString name, const QVector<QString> &argnames, const QVector<Value> &argvalues) const; diff --git a/src/control.cc b/src/control.cc index ae1d654..ca1a4c6 100644 --- a/src/control.cc +++ b/src/control.cc @@ -124,7 +124,7 @@ AbstractNode *ControlModule::evaluate(const Context*, const ModuleInstantiation msg += QString(", "); if (!inst->argnames[i].isEmpty()) msg += inst->argnames[i] + QString(" = "); - msg += inst->argvalues[i].dump(); + msg += QString::fromStdString(inst->argvalues[i].toString()); } PRINT(msg); } diff --git a/src/csgnode.h b/src/csgnode.h new file mode 100644 index 0000000..2e1d9fb --- /dev/null +++ b/src/csgnode.h @@ -0,0 +1,25 @@ +#ifndef CSGNODE_H_ +#define CSGNODE_H_ + +#include "node.h" +#include "visitor.h" + +enum csg_type_e { + CSG_TYPE_UNION, + CSG_TYPE_DIFFERENCE, + CSG_TYPE_INTERSECTION +}; + +class CsgNode : public AbstractNode +{ +public: + csg_type_e type; + CsgNode(const ModuleInstantiation *mi, csg_type_e type) : AbstractNode(mi), type(type) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const; +}; + +#endif diff --git a/src/csgops.cc b/src/csgops.cc index 55d91d2..53e9ed6 100644 --- a/src/csgops.cc +++ b/src/csgops.cc @@ -24,22 +24,14 @@ * */ +#include "csgnode.h" + #include "module.h" -#include "node.h" #include "csgterm.h" #include "builtin.h" #include "printutils.h" -#ifdef ENABLE_CGAL -# include "cgal.h" -# include <CGAL/assertions_behaviour.h> -# include <CGAL/exceptions.h> -#endif - -enum csg_type_e { - CSG_TYPE_UNION, - CSG_TYPE_DIFFERENCE, - CSG_TYPE_INTERSECTION -}; +#include <sstream> +#include <assert.h> class CsgModule : public AbstractModule { @@ -49,18 +41,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class CsgNode : public AbstractNode -{ -public: - csg_type_e type; - CsgNode(const ModuleInstantiation *mi, csg_type_e type) : AbstractNode(mi), type(type) { } -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *CsgModule::evaluate(const Context*, const ModuleInstantiation *inst) const { CsgNode *node = new CsgNode(inst, type); @@ -72,105 +52,26 @@ AbstractNode *CsgModule::evaluate(const Context*, const ModuleInstantiation *ins return node; } -#ifdef ENABLE_CGAL - -CGAL_Nef_polyhedron CsgNode::render_cgal_nef_polyhedron() const +std::string CsgNode::toString() const { - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) { - progress_report(); - PRINT(cgal_nef_cache[cache_id]->msg); - return cgal_nef_cache[cache_id]->N; - } - - print_messages_push(); - - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - bool first = true; - CGAL_Nef_polyhedron N; - try { - foreach (AbstractNode *v, children) { - if (v->modinst->tag_background) - continue; - if (first) { - N = v->render_cgal_nef_polyhedron(); - if (N.dim != 0) - first = false; - } else if (N.dim == 2) { - if (type == CSG_TYPE_UNION) { - N.p2 += v->render_cgal_nef_polyhedron().p2; - } else if (type == CSG_TYPE_DIFFERENCE) { - N.p2 -= v->render_cgal_nef_polyhedron().p2; - } else if (type == CSG_TYPE_INTERSECTION) { - N.p2 *= v->render_cgal_nef_polyhedron().p2; - } - } else if (N.dim == 3) { - if (type == CSG_TYPE_UNION) { - N.p3 += v->render_cgal_nef_polyhedron().p3; - } else if (type == CSG_TYPE_DIFFERENCE) { - N.p3 -= v->render_cgal_nef_polyhedron().p3; - } else if (type == CSG_TYPE_INTERSECTION) { - N.p3 *= v->render_cgal_nef_polyhedron().p3; - } - } - v->progress_report(); - } - cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight()); - } - catch (CGAL::Assertion_exception e) { - PRINTF("ERROR: Illegal polygonal object - make sure all polygons are defined with the same winding order. Skipping affected object."); - } - CGAL::set_error_behaviour(old_behaviour); - - print_messages_pop(); - progress_report(); - - return N; -} - -#endif /* ENABLE_CGAL */ - -CSGTerm *CsgNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - CSGTerm *t1 = NULL; - foreach (AbstractNode *v, children) { - CSGTerm *t2 = v->render_csg_term(m, highlights, background); - if (t2 && !t1) { - t1 = t2; - } else if (t2 && t1) { - if (type == CSG_TYPE_UNION) { - t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); - } else if (type == CSG_TYPE_DIFFERENCE) { - t1 = new CSGTerm(CSGTerm::TYPE_DIFFERENCE, t1, t2); - } else if (type == CSG_TYPE_INTERSECTION) { - t1 = new CSGTerm(CSGTerm::TYPE_INTERSECTION, t1, t2); - } - } - } - if (t1 && modinst->tag_highlight && highlights) - highlights->append(t1->link()); - if (t1 && modinst->tag_background && background) { - background->append(t1); - return NULL; - } - return t1; + return this->name() + "()"; } -QString CsgNode::dump(QString indent) const +std::string CsgNode::name() const { - if (dump_cache.isEmpty()) { - QString text = indent + QString("n%1: ").arg(idx); - if (type == CSG_TYPE_UNION) - text += "union() {\n"; - if (type == CSG_TYPE_DIFFERENCE) - text += "difference() {\n"; - if (type == CSG_TYPE_INTERSECTION) - text += "intersection() {\n"; - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; + switch (this->type) { + case CSG_TYPE_UNION: + return "union"; + break; + case CSG_TYPE_DIFFERENCE: + return "difference"; + break; + case CSG_TYPE_INTERSECTION: + return "intersection"; + break; + default: + assert(false); } - return dump_cache; } void register_builtin_csgops() diff --git a/src/csgterm.cc b/src/csgterm.cc index 6dd3bd5..930a540 100644 --- a/src/csgterm.cc +++ b/src/csgterm.cc @@ -27,7 +27,26 @@ #include "csgterm.h" #include "polyset.h" -CSGTerm::CSGTerm(PolySet *polyset, double m[20], QString label) +/*! + \class CSGTerm + + A CSGTerm is either a "primitive" or a CSG operation with two + children terms. A primitive in this context is any PolySet, which + may or may not have a subtree which is already evaluated (e.g. using + the render() module). + + */ + +/*! + \class CSGChain + + A CSGChain is just a vector of primitives, each having a CSG type associated with it. + It's created by importing a CSGTerm tree. + + */ + + +CSGTerm::CSGTerm(PolySet *polyset, const double m[20], QString label) { this->type = TYPE_PRIMITIVE; this->polyset = polyset; @@ -200,9 +219,9 @@ QString CSGChain::dump() text += "\n"; text += "+"; } - if (types[i] == CSGTerm::TYPE_DIFFERENCE) + else if (types[i] == CSGTerm::TYPE_DIFFERENCE) text += " -"; - if (types[i] == CSGTerm::TYPE_INTERSECTION) + else if (types[i] == CSGTerm::TYPE_INTERSECTION) text += " *"; text += labels[i]; } diff --git a/src/csgterm.h b/src/csgterm.h index 35d071d..4ee6faa 100644 --- a/src/csgterm.h +++ b/src/csgterm.h @@ -22,7 +22,7 @@ public: double m[20]; int refcounter; - CSGTerm(PolySet *polyset, double m[20], QString label); + CSGTerm(PolySet *polyset, const double m[20], QString label); CSGTerm(type_e type, CSGTerm *left, CSGTerm *right); CSGTerm *normalize(); diff --git a/src/dxfdim.cc b/src/dxfdim.cc index 4c53d86..f8008f5 100644 --- a/src/dxfdim.cc +++ b/src/dxfdim.cc @@ -51,15 +51,15 @@ Value builtin_dxf_dim(const Context *ctx, const QVector<QString> &argnames, cons for (int i = 0; i < argnames.count() && i < args.count(); i++) { if (argnames[i] == "file") - filename = ctx->get_absolute_path(args[i].text); + filename = ctx->get_absolute_path(QString::fromStdString(args[i].text)); if (argnames[i] == "layer") - layername = args[i].text; + layername = QString::fromStdString(args[i].text); if (argnames[i] == "origin") args[i].getv2(xorigin, yorigin); if (argnames[i] == "scale") args[i].getnum(scale); if (argnames[i] == "name") - name = args[i].text; + name = QString::fromStdString(args[i].text); } QFileInfo fileInfo(filename); @@ -133,9 +133,9 @@ Value builtin_dxf_cross(const Context *ctx, const QVector<QString> &argnames, co for (int i = 0; i < argnames.count() && i < args.count(); i++) { if (argnames[i] == "file") - filename = ctx->get_absolute_path(args[i].text); + filename = ctx->get_absolute_path(QString::fromStdString(args[i].text)); if (argnames[i] == "layer") - layername = args[i].text; + layername = QString::fromStdString(args[i].text); if (argnames[i] == "origin") args[i].getv2(xorigin, yorigin); if (argnames[i] == "scale") @@ -176,8 +176,8 @@ Value builtin_dxf_cross(const Context *ctx, const QVector<QString> &argnames, co double y = y1 + ua*(y2 - y1); Value ret; ret.type = Value::VECTOR; - ret.vec.append(new Value(x)); - ret.vec.append(new Value(y)); + ret.append(new Value(x)); + ret.append(new Value(y)); return dxf_cross_cache[key] = ret; } } diff --git a/src/dxflinextrude.cc b/src/dxflinextrude.cc index 9661066..64c4c35 100644 --- a/src/dxflinextrude.cc +++ b/src/dxflinextrude.cc @@ -24,8 +24,9 @@ * */ +#include "dxflinextrudenode.h" + #include "module.h" -#include "node.h" #include "context.h" #include "printutils.h" #include "builtin.h" @@ -33,8 +34,12 @@ #include "dxftess.h" #include "polyset.h" #include "progress.h" +#include "visitor.h" +#include "PolySetEvaluator.h" #include "openscad.h" // get_fragments_from_r() +#include <sstream> + #include <QApplication> #include <QTime> #include <QProgressDialog> @@ -48,24 +53,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class DxfLinearExtrudeNode : public AbstractPolyNode -{ -public: - int convexity, slices; - double fn, fs, fa, height, twist; - double origin_x, origin_y, scale; - bool center, has_twist; - QString filename, layername; - DxfLinearExtrudeNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { - convexity = slices = 0; - fn = fs = fa = height = twist = 0; - origin_x = origin_y = scale = 0; - center = has_twist = false; - } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *DxfLinearExtrudeModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { DxfLinearExtrudeNode *node = new DxfLinearExtrudeNode(inst); @@ -90,12 +77,10 @@ AbstractNode *DxfLinearExtrudeModule::evaluate(const Context *ctx, const ModuleI Value twist = c.lookup_variable("twist", true); Value slices = c.lookup_variable("slices", true); - if(!file.text.isNull()) - node->filename = c.get_absolute_path(file.text); - else - node->filename = file.text; + if (!file.text.empty()) + node->filename = c.get_absolute_path(QString::fromStdString(file.text)); - node->layername = layer.text; + node->layername = QString::fromStdString(layer.text); node->height = height.num; node->convexity = (int)convexity.num; origin.getv2(node->origin_x, node->origin_y); @@ -141,203 +126,45 @@ void register_builtin_dxf_linear_extrude() builtin_modules["linear_extrude"] = new DxfLinearExtrudeModule(); } -static void add_slice(PolySet *ps, DxfData::Path *pt, double rot1, double rot2, double h1, double h2) -{ - for (int j = 1; j < pt->points.count(); j++) - { - int k = j - 1; - - double jx1 = pt->points[j]->x * cos(rot1*M_PI/180) + pt->points[j]->y * sin(rot1*M_PI/180); - double jy1 = pt->points[j]->x * -sin(rot1*M_PI/180) + pt->points[j]->y * cos(rot1*M_PI/180); - - double jx2 = pt->points[j]->x * cos(rot2*M_PI/180) + pt->points[j]->y * sin(rot2*M_PI/180); - double jy2 = pt->points[j]->x * -sin(rot2*M_PI/180) + pt->points[j]->y * cos(rot2*M_PI/180); - - double kx1 = pt->points[k]->x * cos(rot1*M_PI/180) + pt->points[k]->y * sin(rot1*M_PI/180); - double ky1 = pt->points[k]->x * -sin(rot1*M_PI/180) + pt->points[k]->y * cos(rot1*M_PI/180); - - double kx2 = pt->points[k]->x * cos(rot2*M_PI/180) + pt->points[k]->y * sin(rot2*M_PI/180); - double ky2 = pt->points[k]->x * -sin(rot2*M_PI/180) + pt->points[k]->y * cos(rot2*M_PI/180); - - double dia1_len_sq = (jy1-ky2)*(jy1-ky2) + (jx1-kx2)*(jx1-kx2); - double dia2_len_sq = (jy2-ky1)*(jy2-ky1) + (jx2-kx1)*(jx2-kx1); - - if (dia1_len_sq > dia2_len_sq) - { - ps->append_poly(); - if (pt->is_inner) { - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx1, jy1, h1); - ps->append_vertex(jx2, jy2, h2); - } else { - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx1, jy1, h1); - ps->insert_vertex(jx2, jy2, h2); - } - - ps->append_poly(); - if (pt->is_inner) { - ps->append_vertex(kx2, ky2, h2); - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx2, jy2, h2); - } else { - ps->insert_vertex(kx2, ky2, h2); - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx2, jy2, h2); - } - } - else - { - ps->append_poly(); - if (pt->is_inner) { - ps->append_vertex(kx1, ky1, h1); - ps->append_vertex(jx1, jy1, h1); - ps->append_vertex(kx2, ky2, h2); - } else { - ps->insert_vertex(kx1, ky1, h1); - ps->insert_vertex(jx1, jy1, h1); - ps->insert_vertex(kx2, ky2, h2); - } - - ps->append_poly(); - if (pt->is_inner) { - ps->append_vertex(jx2, jy2, h2); - ps->append_vertex(kx2, ky2, h2); - ps->append_vertex(jx1, jy1, h1); - } else { - ps->insert_vertex(jx2, jy2, h2); - ps->insert_vertex(kx2, ky2, h2); - ps->insert_vertex(jx1, jy1, h1); - } - } - } -} - -PolySet *DxfLinearExtrudeNode::render_polyset(render_mode_e) const +PolySet *DxfLinearExtrudeNode::evaluate_polyset(render_mode_e mode, + PolySetEvaluator *evaluator) const { - QString key = mk_cache_id(); - if (PolySet::ps_cache.contains(key)) { - PRINT(PolySet::ps_cache[key]->msg); - return PolySet::ps_cache[key]->ps->link(); + if (!evaluator) { + PRINTF("WARNING: No suitable PolySetEvaluator found for %s module!", this->name().c_str()); + PolySet *ps = new PolySet(); + ps->is2d = true; + return ps; } print_messages_push(); - DxfData *dxf; - - if (filename.isEmpty()) - { -#ifdef ENABLE_CGAL - - // Before extruding, union all (2D) children nodes - // to a single DxfData, then tesselate this into a PolySet - CGAL_Nef_polyhedron N; - N.dim = 2; - foreach(AbstractNode * v, children) { - if (v->modinst->tag_background) - continue; - N.p2 += v->render_cgal_nef_polyhedron().p2; - } - dxf = new DxfData(N); - -#else // ENABLE_CGAL - PRINT("WARNING: Found linear_extrude() statement without dxf file but compiled without CGAL support!"); - dxf = new DxfData(); -#endif // ENABLE_CGAL - } else { - dxf = new DxfData(fn, fs, fa, filename, layername, origin_x, origin_y, scale); - } - - PolySet *ps = new PolySet(); - ps->convexity = convexity; - - double h1, h2; - - if (center) { - h1 = -height/2.0; - h2 = +height/2.0; - } else { - h1 = 0; - h2 = height; - } - bool first_open_path = true; - for (int i = 0; i < dxf->paths.count(); i++) - { - if (dxf->paths[i].is_closed) - continue; - if (first_open_path) { - PRINTF("WARING: Open paths in dxf_liniear_extrude(file = \"%s\", layer = \"%s\"):", - filename.toAscii().data(), layername.toAscii().data()); - first_open_path = false; - } - PRINTF(" %9.5f %10.5f ... %10.5f %10.5f", - dxf->paths[i].points.first()->x / scale + origin_x, - dxf->paths[i].points.first()->y / scale + origin_y, - dxf->paths[i].points.last()->x / scale + origin_x, - dxf->paths[i].points.last()->y / scale + origin_y); - } - - - if (has_twist) - { - dxf_tesselate(ps, dxf, 0, false, true, h1); - dxf_tesselate(ps, dxf, twist, true, true, h2); - for (int j = 0; j < slices; j++) - { - double t1 = twist*j / slices; - double t2 = twist*(j+1) / slices; - double g1 = h1 + (h2-h1)*j / slices; - double g2 = h1 + (h2-h1)*(j+1) / slices; - for (int i = 0; i < dxf->paths.count(); i++) - { - if (!dxf->paths[i].is_closed) - continue; - add_slice(ps, &dxf->paths[i], t1, t2, g1, g2); - } - } - } - else - { - dxf_tesselate(ps, dxf, 0, false, true, h1); - dxf_tesselate(ps, dxf, 0, true, true, h2); - for (int i = 0; i < dxf->paths.count(); i++) - { - if (!dxf->paths[i].is_closed) - continue; - add_slice(ps, &dxf->paths[i], 0, 0, h1, h2); - } - } + PolySet *ps = evaluator->evaluatePolySet(*this, mode); - PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link())); print_messages_pop(); - delete dxf; return ps; } -QString DxfLinearExtrudeNode::dump(QString indent) const +std::string DxfLinearExtrudeNode::toString() const { - if (dump_cache.isEmpty()) { - QString text; - QFileInfo fileInfo(filename); - text.sprintf("linear_extrude(file = \"%s\", cache = \"%x.%x\", layer = \"%s\", " - "height = %g, origin = [ %g %g ], scale = %g, center = %s, convexity = %d", - filename.toAscii().data(), (int)fileInfo.lastModified().toTime_t(), - (int)fileInfo.size(), layername.toAscii().data(), height, origin_x, - origin_y, scale, center ? "true" : "false", convexity); - if (has_twist) { - QString t2; - t2.sprintf(", twist = %g, slices = %d", twist, slices); - text += t2; - } - QString t3; - t3.sprintf(", $fn = %g, $fa = %g, $fs = %g) {\n", fn, fa, fs); - text += t3; - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - text += indent + "}\n"; - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; + std::stringstream stream; + + QString text; + + stream << this->name() << "(" + "file = \"" << this->filename << "\", " + "cache = \"" << QFileInfo(this->filename) << "\", " + "layer = \"" << this->layername << "\", " + "height = " << std::dec << this->height << ", " + "origin = [ " << this->origin_x << " " << this->origin_y << " ], " + "scale = " << this->scale << ", " + "center = " << (this->center?"true":"false") << ", " + "convexity = " << this->convexity; + + if (this->has_twist) { + stream << ", twist = " << this->twist << ", slices = " << this->slices; } - return dump_cache; + stream << ", $fn = " << this->fn << ", $fa = " << this->fa << ", $fs = " << this->fs << ")"; + + return stream.str(); } - diff --git a/src/dxflinextrudenode.h b/src/dxflinextrudenode.h new file mode 100644 index 0000000..dba03e8 --- /dev/null +++ b/src/dxflinextrudenode.h @@ -0,0 +1,30 @@ +#ifndef DXFLINEXTRUDENODE_H_ +#define DXFLINEXTRUDENODE_H_ + +#include "node.h" +#include "visitor.h" + +class DxfLinearExtrudeNode : public AbstractPolyNode +{ +public: + DxfLinearExtrudeNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { + convexity = slices = 0; + fn = fs = fa = height = twist = 0; + origin_x = origin_y = scale = 0; + center = has_twist = false; + } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "linear_extrude"; } + + int convexity, slices; + double fn, fs, fa, height, twist; + double origin_x, origin_y, scale; + bool center, has_twist; + QString filename, layername; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *) const; +}; + +#endif diff --git a/src/dxfrotextrude.cc b/src/dxfrotextrude.cc index 1be2265..ec75a52 100644 --- a/src/dxfrotextrude.cc +++ b/src/dxfrotextrude.cc @@ -24,16 +24,20 @@ * */ +#include "dxfrotextrudenode.h" #include "module.h" -#include "node.h" #include "context.h" #include "printutils.h" #include "builtin.h" #include "polyset.h" #include "dxfdata.h" #include "progress.h" +#include "visitor.h" +#include "PolySetEvaluator.h" #include "openscad.h" // get_fragments_from_r() +#include <sstream> + #include <QTime> #include <QApplication> #include <QProgressDialog> @@ -47,22 +51,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class DxfRotateExtrudeNode : public AbstractPolyNode -{ -public: - int convexity; - double fn, fs, fa; - double origin_x, origin_y, scale; - QString filename, layername; - DxfRotateExtrudeNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { - convexity = 0; - fn = fs = fa = 0; - origin_x = origin_y = scale = 0; - } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *DxfRotateExtrudeModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { DxfRotateExtrudeNode *node = new DxfRotateExtrudeNode(inst); @@ -83,12 +71,10 @@ AbstractNode *DxfRotateExtrudeModule::evaluate(const Context *ctx, const ModuleI Value origin = c.lookup_variable("origin", true); Value scale = c.lookup_variable("scale", true); - if(!file.text.isNull()) - node->filename = c.get_absolute_path(file.text); - else - node->filename = file.text; + if (!file.text.empty()) + node->filename = c.get_absolute_path(QString::fromStdString(file.text)); - node->layername = layer.text; + node->layername = QString::fromStdString(layer.text); node->convexity = (int)convexity.num; origin.getv2(node->origin_x, node->origin_y); node->scale = scale.num; @@ -116,131 +102,37 @@ void register_builtin_dxf_rotate_extrude() builtin_modules["rotate_extrude"] = new DxfRotateExtrudeModule(); } -PolySet *DxfRotateExtrudeNode::render_polyset(render_mode_e) const +PolySet *DxfRotateExtrudeNode::evaluate_polyset(render_mode_e mode, + PolySetEvaluator *evaluator) const { - QString key = mk_cache_id(); - if (PolySet::ps_cache.contains(key)) { - PRINT(PolySet::ps_cache[key]->msg); - return PolySet::ps_cache[key]->ps->link(); + if (!evaluator) { + PRINTF("WARNING: No suitable PolySetEvaluator found for %s module!", this->name().c_str()); + PolySet *ps = new PolySet(); + ps->is2d = true; + return ps; } print_messages_push(); - DxfData *dxf; - - if (filename.isEmpty()) - { -#ifdef ENABLE_CGAL - CGAL_Nef_polyhedron N; - N.dim = 2; - foreach(AbstractNode * v, children) { - if (v->modinst->tag_background) - continue; - N.p2 += v->render_cgal_nef_polyhedron().p2; - } - dxf = new DxfData(N); - -#else // ENABLE_CGAL - PRINT("WARNING: Found rotate_extrude() statement without dxf file but compiled without CGAL support!"); - dxf = new DxfData(); -#endif // ENABLE_CGAL - } else { - dxf = new DxfData(fn, fs, fa, filename, layername, origin_x, origin_y, scale); - } - PolySet *ps = new PolySet(); - ps->convexity = convexity; - - for (int i = 0; i < dxf->paths.count(); i++) - { - double max_x = 0; - for (int j = 0; j < dxf->paths[i].points.count(); j++) { - max_x = fmax(max_x, dxf->paths[i].points[j]->x); - } - - int fragments = get_fragments_from_r(max_x, fn, fs, fa); - - double ***points; - points = new double**[fragments]; - for (int j=0; j < fragments; j++) { - points[j] = new double*[dxf->paths[i].points.count()]; - for (int k=0; k < dxf->paths[i].points.count(); k++) - points[j][k] = new double[3]; - } - - for (int j = 0; j < fragments; j++) { - double a = (j*2*M_PI) / fragments; - for (int k = 0; k < dxf->paths[i].points.count(); k++) { - if (dxf->paths[i].points[k]->x == 0) { - points[j][k][0] = 0; - points[j][k][1] = 0; - } else { - points[j][k][0] = dxf->paths[i].points[k]->x * sin(a); - points[j][k][1] = dxf->paths[i].points[k]->x * cos(a); - } - points[j][k][2] = dxf->paths[i].points[k]->y; - } - } - - for (int j = 0; j < fragments; j++) { - int j1 = j + 1 < fragments ? j + 1 : 0; - for (int k = 0; k < dxf->paths[i].points.count(); k++) { - int k1 = k + 1 < dxf->paths[i].points.count() ? k + 1 : 0; - if (points[j][k][0] != points[j1][k][0] || - points[j][k][1] != points[j1][k][1] || - points[j][k][2] != points[j1][k][2]) { - ps->append_poly(); - ps->append_vertex(points[j ][k ][0], - points[j ][k ][1], points[j ][k ][2]); - ps->append_vertex(points[j1][k ][0], - points[j1][k ][1], points[j1][k ][2]); - ps->append_vertex(points[j ][k1][0], - points[j ][k1][1], points[j ][k1][2]); - } - if (points[j][k1][0] != points[j1][k1][0] || - points[j][k1][1] != points[j1][k1][1] || - points[j][k1][2] != points[j1][k1][2]) { - ps->append_poly(); - ps->append_vertex(points[j ][k1][0], - points[j ][k1][1], points[j ][k1][2]); - ps->append_vertex(points[j1][k ][0], - points[j1][k ][1], points[j1][k ][2]); - ps->append_vertex(points[j1][k1][0], - points[j1][k1][1], points[j1][k1][2]); - } - } - } - - for (int j=0; j < fragments; j++) { - for (int k=0; k < dxf->paths[i].points.count(); k++) - delete[] points[j][k]; - delete[] points[j]; - } - delete[] points; - } - - PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link())); + PolySet *ps = evaluator->evaluatePolySet(*this, mode); + print_messages_pop(); - delete dxf; return ps; } -QString DxfRotateExtrudeNode::dump(QString indent) const +std::string DxfRotateExtrudeNode::toString() const { - if (dump_cache.isEmpty()) { - QString text; - QFileInfo fileInfo(filename); - text.sprintf("rotate_extrude(file = \"%s\", cache = \"%x.%x\", layer = \"%s\", " - "origin = [ %g %g ], scale = %g, convexity = %d, " - "$fn = %g, $fa = %g, $fs = %g) {\n", - filename.toAscii().data(), (int)fileInfo.lastModified().toTime_t(), - (int)fileInfo.size(),layername.toAscii().data(), origin_x, origin_y, - scale, convexity, fn, fa, fs); - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - text += indent + "}\n"; - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; - } - return dump_cache; + std::stringstream stream; + + stream << this->name() << "(" + "file = \"" << this->filename << "\", " + "cache = \"" << QFileInfo(this->filename) << "\", " + "layer = \"" << this->layername << "\", " + "origin = [ " << std::dec << this->origin_x << " " << this->origin_y << " ], " + "scale = " << this->scale << ", " + "convexity = " << this->convexity << ", " + "$fn = " << this->fn << ", $fa = " << this->fa << ", $fs = " << this->fs << ")"; + + return stream.str(); } - diff --git a/src/dxfrotextrudenode.h b/src/dxfrotextrudenode.h new file mode 100644 index 0000000..38ca087 --- /dev/null +++ b/src/dxfrotextrudenode.h @@ -0,0 +1,28 @@ +#ifndef DXFROTEXTRUDENODE_H_ +#define DXFROTEXTRUDENODE_H_ + +#include "node.h" +#include "visitor.h" + +class DxfRotateExtrudeNode : public AbstractPolyNode +{ +public: + DxfRotateExtrudeNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { + convexity = 0; + fn = fs = fa = 0; + origin_x = origin_y = scale = 0; + } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "rotate_extrude"; } + + int convexity; + double fn, fs, fa; + double origin_x, origin_y, scale; + QString filename, layername; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *) const; +}; + +#endif diff --git a/src/export.cc b/src/export.cc index 8e0ab16..1f9046e 100644 --- a/src/export.cc +++ b/src/export.cc @@ -30,6 +30,7 @@ #include <QApplication> #include <QProgressDialog> +#include <QTextStream> #include <errno.h> #ifdef ENABLE_CGAL @@ -72,9 +73,10 @@ void cgal_nef3_to_polyset(PolySet *ps, CGAL_Nef_polyhedron *root_N) } /*! - Saves the current 3D CGAL Nef polyhedron as STL to the given absolute filename. + Saves the current 3D CGAL Nef polyhedron as STL to the given file. + The file must be open. */ -void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd) +void export_stl(CGAL_Nef_polyhedron *root_N, QTextStream &output, QProgressDialog *pd) { CGAL_Polyhedron P; root_N->p3.convert_to_Polyhedron(P); @@ -86,14 +88,7 @@ void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog * setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output - FILE *f = fopen(filename.toUtf8().data(), "w"); - if (!f) { - PRINTA("Can't open STL file \"%1\" for STL export: %2", - filename, QString(strerror(errno))); - set_output_handler(NULL, NULL); - return; - } - fprintf(f, "solid OpenSCAD_Model\n"); + output << "solid OpenSCAD_Model\n"; int facet_count = 0; for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) { @@ -127,14 +122,16 @@ void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog * // Avoid generating normals for polygons with zero area double eps = 0.000001; if (nlength < eps) nlength = 1.0; - fprintf(f, " facet normal %f %f %f\n", - nx / nlength, ny / nlength, nz / nlength); - fprintf(f, " outer loop\n"); - fprintf(f, " vertex %s\n", vs1.toAscii().data()); - fprintf(f, " vertex %s\n", vs2.toAscii().data()); - fprintf(f, " vertex %s\n", vs3.toAscii().data()); - fprintf(f, " endloop\n"); - fprintf(f, " endfacet\n"); + output << " facet normal " + << nx / nlength << " " + << ny / nlength << " " + << nz / nlength << "\n"; + output << " outer loop\n"; + output << " vertex " << vs1 << "\n"; + output << " vertex " << vs2 << "\n"; + output << " vertex " << vs3 << "\n"; + output << " endloop\n"; + output << " endfacet\n"; } } while (hc != hc_end); if (pd) { @@ -143,12 +140,11 @@ void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog * } } - fprintf(f, "endsolid OpenSCAD_Model\n"); - fclose(f); + output << "endsolid OpenSCAD_Model\n"; setlocale(LC_NUMERIC, ""); // Set default locale } -void export_off(CGAL_Nef_polyhedron*, QString, QProgressDialog*) +void export_off(CGAL_Nef_polyhedron*, QTextStream&, QProgressDialog*) { PRINTF("WARNING: OFF import is not implemented yet."); } @@ -156,29 +152,20 @@ void export_off(CGAL_Nef_polyhedron*, QString, QProgressDialog*) /*! Saves the current 2D CGAL Nef polyhedron as DXF to the given absolute filename. */ -void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *) +void export_dxf(CGAL_Nef_polyhedron *root_N, QTextStream &output, QProgressDialog *) { - FILE *f = fopen(filename.toUtf8().data(), "w"); - if (!f) { - PRINTA("Can't open DXF file \"%1\" for DXF export: %2", - filename, QString(strerror(errno))); - set_output_handler(NULL, NULL); - return; - } - setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output // Some importers (e.g. Inkscape) needs a BLOCKS section to be present - fprintf(f, " 0\n" - "SECTION\n" - " 2\n" - "BLOCKS\n" - " 0\n" - "ENDSEC\n"); - - fprintf(f, " 0\n" - "SECTION\n" - " 2\n" - "ENTITIES\n"); + output << " 0\n" + << "SECTION\n" + << " 2\n" + << "BLOCKS\n" + << " 0\n" + << "ENDSEC\n" + << " 0\n" + << "SECTION\n" + << " 2\n" + << "ENTITIES\n"; DxfData dd(*root_N); for (int i=0; i<dd.paths.size(); i++) @@ -190,37 +177,38 @@ void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog * double y1 = p1->y; double x2 = p2->x; double y2 = p2->y; - fprintf(f, " 0\n"); - fprintf(f, "LINE\n"); + output << " 0\n" + << "LINE\n"; // Some importers (e.g. Inkscape) needs a layer to be specified - fprintf(f, " 8\n"); - fprintf(f, "0\n"); - fprintf(f, " 10\n"); - fprintf(f, "%f\n", x1); - fprintf(f, " 11\n"); - fprintf(f, "%f\n", x2); - fprintf(f, " 20\n"); - fprintf(f, "%f\n", y1); - fprintf(f, " 21\n"); - fprintf(f, "%f\n", y2); + output << " 8\n" + << "0\n" + << " 10\n" + << x1 << "\n" + << " 11\n" + << x2 << "\n" + << " 20\n" + << y1 << "\n" + << " 21\n" + << y2 << "\n"; } } + + output << " 0\n" + << "ENDSEC\n"; - fprintf(f, " 0\n"); - fprintf(f, "ENDSEC\n"); // Some importers (e.g. Inkscape) needs an OBJECTS section with a DICTIONARY entry - fprintf(f, " 0\n" - "SECTION\n" - " 2\n" - "OBJECTS\n" - " 0\n" - "DICTIONARY\n" - " 0\n" - "ENDSEC\n"); - fprintf(f, " 0\n"); - fprintf(f, "EOF\n"); - - fclose(f); + output << " 0\n" + << "SECTION\n" + << " 2\n" + << "OBJECTS\n" + << " 0\n" + << "DICTIONARY\n" + << " 0\n" + << "ENDSEC\n"; + + output << " 0\n" + <<"EOF\n"; + setlocale(LC_NUMERIC, ""); // Set default locale } diff --git a/src/export.h b/src/export.h index 619d9e0..d35246c 100644 --- a/src/export.h +++ b/src/export.h @@ -2,10 +2,11 @@ #define EXPORT_H_ #ifdef ENABLE_CGAL +#include "cgal.h" void cgal_nef3_to_polyset(PolySet *ps, CGAL_Nef_polyhedron *root_N); -void export_stl(class CGAL_Nef_polyhedron *root_N, QString filename, class QProgressDialog *pd); -void export_off(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd); -void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd); +void export_stl(class CGAL_Nef_polyhedron *root_N, class QTextStream &output, class QProgressDialog *pd); +void export_off(CGAL_Nef_polyhedron *root_N, class QTextStream &output, QProgressDialog *pd); +void export_dxf(CGAL_Nef_polyhedron *root_N, class QTextStream &output, QProgressDialog *pd); #endif #endif diff --git a/src/expr.cc b/src/expr.cc index 50950d0..72f92a0 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -27,6 +27,8 @@ #include "expression.h" #include "value.h" #include "context.h" +#include <assert.h> +#include <sstream> Expression::Expression() { @@ -109,7 +111,7 @@ Value Expression::evaluate(const Context *context) const Value v; v.type = Value::VECTOR; for (int i = 0; i < children.size(); i++) - v.vec.append(new Value(children[i]->evaluate(context))); + v.append(new Value(children[i]->evaluate(context))); return v; } if (type == "L") @@ -143,45 +145,62 @@ Value Expression::evaluate(const Context *context) const abort(); } -QString Expression::dump() const +std::string Expression::toString() const { - if (type == "*" || type == "/" || type == "%" || type == "+" || type == "-" || - type == "<" || type == "<=" || type == "==" || type == "!=" || type == ">=" || type == ">") - return QString("(%1 %2 %3)").arg(children[0]->dump(), QString(type), children[1]->dump()); - if (type == "?:") - return QString("(%1 ? %2 : %3)").arg(children[0]->dump(), children[1]->dump(), children[2]->dump()); - if (type == "[]") - return QString("(%1[%2])").arg(children[0]->dump(), children[1]->dump()); - if (type == "I") - return QString("(-%1)").arg(children[0]->dump()); - if (type == "C") - return const_value->dump(); - if (type == "R") - return QString("[%1 : %2 : %3]").arg(children[0]->dump(), children[1]->dump(), children[2]->dump()); - if (type == "V") { - QString text = QString("["); + std::stringstream stream; + + if (this->type == "*" || this->type == "/" || this->type == "%" || this->type == "+" || + this->type == "-" || this->type == "<" || this->type == "<=" || this->type == "==" || + this->type == "!=" || this->type == ">=" || this->type == ">") { + stream << "(" << *children[0] << " " << this->type << " " << *children[1] << ")"; + } + else if (this->type == "?:") { + stream << "(" << *children[0] << " ? " << this->type << " : " << *children[1] << ")"; + } + else if (this->type == "[]") { + stream << "(" << *children[0] << "[" << *children[1] << "])"; + } + else if (this->type == "I") { + stream << "(-" << *children[0] << ")"; + } + else if (this->type == "C") { + stream << *const_value; + } + else if (this->type == "R") { + stream << "[" << *children[0] << " : " << *children[1] << " : " << children[2] << "]"; + } + else if (this->type == "V") { + stream << "["; for (int i=0; i < children.size(); i++) { - if (i > 0) - text += QString(", "); - text += children[i]->dump(); + if (i > 0) stream << ", "; + stream << *children[i]; } - return text + QString("]"); + stream << "]"; } - if (type == "L") - return var_name; - if (type == "N") - return QString("(%1.%2)").arg(children[0]->dump(), var_name); - if (type == "F") { - QString text = call_funcname + QString("("); + else if (this->type == "L") { + stream << var_name; + } + else if (this->type == "N") { + stream << "(" << *children[0] << "." << var_name << ")"; + } + else if (this->type == "F") { + stream << call_funcname << "("; for (int i=0; i < children.size(); i++) { - if (i > 0) - text += QString(", "); - if (!call_argnames[i].isEmpty()) - text += call_argnames[i] + QString(" = "); - text += children[i]->dump(); + if (i > 0) stream << ", "; + if (!call_argnames[i].isEmpty()) stream << call_argnames[i] << " = "; + stream << *children[i]; } - return text + QString(")"); + stream << ")"; } - abort(); + else { + assert(false && "Illegal expression type"); + } + + return stream.str(); } +std::ostream &operator<<(std::ostream &stream, const Expression &expr) +{ + stream << expr.toString(); + return stream; +} diff --git a/src/expression.h b/src/expression.h index dadcea0..38043db 100644 --- a/src/expression.h +++ b/src/expression.h @@ -34,7 +34,9 @@ public: ~Expression(); Value evaluate(const class Context *context) const; - QString dump() const; + std::string toString() const; }; +std::ostream &operator<<(std::ostream &stream, const Expression &expr); + #endif diff --git a/src/func.cc b/src/func.cc index 2608960..fd4bd7e 100644 --- a/src/func.cc +++ b/src/func.cc @@ -29,8 +29,8 @@ #include "context.h" #include "dxfdim.h" #include "builtin.h" +#include <sstream> #include "mathc99.h" -#include <time.h> AbstractFunction::~AbstractFunction() { @@ -70,9 +70,9 @@ QString Function::dump(QString indent, QString name) const text += QString(", "); text += argnames[i]; if (argexpr[i]) - text += QString(" = ") + argexpr[i]->dump(); + text += QString(" = ") + QString::fromStdString(argexpr[i]->toString()); } - text += QString(") = %1;\n").arg(expr->dump()); + text += QString(") = %1;\n").arg(QString::fromStdString(expr->toString())); return text; } @@ -164,7 +164,7 @@ Value builtin_rands(const Context *, const QVector<QString>&, const QVector<Valu for (int i=0; i<args[2].num; i++) { Value * r = new Value(frand(args[0].num, args[1].num)); - v.vec.append(r); + v.vec.push_back(r); } return v; @@ -304,15 +304,12 @@ Value builtin_ln(const Context *, const QVector<QString>&, const QVector<Value> Value builtin_str(const Context *, const QVector<QString>&, const QVector<Value> &args) { - QString str; - for (int i = 0; i < args.size(); i++) - { - if (args[i].type == Value::STRING) - str += args[i].text; - else - str += args[i].dump(); + std::stringstream stream; + + for (int i = 0; i < args.size(); i++) { + stream << args[i]; } - return Value(str); + return Value(stream.str()); } Value builtin_lookup(const Context *, const QVector<QString>&, const QVector<Value> &args) diff --git a/src/function.h b/src/function.h index 3bb89c2..7b58e38 100644 --- a/src/function.h +++ b/src/function.h @@ -3,6 +3,7 @@ #include "value.h" #include <QString> +#include <QVector> class AbstractFunction { diff --git a/src/glview.cc b/src/glview.cc index 9d82443..896ff0a 100644 --- a/src/glview.cc +++ b/src/glview.cc @@ -50,32 +50,42 @@ GLView::GLView(QWidget *parent) : QGLWidget(parent), renderer(NULL) { - viewer_distance = 500; - object_rot_x = 35; - object_rot_y = 0; - object_rot_z = 25; - object_trans_x = 0; - object_trans_y = 0; - object_trans_z = 0; + init(); +} - mouse_drag_active = false; +GLView::GLView(const QGLFormat & format, QWidget *parent) : QGLWidget(format, parent) +{ + init(); +} - orthomode = false; - showaxes = false; - showcrosshairs = false; - showedges = false; - showfaces = true; +void GLView::init() +{ + this->viewer_distance = 500; + this->object_rot_x = 35; + this->object_rot_y = 0; + this->object_rot_z = 25; + this->object_trans_x = 0; + this->object_trans_y = 0; + this->object_trans_z = 0; + + this->mouse_drag_active = false; + + this->showedges = false; + this->showfaces = true; + this->orthomode = false; + this->showaxes = false; + this->showcrosshairs = false; for (int i = 0; i < 10; i++) - shaderinfo[i] = 0; + this->shaderinfo[i] = 0; - statusLabel = NULL; + this->statusLabel = NULL; setMouseTracking(true); #ifdef ENABLE_OPENCSG - opencsg_support = true; + this->opencsg_support = true; static int sId = 0; - opencsg_id = sId++; + this->opencsg_id = sId++; #endif } @@ -527,4 +537,3 @@ void GLView::mouseReleaseEvent(QMouseEvent*) mouse_drag_active = false; releaseMouse(); } - @@ -1,6 +1,7 @@ #ifndef GRID_H_ #define GRID_H_ +#include <QHash> #include "mathc99.h" #ifdef WIN32 typedef __int64 int64_t; @@ -8,7 +9,6 @@ typedef __int64 int64_t; #include <stdint.h> #endif #include <stdlib.h> -#include <QHash> const double GRID_COARSE = 0.001; const double GRID_FINE = 0.000001; diff --git a/src/import.cc b/src/import.cc index a924e24..fdac5b9 100644 --- a/src/import.cc +++ b/src/import.cc @@ -24,8 +24,9 @@ * */ +#include "importnode.h" + #include "module.h" -#include "node.h" #include "polyset.h" #include "context.h" #include "builtin.h" @@ -37,12 +38,8 @@ #include <QFile> #include <sys/types.h> #include <sys/stat.h> - -enum import_type_e { - TYPE_STL, - TYPE_OFF, - TYPE_DXF -}; +#include <sstream> +#include <assert.h> class ImportModule : public AbstractModule { @@ -52,26 +49,12 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class ImportNode : public AbstractPolyNode -{ -public: - import_type_e type; - QString filename; - QString layername; - int convexity; - double fn, fs, fa; - double origin_x, origin_y, scale; - ImportNode(const ModuleInstantiation *mi, import_type_e type) : AbstractPolyNode(mi), type(type) { } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *ImportModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { ImportNode *node = new ImportNode(inst, type); QVector<QString> argnames; - if (type == TYPE_DXF) { + if (this->type == TYPE_DXF) { argnames = QVector<QString>() << "file" << "layer" << "convexity" << "origin" << "scale"; } else { argnames = QVector<QString>() << "file" << "convexity"; @@ -94,8 +77,10 @@ AbstractNode *ImportModule::evaluate(const Context *ctx, const ModuleInstantiati node->fs = c.lookup_variable("$fs").num; node->fa = c.lookup_variable("$fa").num; - node->filename = c.get_absolute_path(c.lookup_variable("file").text); - node->layername = c.lookup_variable("layer", true).text; + Value v = c.lookup_variable("file"); + node->filename = c.get_absolute_path(QString::fromStdString(v.text)); +// node->filename = c.get_absolute_path(QString::fromStdString(c.lookup_variable("file").text)); + node->layername = QString::fromStdString(c.lookup_variable("layer", true).text); node->convexity = c.lookup_variable("convexity", true).num; if (node->convexity <= 0) @@ -120,17 +105,17 @@ void register_builtin_import() builtin_modules["import_dxf"] = new ImportModule(TYPE_DXF); } -PolySet *ImportNode::render_polyset(render_mode_e) const +PolySet *ImportNode::evaluate_polyset(render_mode_e, class PolySetEvaluator *) const { PolySet *p = new PolySet(); - p->convexity = convexity; + p->convexity = this->convexity; - if (type == TYPE_STL) + if (this->type == TYPE_STL) { - handle_dep(filename); - QFile f(filename); + handle_dep(this->filename); + QFile f(this->filename); if (!f.open(QIODevice::ReadOnly)) { - PRINTF("WARNING: Can't open import file `%s'.", filename.toAscii().data()); + PRINTF("WARNING: Can't open import file `%s'.", this->filename.toAscii().data()); return p; } @@ -202,14 +187,14 @@ PolySet *ImportNode::render_polyset(render_mode_e) const } } - if (type == TYPE_OFF) + if (this->type == TYPE_OFF) { PRINTF("WARNING: OFF import is not implemented yet."); } - if (type == TYPE_DXF) + if (this->type == TYPE_DXF) { - DxfData dd(fn, fs, fa, filename, layername, origin_x, origin_y, scale); + DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layername, this->origin_x, this->origin_y, this->scale); p->is2d = true; dxf_tesselate(p, &dd, 0, true, false, 0); dxf_border_to_ps(p, &dd); @@ -218,28 +203,56 @@ PolySet *ImportNode::render_polyset(render_mode_e) const return p; } -QString ImportNode::dump(QString indent) const +std::string ImportNode::toString() const { - if (dump_cache.isEmpty()) { - QString text; - struct stat st; - memset(&st, 0, sizeof(struct stat)); - stat(filename.toAscii().data(), &st); - if (type == TYPE_STL) - text.sprintf("import_stl(file = \"%s\", cache = \"%x.%x\", convexity = %d);\n", - filename.toAscii().data(), (int)st.st_mtime, (int)st.st_size, convexity); - if (type == TYPE_OFF) - text.sprintf("import_off(file = \"%s\", cache = \"%x.%x\", convexity = %d);\n", - filename.toAscii().data(), (int)st.st_mtime, (int)st.st_size, convexity); - if (type == TYPE_DXF) - text.sprintf("import_dxf(file = \"%s\", cache = \"%x.%x\", layer = \"%s\", " - "origin = [ %g %g ], scale = %g, convexity = %d, " - "$fn = %g, $fa = %g, $fs = %g);\n", - filename.toAscii().data(), (int)st.st_mtime, (int)st.st_size, - layername.toAscii().data(), origin_x, origin_y, scale, convexity, - fn, fa, fs); - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; + std::stringstream stream; + + QString text; + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(this->filename.toAscii().data(), &st); + + stream << this->name(); + switch (this->type) { + case TYPE_STL: + stream << "(file = \"" << this->filename << "\", " + "cache = \"" << std::hex << (int)st.st_mtime << "." << (int)st.st_size << "\", " + "convexity = " << std::dec << this->convexity << ")"; + break; + case TYPE_OFF: + stream << "(file = \"" << this->filename << "\", " + "cache = \"" << std::hex << (int)st.st_mtime << "." << (int)st.st_size << "\", " + "convexity = " << std::dec << this->convexity << ")"; + break; + case TYPE_DXF: + stream << "(file = \"" << this->filename << "\", " + "cache = \"" << std::hex << (int)st.st_mtime << "." << (int)st.st_size << "\", " + "layer = \"" << this->layername << "\", " + "origin = [ " << std::dec << this->origin_x << " " << this->origin_y << " ], " + "scale = " << this->scale << ", " + "convexity = " << this->convexity << ", " + "$fn = " << this->fn << ", $fa = " << this->fa << ", $fs = " << this->fs << ")"; + break; + default: + assert(false); } - return dump_cache; + + return stream.str(); } +std::string ImportNode::name() const +{ + switch (this->type) { + case TYPE_STL: + return "import_stl"; + break; + case TYPE_OFF: + return "import_off"; + break; + case TYPE_DXF: + return "import_dxf"; + break; + default: + assert(false); + } +} diff --git a/src/importnode.h b/src/importnode.h new file mode 100644 index 0000000..bdbf193 --- /dev/null +++ b/src/importnode.h @@ -0,0 +1,32 @@ +#ifndef IMPORTNODE_H_ +#define IMPORTNODE_H_ + +#include "node.h" +#include "visitor.h" + +enum import_type_e { + TYPE_STL, + TYPE_OFF, + TYPE_DXF +}; + +class ImportNode : public AbstractPolyNode +{ +public: + ImportNode(const ModuleInstantiation *mi, import_type_e type) : AbstractPolyNode(mi), type(type) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const; + + import_type_e type; + QString filename; + QString layername; + int convexity; + double fn, fs, fa; + double origin_x, origin_y, scale; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *) const; +}; + +#endif diff --git a/src/lexer.l b/src/lexer.l index 0da3f5d..bc61c20 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -88,7 +88,7 @@ DIGIT [0-9] include[ \t\r\n>]*"<" { BEGIN(include); } <include>{ -[^\t\r\n>]+"/" { filepath = yytext; } +[^\t\r\n>]*"/" { filepath = yytext; } [^\t\r\n>/]+ { filename = yytext; } ">" { BEGIN(INITIAL); includefile(); } } diff --git a/src/mainwin.cc b/src/mainwin.cc index 3453b1c..5bc16cb 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -40,11 +40,14 @@ #include "dxftess.h" #include "progress.h" #ifdef ENABLE_OPENCSG +#include "CSGTermEvaluator.h" #include "opencsgRenderer.h" #endif #ifdef USE_PROGRESSWIDGET #include "ProgressWidget.h" #endif +#include "CGALEvaluator.h" +#include "PolySetCGALEvaluator.h" #include "thrownTogetherRenderer.h" #include <QMenu> @@ -74,19 +77,26 @@ #include "qlanguagefactory.h" #endif +#include <algorithm> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> +using namespace boost::lambda; + #ifdef ENABLE_CGAL #include "cgalrenderer.h" #endif // ENABLE_CGAL +// Global application state +unsigned int GuiLocker::gui_locked = 0; + #define QUOTE(x__) # x__ #define QUOTED(x__) QUOTE(x__) static char helptitle[] = - "OpenSCAD " - QUOTED(OPENSCAD_VERSION) - " (www.openscad.org)\n"; + "OpenSCAD " QUOTED(OPENSCAD_VERSION) " (www.openscad.org)\n" + "Visitor refactored version\n"; static char copyrighttext[] = "Copyright (C) 2009-2011 Marius Kintel <marius@kintel.net> and Clifford Wolf <clifford@clifford.at>\n" "\n" @@ -137,11 +147,13 @@ MainWindow::MainWindow(const QString &filename) root_ctx.set_variable("$fa", Value(12.0)); root_ctx.set_variable("$t", Value(0.0)); + root_ctx.set_constant("PI",Value(M_PI)); + Value zero3; zero3.type = Value::VECTOR; - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); root_ctx.set_variable("$vpt", zero3); root_ctx.set_variable("$vpr", zero3); @@ -180,8 +192,8 @@ MainWindow::MainWindow(const QString &filename) editor->setLineWrapping(true); // Not designable setFont("", 0); // Init default font - screen->statusLabel = new QLabel(this); - statusBar()->addWidget(screen->statusLabel); + this->glview->statusLabel = new QLabel(this); + statusBar()->addWidget(this->glview->statusLabel); animate_timer = new QTimer(this); connect(animate_timer, SIGNAL(timeout()), this, SLOT(updateTVal())); @@ -276,7 +288,7 @@ MainWindow::MainWindow(const QString &filename) this->viewActionOpenCSG->setVisible(false); #else connect(this->viewActionOpenCSG, SIGNAL(triggered()), this, SLOT(viewModeOpenCSG())); - if (!screen->hasOpenCSGSupport()) { + if (!this->glview->hasOpenCSGSupport()) { this->viewActionOpenCSG->setEnabled(false); } #endif @@ -332,9 +344,9 @@ MainWindow::MainWindow(const QString &filename) connect(editor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool))); connect(editor->document(), SIGNAL(modificationChanged(bool)), fileActionSave, SLOT(setEnabled(bool))); #endif - connect(screen, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate())); + connect(this->glview, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate())); - connect(Preferences::inst(), SIGNAL(requestRedraw()), this->screen, SLOT(updateGL())); + connect(Preferences::inst(), SIGNAL(requestRedraw()), this->glview, SLOT(updateGL())); connect(Preferences::inst(), SIGNAL(fontChanged(const QString&,uint)), this, SLOT(setFont(const QString&,uint))); Preferences::inst()->apply(); @@ -572,7 +584,7 @@ AbstractNode *MainWindow::find_root_tag(AbstractNode *n) } /*! - Parse and evaluate the design -> this->root_node + Parse and evaluate the design => this->root_node */ void MainWindow::compile(bool procevents) { @@ -581,7 +593,7 @@ void MainWindow::compile(bool procevents) QApplication::processEvents(); // Invalidate renderers before we kill the CSG tree - screen->setRenderer(NULL); + this->glview->setRenderer(NULL); if (this->opencsgRenderer) { delete this->opencsgRenderer; this->opencsgRenderer = NULL; @@ -592,80 +604,83 @@ void MainWindow::compile(bool procevents) } // Remove previous CSG tree - if (root_module) { - delete root_module; - root_module = NULL; + if (this->root_module) { + delete this->root_module; + this->root_module = NULL; } - if (absolute_root_node) { - delete absolute_root_node; - absolute_root_node = NULL; + if (this->absolute_root_node) { + delete this->absolute_root_node; + this->absolute_root_node = NULL; } - if (root_raw_term) { - root_raw_term->unlink(); - root_raw_term = NULL; + if (this->root_raw_term) { + this->root_raw_term->unlink(); + this->root_raw_term = NULL; } - if (root_norm_term) { - root_norm_term->unlink(); - root_norm_term = NULL; + if (this->root_norm_term) { + this->root_norm_term->unlink(); + this->root_norm_term = NULL; } - if (root_chain) { - delete root_chain; - root_chain = NULL; + if (this->root_chain) { + delete this->root_chain; + this->root_chain = NULL; } - foreach(CSGTerm *v, highlight_terms) { - v->unlink(); - } - highlight_terms.clear(); - if (highlights_chain) { - delete highlights_chain; - highlights_chain = NULL; - } - foreach(CSGTerm *v, background_terms) { - v->unlink(); - } - background_terms.clear(); - if (background_chain) { - delete background_chain; - background_chain = NULL; - } - root_node = NULL; + std::for_each(this->highlight_terms.begin(), this->highlight_terms.end(), + bind(&CSGTerm::unlink, _1)); + + this->highlight_terms.clear(); + delete this->highlights_chain; + this->highlights_chain = NULL; + + std::for_each(this->background_terms.begin(), this->background_terms.end(), + bind(&CSGTerm::unlink, _1)); + this->background_terms.clear(); + delete this->background_chain; + this->background_chain = NULL; + + this->root_node = NULL; + this->tree.setRoot(NULL); // Initialize special variables - root_ctx.set_variable("$t", Value(e_tval->text().toDouble())); + this->root_ctx.set_variable("$t", Value(e_tval->text().toDouble())); Value vpt; vpt.type = Value::VECTOR; - vpt.vec.append(new Value(-screen->object_trans_x)); - vpt.vec.append(new Value(-screen->object_trans_y)); - vpt.vec.append(new Value(-screen->object_trans_z)); - root_ctx.set_variable("$vpt", vpt); + 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.vec.append(new Value(fmodf(360 - screen->object_rot_x + 90, 360))); - vpr.vec.append(new Value(fmodf(360 - screen->object_rot_y, 360))); - vpr.vec.append(new Value(fmodf(360 - screen->object_rot_z, 360))); + 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 - last_compiled_doc = editor->toPlainText(); - root_module = parse((last_compiled_doc + "\n" + commandline_commands).toAscii().data(), this->fileName.isEmpty() ? "" : QFileInfo(this->fileName).absolutePath().toLocal8Bit(), false); + this->last_compiled_doc = editor->toPlainText(); + this->root_module = parse((this->last_compiled_doc + "\n" + + commandline_commands).toAscii().data(), + this->fileName.isEmpty() ? + "" : + QFileInfo(this->fileName).absolutePath().toLocal8Bit(), + false); // Error highlighting - if (highlighter) { - delete highlighter; - highlighter = NULL; + if (this->highlighter) { + delete this->highlighter; + this->highlighter = NULL; } if (parser_error_pos >= 0) { - highlighter = new Highlighter(editor->document()); + this->highlighter = new Highlighter(editor->document()); } - if (!root_module) { + if (!this->root_module) { if (!animate_panel->isVisible()) { #ifdef _QCODE_EDIT_ QDocumentCursor cursor = editor->cursor(); @@ -685,19 +700,23 @@ void MainWindow::compile(bool procevents) QApplication::processEvents(); AbstractNode::resetIndexCounter(); - root_inst = ModuleInstantiation(); - absolute_root_node = root_module->evaluate(&root_ctx, &root_inst); + this->root_inst = ModuleInstantiation(); + this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); - if (!absolute_root_node) + if (!this->absolute_root_node) goto fail; // Do we have an explicit root node (! modifier)? - if (!(this->root_node = find_root_tag(absolute_root_node))) { - this->root_node = absolute_root_node; + if (!(this->root_node = find_root_tag(this->absolute_root_node))) { + this->root_node = this->absolute_root_node; } - root_node->dump(""); + // 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) { + if (1) { PRINT("Compilation finished."); if (procevents) QApplication::processEvents(); @@ -707,7 +726,7 @@ fail: PRINT("ERROR: Compilation failed! (no top level object found)"); } else { int line = 1; - QByteArray pb = last_compiled_doc.toAscii(); + QByteArray pb = this->last_compiled_doc.toAscii(); char *p = pb.data(); for (int i = 0; i < parser_error_pos; i++) { if (p[i] == '\n') @@ -735,13 +754,6 @@ void MainWindow::compileCSG(bool procevents) if (procevents) QApplication::processEvents(); - double m[20]; - - for (int i = 0; i < 16; i++) - m[i] = i % 5 == 0 ? 1.0 : 0.0; - for (int i = 16; i < 20; i++) - m[i] = -1; - // Main CSG evaluation QTime t; t.start(); @@ -762,7 +774,12 @@ void MainWindow::compileCSG(bool procevents) progress_report_prep(root_node, report_func, pd); try { - root_raw_term = root_node->render_csg_term(m, &highlight_terms, &background_terms); + // FIXME: put cache somewhere else as it's pretty useless now + QHash<std::string, CGAL_Nef_polyhedron> cache; + CGALEvaluator cgalevaluator(cache, this->tree); + PolySetCGALEvaluator psevaluator(cgalevaluator); + CSGTermEvaluator csgrenderer(this->tree, &psevaluator); + root_raw_term = csgrenderer.evaluateCSGTerm(*root_node, &highlight_terms, &background_terms); if (!root_raw_term) { PRINT("ERROR: CSG generation failed! (no top level object found)"); if (procevents) @@ -801,12 +818,12 @@ void MainWindow::compileCSG(bool procevents) if (highlight_terms.size() > 0) { - PRINTF("Compiling highlights (%d CSG Trees)...", highlight_terms.size()); + PRINTF("Compiling highlights (%zu CSG Trees)...", highlight_terms.size()); if (procevents) QApplication::processEvents(); highlights_chain = new CSGChain(); - for (int i = 0; i < highlight_terms.size(); i++) { + for (unsigned int i = 0; i < highlight_terms.size(); i++) { while (1) { CSGTerm *n = highlight_terms[i]->normalize(); highlight_terms[i]->unlink(); @@ -820,12 +837,12 @@ void MainWindow::compileCSG(bool procevents) if (background_terms.size() > 0) { - PRINTF("Compiling background (%d CSG Trees)...", background_terms.size()); + PRINTF("Compiling background (%zu CSG Trees)...", background_terms.size()); if (procevents) QApplication::processEvents(); background_chain = new CSGChain(); - for (int i = 0; i < background_terms.size(); i++) { + for (unsigned int i = 0; i < background_terms.size(); i++) { while (1) { CSGTerm *n = background_terms[i]->normalize(); background_terms[i]->unlink(); @@ -845,7 +862,7 @@ void MainWindow::compileCSG(bool procevents) this->opencsgRenderer = new OpenCSGRenderer(this->root_chain, this->highlights_chain, this->background_chain, - this->screen->shaderinfo); + this->glview->shaderinfo); } this->thrownTogetherRenderer = new ThrownTogetherRenderer(this->root_chain, this->highlights_chain, @@ -1027,7 +1044,7 @@ void MainWindow::pasteViewportTranslation() QTextCursor cursor = editor->textCursor(); #endif QString txt; - txt.sprintf("[ %.2f, %.2f, %.2f ]", -screen->object_trans_x, -screen->object_trans_y, -screen->object_trans_z); + txt.sprintf("[ %.2f, %.2f, %.2f ]", -this->glview->object_trans_x, -this->glview->object_trans_y, -this->glview->object_trans_z); cursor.insertText(txt); } @@ -1040,7 +1057,7 @@ void MainWindow::pasteViewportRotation() #endif QString txt; txt.sprintf("[ %.2f, %.2f, %.2f ]", - fmodf(360 - screen->object_rot_x + 90, 360), fmodf(360 - screen->object_rot_y, 360), fmodf(360 - screen->object_rot_z, 360)); + fmodf(360 - this->glview->object_rot_x + 90, 360), fmodf(360 - this->glview->object_rot_y, 360), fmodf(360 - this->glview->object_rot_z, 360)); cursor.insertText(txt); } @@ -1084,6 +1101,9 @@ bool MainWindow::checkModified() void MainWindow::actionReloadCompile() { + if (GuiLocker::isLocked()) return; + GuiLocker lock; + if (!checkModified()) return; console->clear(); @@ -1102,13 +1122,16 @@ void MainWindow::actionReloadCompile() else #endif { - screen->updateGL(); + this->glview->updateGL(); } clearCurrentOutput(); } void MainWindow::actionCompile() { + if (GuiLocker::isLocked()) return; + GuiLocker lock; + setCurrentOutput(); console->clear(); @@ -1128,7 +1151,7 @@ void MainWindow::actionCompile() } if (viewActionAnimate->isChecked() && e_dump->isChecked()) { - QImage img = screen->grabFrameBuffer(); + QImage img = this->glview->grabFrameBuffer(); QString filename; double s = e_fsteps->text().toDouble(); double t = e_tval->text().toDouble(); @@ -1143,15 +1166,19 @@ void MainWindow::actionCompile() void MainWindow::actionRenderCGAL() { + if (GuiLocker::isLocked()) return; + GuiLocker lock; + setCurrentOutput(); console->clear(); compile(true); - if (!root_module || !root_node) + if (!this->root_module || !this->root_node) { return; + } - this->screen->setRenderer(NULL); + this->glview->setRenderer(NULL); delete this->cgalRenderer; this->cgalRenderer = NULL; if (this->root_N) { @@ -1181,9 +1208,12 @@ void MainWindow::actionRenderCGAL() QApplication::processEvents(); - progress_report_prep(root_node, report_func, pd); + progress_report_prep(this->root_node, report_func, pd); try { - this->root_N = new CGAL_Nef_polyhedron(root_node->render_cgal_nef_polyhedron()); + // FIXME: put cache somewhere else as it's pretty useless now + QHash<std::string, CGAL_Nef_polyhedron> cache; + CGALEvaluator evaluator(cache, this->tree); + this->root_N = new CGAL_Nef_polyhedron(evaluator.evaluateCGALMesh(*this->root_node)); } catch (ProgressCancelException e) { PRINT("Rendering cancelled."); @@ -1192,8 +1222,9 @@ void MainWindow::actionRenderCGAL() if (this->root_N) { - PRINTF("Number of vertices currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.totalCost()); - PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size()); + // FIXME: Reenable cache cost info +// PRINTF("Number of vertices currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.totalCost()); +// PRINTF("Number of objects currently in CGAL cache: %d", AbstractNode::cgal_nef_cache.size()); QApplication::processEvents(); if (this->root_N->dim == 2) { @@ -1287,8 +1318,8 @@ void MainWindow::actionDisplayCSGTree() e->setTabStopWidth(30); e->setWindowTitle("CSG Tree Dump"); e->setReadOnly(true); - if (root_node) { - e->setPlainText(root_node->dump("")); + if (this->root_node) { + e->setPlainText(QString::fromStdString(this->tree.getString(*this->root_node))); } else { e->setPlainText("No CSG to dump. Please try compiling first..."); } @@ -1317,6 +1348,8 @@ void MainWindow::actionExportSTLorOFF(bool stl_mode) void MainWindow::actionExportSTLorOFF(bool) #endif { + if (GuiLocker::isLocked()) return; + GuiLocker lock; #ifdef ENABLE_CGAL setCurrentOutput(); @@ -1338,8 +1371,10 @@ void MainWindow::actionExportSTLorOFF(bool) return; } + QString suffix = stl_mode ? ".stl" : ".off"; QString stl_filename = QFileDialog::getSaveFileName(this, - stl_mode ? "Export STL File" : "Export OFF File", "", + stl_mode ? "Export STL File" : "Export OFF File", + this->fileName.isEmpty() ? "Untitled"+suffix : QFileInfo(this->fileName).baseName()+suffix, stl_mode ? "STL Files (*.stl)" : "OFF Files (*.off)"); if (stl_filename.isEmpty()) { PRINTF("No filename specified. %s export aborted.", stl_mode ? "STL" : "OFF"); @@ -1355,13 +1390,18 @@ void MainWindow::actionExportSTLorOFF(bool) pd->show(); QApplication::processEvents(); - if (stl_mode) - export_stl(this->root_N, stl_filename, pd); - else - export_off(this->root_N, stl_filename, pd); - - PRINTF("%s export finished.", stl_mode ? "STL" : "OFF"); + QFile file(stl_filename); + if (!file.open(QIODevice::ReadWrite)) { + PRINTA("Can't open file \"%1\" for export", stl_filename); + } + else { + QTextStream fstream(&file); + if (stl_mode) export_stl(this->root_N, fstream, pd); + else export_off(this->root_N, fstream, pd); + file.close(); + PRINTF("%s export finished.", stl_mode ? "STL" : "OFF"); + } delete pd; clearCurrentOutput(); @@ -1395,16 +1435,26 @@ void MainWindow::actionExportDXF() return; } - QString stl_filename = QFileDialog::getSaveFileName(this, - "Export DXF File", "", "DXF Files (*.dxf)"); - if (stl_filename.isEmpty()) { + QString dxf_filename = QFileDialog::getSaveFileName(this, + "Export DXF File", + this->fileName.isEmpty() ? "Untitled.dxf" : QFileInfo(this->fileName).baseName()+".dxf", + "DXF Files (*.dxf)"); + if (dxf_filename.isEmpty()) { PRINTF("No filename specified. DXF export aborted."); clearCurrentOutput(); return; } - export_dxf(this->root_N, stl_filename, NULL); - PRINTF("DXF export finished."); + QFile file(dxf_filename); + if (!file.open(QIODevice::ReadWrite)) { + PRINTA("Can't open file \"%1\" for export", dxf_filename); + } + else { + QTextStream fstream(&file); + export_dxf(this->root_N, fstream, NULL); + file.close(); + PRINTF("DXF export finished."); + } clearCurrentOutput(); #endif /* ENABLE_CGAL */ @@ -1412,7 +1462,7 @@ void MainWindow::actionExportDXF() void MainWindow::actionExportImage() { - QImage img = screen->grabFrameBuffer(); + QImage img = this->glview->grabFrameBuffer(); setCurrentOutput(); QString img_filename = QFileDialog::getSaveFileName(this, @@ -1430,9 +1480,12 @@ void MainWindow::actionExportImage() void MainWindow::actionFlushCaches() { - PolySet::ps_cache.clear(); +// FIXME: Polycache -> PolySetEvaluator +// FIXME: PolySetEvaluator->clearCache(); #ifdef ENABLE_CGAL - AbstractNode::cgal_nef_cache.clear(); +// FIXME: Flush caches through whatever channels we have + // CGALEvaluator::evaluator()->getCache().clear(); + // this->dumper->clearCache(); #endif dxf_dim_cache.clear(); dxf_cross_cache.clear(); @@ -1456,10 +1509,10 @@ void MainWindow::viewModeActionsUncheck() */ void MainWindow::viewModeOpenCSG() { - if (screen->hasOpenCSGSupport()) { + if (this->glview->hasOpenCSGSupport()) { viewModeActionsUncheck(); viewActionOpenCSG->setChecked(true); - screen->setRenderer(this->opencsgRenderer ? (Renderer *)this->opencsgRenderer : (Renderer *)this->thrownTogetherRenderer); + this->glview->setRenderer(this->opencsgRenderer ? (Renderer *)this->opencsgRenderer : (Renderer *)this->thrownTogetherRenderer); } else { viewModeThrownTogether(); } @@ -1473,17 +1526,17 @@ void MainWindow::viewModeCGALSurface() { viewModeActionsUncheck(); viewActionCGALSurfaces->setChecked(true); - screen->setShowFaces(true); - screen->setRenderer(this->cgalRenderer); - screen->updateGL(); + this->glview->setShowFaces(true); + this->glview->setRenderer(this->cgalRenderer); + this->glview->updateGL(); } void MainWindow::viewModeCGALGrid() { viewModeActionsUncheck(); viewActionCGALGrid->setChecked(true); - screen->setShowFaces(false); - screen->setRenderer(this->cgalRenderer); + this->glview->setShowFaces(false); + this->glview->setRenderer(this->cgalRenderer); } #endif /* ENABLE_CGAL */ @@ -1492,31 +1545,31 @@ void MainWindow::viewModeThrownTogether() { viewModeActionsUncheck(); viewActionThrownTogether->setChecked(true); - screen->setRenderer(this->thrownTogetherRenderer); + this->glview->setRenderer(this->thrownTogetherRenderer); } void MainWindow::viewModeShowEdges() { QSettings settings; settings.setValue("view/showEdges",viewActionShowEdges->isChecked()); - screen->setShowEdges(viewActionShowEdges->isChecked()); - screen->updateGL(); + this->glview->setShowEdges(viewActionShowEdges->isChecked()); + this->glview->updateGL(); } void MainWindow::viewModeShowAxes() { QSettings settings; settings.setValue("view/showAxes",viewActionShowAxes->isChecked()); - screen->setShowAxes(viewActionShowAxes->isChecked()); - screen->updateGL(); + this->glview->setShowAxes(viewActionShowAxes->isChecked()); + this->glview->updateGL(); } void MainWindow::viewModeShowCrosshairs() { QSettings settings; settings.setValue("view/showCrosshairs",viewActionShowCrosshairs->isChecked()); - screen->setShowCrosshairs(viewActionShowCrosshairs->isChecked()); - screen->updateGL(); + this->glview->setShowCrosshairs(viewActionShowCrosshairs->isChecked()); + this->glview->updateGL(); } void MainWindow::viewModeAnimate() @@ -1554,66 +1607,66 @@ void MainWindow::animateUpdate() void MainWindow::viewAngleTop() { - screen->object_rot_x = 90; - screen->object_rot_y = 0; - screen->object_rot_z = 0; - screen->updateGL(); + this->glview->object_rot_x = 90; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 0; + this->glview->updateGL(); } void MainWindow::viewAngleBottom() { - screen->object_rot_x = 270; - screen->object_rot_y = 0; - screen->object_rot_z = 0; - screen->updateGL(); + this->glview->object_rot_x = 270; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 0; + this->glview->updateGL(); } void MainWindow::viewAngleLeft() { - screen->object_rot_x = 0; - screen->object_rot_y = 0; - screen->object_rot_z = 90; - screen->updateGL(); + this->glview->object_rot_x = 0; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 90; + this->glview->updateGL(); } void MainWindow::viewAngleRight() { - screen->object_rot_x = 0; - screen->object_rot_y = 0; - screen->object_rot_z = 270; - screen->updateGL(); + this->glview->object_rot_x = 0; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 270; + this->glview->updateGL(); } void MainWindow::viewAngleFront() { - screen->object_rot_x = 0; - screen->object_rot_y = 0; - screen->object_rot_z = 0; - screen->updateGL(); + this->glview->object_rot_x = 0; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 0; + this->glview->updateGL(); } void MainWindow::viewAngleBack() { - screen->object_rot_x = 0; - screen->object_rot_y = 0; - screen->object_rot_z = 180; - screen->updateGL(); + this->glview->object_rot_x = 0; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 180; + this->glview->updateGL(); } void MainWindow::viewAngleDiagonal() { - screen->object_rot_x = 35; - screen->object_rot_y = 0; - screen->object_rot_z = 25; - screen->updateGL(); + this->glview->object_rot_x = 35; + this->glview->object_rot_y = 0; + this->glview->object_rot_z = 25; + this->glview->updateGL(); } void MainWindow::viewCenter() { - screen->object_trans_x = 0; - screen->object_trans_y = 0; - screen->object_trans_z = 0; - screen->updateGL(); + this->glview->object_trans_x = 0; + this->glview->object_trans_y = 0; + this->glview->object_trans_z = 0; + this->glview->updateGL(); } void MainWindow::viewPerspective() @@ -1622,8 +1675,8 @@ void MainWindow::viewPerspective() settings.setValue("view/orthogonalProjection",false); viewActionPerspective->setChecked(true); viewActionOrthogonal->setChecked(false); - screen->setOrthoMode(false); - screen->updateGL(); + this->glview->setOrthoMode(false); + this->glview->updateGL(); } void MainWindow::viewOrthogonal() @@ -1632,8 +1685,8 @@ void MainWindow::viewOrthogonal() settings.setValue("view/orthogonalProjection",true); viewActionPerspective->setChecked(false); viewActionOrthogonal->setChecked(true); - screen->setOrthoMode(true); - screen->updateGL(); + this->glview->setOrthoMode(true); + this->glview->updateGL(); } void MainWindow::hideConsole() diff --git a/src/module.cc b/src/module.cc index 54b151c..efe98e4 100644 --- a/src/module.cc +++ b/src/module.cc @@ -79,7 +79,7 @@ QString ModuleInstantiation::dump(QString indent) const text += QString(", "); if (!argnames[i].isEmpty()) text += argnames[i] + QString(" = "); - text += argexpr[i]->dump(); + text += QString::fromStdString(argexpr[i]->toString()); } if (children.size() == 0) { text += QString(");\n"); @@ -167,7 +167,7 @@ QString Module::dump(QString indent, QString name) const text += QString(", "); text += argnames[i]; if (argexpr[i]) - text += QString(" = ") + argexpr[i]->dump(); + text += QString(" = ") + QString::fromStdString(argexpr[i]->toString()); } text += QString(") {\n"); tab = "\t"; @@ -187,7 +187,7 @@ QString Module::dump(QString indent, QString name) const } } for (int i = 0; i < assignments_var.size(); i++) { - text += QString("%1%2 = %3;\n").arg(indent + tab, assignments_var[i], assignments_expr[i]->dump()); + text += QString("%1%2 = %3;\n").arg(indent + tab, assignments_var[i], QString::fromStdString(assignments_expr[i]->toString())); } for (int i = 0; i < children.size(); i++) { text += children[i]->dump(indent + tab); diff --git a/src/myqhash.h b/src/myqhash.h new file mode 100644 index 0000000..9156ec2 --- /dev/null +++ b/src/myqhash.h @@ -0,0 +1,16 @@ +#ifndef OPENSCAD_QHASH_H_ +#define OPENSCAD_QHASH_H_ + +/*! + Defines a qHash for std::string. + + Note that this header must be included before Qt headers (at least + before qhash.h) to take effect. + */ + +#include <qglobal.h> +#include <string> +extern uint qHash(const std::string &); +#include <QHash> + +#endif diff --git a/src/namedcolors.cpp b/src/namedcolors.cpp new file mode 100644 index 0000000..3150a40 --- /dev/null +++ b/src/namedcolors.cpp @@ -0,0 +1,155 @@ +#define rgb(r,g,b) (0xff000000 | (r << 16) | (g << 8) | b) + +static const struct RGBData { + const char *name; + uint value; +} named_colors[] = { + { "aliceblue", rgb(240, 248, 255) }, + { "antiquewhite", rgb(250, 235, 215) }, + { "aqua", rgb( 0, 255, 255) }, + { "aquamarine", rgb(127, 255, 212) }, + { "azure", rgb(240, 255, 255) }, + { "beige", rgb(245, 245, 220) }, + { "bisque", rgb(255, 228, 196) }, + { "black", rgb( 0, 0, 0) }, + { "blanchedalmond", rgb(255, 235, 205) }, + { "blue", rgb( 0, 0, 255) }, + { "blueviolet", rgb(138, 43, 226) }, + { "brown", rgb(165, 42, 42) }, + { "burlywood", rgb(222, 184, 135) }, + { "cadetblue", rgb( 95, 158, 160) }, + { "chartreuse", rgb(127, 255, 0) }, + { "chocolate", rgb(210, 105, 30) }, + { "coral", rgb(255, 127, 80) }, + { "cornflowerblue", rgb(100, 149, 237) }, + { "cornsilk", rgb(255, 248, 220) }, + { "crimson", rgb(220, 20, 60) }, + { "cyan", rgb( 0, 255, 255) }, + { "darkblue", rgb( 0, 0, 139) }, + { "darkcyan", rgb( 0, 139, 139) }, + { "darkgoldenrod", rgb(184, 134, 11) }, + { "darkgray", rgb(169, 169, 169) }, + { "darkgreen", rgb( 0, 100, 0) }, + { "darkgrey", rgb(169, 169, 169) }, + { "darkkhaki", rgb(189, 183, 107) }, + { "darkmagenta", rgb(139, 0, 139) }, + { "darkolivegreen", rgb( 85, 107, 47) }, + { "darkorange", rgb(255, 140, 0) }, + { "darkorchid", rgb(153, 50, 204) }, + { "darkred", rgb(139, 0, 0) }, + { "darksalmon", rgb(233, 150, 122) }, + { "darkseagreen", rgb(143, 188, 143) }, + { "darkslateblue", rgb( 72, 61, 139) }, + { "darkslategray", rgb( 47, 79, 79) }, + { "darkslategrey", rgb( 47, 79, 79) }, + { "darkturquoise", rgb( 0, 206, 209) }, + { "darkviolet", rgb(148, 0, 211) }, + { "deeppink", rgb(255, 20, 147) }, + { "deepskyblue", rgb( 0, 191, 255) }, + { "dimgray", rgb(105, 105, 105) }, + { "dimgrey", rgb(105, 105, 105) }, + { "dodgerblue", rgb( 30, 144, 255) }, + { "firebrick", rgb(178, 34, 34) }, + { "floralwhite", rgb(255, 250, 240) }, + { "forestgreen", rgb( 34, 139, 34) }, + { "fuchsia", rgb(255, 0, 255) }, + { "gainsboro", rgb(220, 220, 220) }, + { "ghostwhite", rgb(248, 248, 255) }, + { "gold", rgb(255, 215, 0) }, + { "goldenrod", rgb(218, 165, 32) }, + { "gray", rgb(128, 128, 128) }, + { "green", rgb( 0, 128, 0) }, + { "greenyellow", rgb(173, 255, 47) }, + { "grey", rgb(128, 128, 128) }, + { "honeydew", rgb(240, 255, 240) }, + { "hotpink", rgb(255, 105, 180) }, + { "indianred", rgb(205, 92, 92) }, + { "indigo", rgb( 75, 0, 130) }, + { "ivory", rgb(255, 255, 240) }, + { "khaki", rgb(240, 230, 140) }, + { "lavender", rgb(230, 230, 250) }, + { "lavenderblush", rgb(255, 240, 245) }, + { "lawngreen", rgb(124, 252, 0) }, + { "lemonchiffon", rgb(255, 250, 205) }, + { "lightblue", rgb(173, 216, 230) }, + { "lightcoral", rgb(240, 128, 128) }, + { "lightcyan", rgb(224, 255, 255) }, + { "lightgoldenrodyellow", rgb(250, 250, 210) }, + { "lightgray", rgb(211, 211, 211) }, + { "lightgreen", rgb(144, 238, 144) }, + { "lightgrey", rgb(211, 211, 211) }, + { "lightpink", rgb(255, 182, 193) }, + { "lightsalmon", rgb(255, 160, 122) }, + { "lightseagreen", rgb( 32, 178, 170) }, + { "lightskyblue", rgb(135, 206, 250) }, + { "lightslategray", rgb(119, 136, 153) }, + { "lightslategrey", rgb(119, 136, 153) }, + { "lightsteelblue", rgb(176, 196, 222) }, + { "lightyellow", rgb(255, 255, 224) }, + { "lime", rgb( 0, 255, 0) }, + { "limegreen", rgb( 50, 205, 50) }, + { "linen", rgb(250, 240, 230) }, + { "magenta", rgb(255, 0, 255) }, + { "maroon", rgb(128, 0, 0) }, + { "mediumaquamarine", rgb(102, 205, 170) }, + { "mediumblue", rgb( 0, 0, 205) }, + { "mediumorchid", rgb(186, 85, 211) }, + { "mediumpurple", rgb(147, 112, 219) }, + { "mediumseagreen", rgb( 60, 179, 113) }, + { "mediumslateblue", rgb(123, 104, 238) }, + { "mediumspringgreen", rgb( 0, 250, 154) }, + { "mediumturquoise", rgb( 72, 209, 204) }, + { "mediumvioletred", rgb(199, 21, 133) }, + { "midnightblue", rgb( 25, 25, 112) }, + { "mintcream", rgb(245, 255, 250) }, + { "mistyrose", rgb(255, 228, 225) }, + { "moccasin", rgb(255, 228, 181) }, + { "navajowhite", rgb(255, 222, 173) }, + { "navy", rgb( 0, 0, 128) }, + { "oldlace", rgb(253, 245, 230) }, + { "olive", rgb(128, 128, 0) }, + { "olivedrab", rgb(107, 142, 35) }, + { "orange", rgb(255, 165, 0) }, + { "orangered", rgb(255, 69, 0) }, + { "orchid", rgb(218, 112, 214) }, + { "palegoldenrod", rgb(238, 232, 170) }, + { "palegreen", rgb(152, 251, 152) }, + { "paleturquoise", rgb(175, 238, 238) }, + { "palevioletred", rgb(219, 112, 147) }, + { "papayawhip", rgb(255, 239, 213) }, + { "peachpuff", rgb(255, 218, 185) }, + { "peru", rgb(205, 133, 63) }, + { "pink", rgb(255, 192, 203) }, + { "plum", rgb(221, 160, 221) }, + { "powderblue", rgb(176, 224, 230) }, + { "purple", rgb(128, 0, 128) }, + { "red", rgb(255, 0, 0) }, + { "rosybrown", rgb(188, 143, 143) }, + { "royalblue", rgb( 65, 105, 225) }, + { "saddlebrown", rgb(139, 69, 19) }, + { "salmon", rgb(250, 128, 114) }, + { "sandybrown", rgb(244, 164, 96) }, + { "seagreen", rgb( 46, 139, 87) }, + { "seashell", rgb(255, 245, 238) }, + { "sienna", rgb(160, 82, 45) }, + { "silver", rgb(192, 192, 192) }, + { "skyblue", rgb(135, 206, 235) }, + { "slateblue", rgb(106, 90, 205) }, + { "slategray", rgb(112, 128, 144) }, + { "slategrey", rgb(112, 128, 144) }, + { "snow", rgb(255, 250, 250) }, + { "springgreen", rgb( 0, 255, 127) }, + { "steelblue", rgb( 70, 130, 180) }, + { "tan", rgb(210, 180, 140) }, + { "teal", rgb( 0, 128, 128) }, + { "thistle", rgb(216, 191, 216) }, + { "tomato", rgb(255, 99, 71) }, + { "transparent", 0 }, + { "turquoise", rgb( 64, 224, 208) }, + { "violet", rgb(238, 130, 238) }, + { "wheat", rgb(245, 222, 179) }, + { "white", rgb(255, 255, 255) }, + { "whitesmoke", rgb(245, 245, 245) }, + { "yellow", rgb(255, 255, 0) }, + { "yellowgreen", rgb(154, 205, 50) } +}; diff --git a/src/nef2dxf.cc b/src/nef2dxf.cc index 320d6b8..b22e605 100644 --- a/src/nef2dxf.cc +++ b/src/nef2dxf.cc @@ -32,6 +32,7 @@ DxfData::DxfData(const struct CGAL_Nef_polyhedron &N) { + assert(N.dim == 2); Grid2d<int> grid(GRID_COARSE); typedef CGAL_Nef_polyhedron2::Explorer Explorer; diff --git a/src/node.cc b/src/node.cc index 87b3c2b..3bc8d7b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -30,9 +30,13 @@ #include "csgterm.h" #include "progress.h" #include "polyset.h" +#include "visitor.h" +#include "nodedumper.h" + #include <QRegExp> +#include <sstream> -int AbstractNode::idx_counter; +size_t AbstractNode::idx_counter; AbstractNode::AbstractNode(const ModuleInstantiation *mi) { @@ -46,129 +50,39 @@ AbstractNode::~AbstractNode() delete v; } -QString AbstractNode::mk_cache_id() const -{ - QString cache_id = dump(""); - cache_id.remove(QRegExp("[a-zA-Z_][a-zA-Z_0-9]*:")); - cache_id.remove(' '); - cache_id.remove('\t'); - cache_id.remove('\n'); - return cache_id; -} - -#ifdef ENABLE_CGAL - -AbstractNode::cgal_nef_cache_entry::cgal_nef_cache_entry(const CGAL_Nef_polyhedron &N) : - N(N), msg(print_messages_stack.last()) { }; - -QCache<QString, AbstractNode::cgal_nef_cache_entry> AbstractNode::cgal_nef_cache(100000); - -static CGAL_Nef_polyhedron render_cgal_nef_polyhedron_backend(const AbstractNode *that, bool intersect) -{ - QString cache_id = that->mk_cache_id(); - if (that->cgal_nef_cache.contains(cache_id)) { - that->progress_report(); - PRINT(that->cgal_nef_cache[cache_id]->msg); - return that->cgal_nef_cache[cache_id]->N; - } - - print_messages_push(); - - bool first = true; - CGAL_Nef_polyhedron N; - foreach (AbstractNode *v, that->children) { - if (v->modinst->tag_background) - continue; - if (first) { - N = v->render_cgal_nef_polyhedron(); - if (N.dim != 0) - first = false; - } else if (N.dim == 2) { - if (intersect) - N.p2 *= v->render_cgal_nef_polyhedron().p2; - else - N.p2 += v->render_cgal_nef_polyhedron().p2; - } else { - if (intersect) - N.p3 *= v->render_cgal_nef_polyhedron().p3; - else - N.p3 += v->render_cgal_nef_polyhedron().p3; - } - v->progress_report(); - } - - that->cgal_nef_cache.insert(cache_id, new AbstractNode::cgal_nef_cache_entry(N), N.weight()); - that->progress_report(); - print_messages_pop(); - - return N; -} - -CGAL_Nef_polyhedron AbstractNode::render_cgal_nef_polyhedron() const +Response AbstractNode::accept(class State &state, Visitor &visitor) const { - return render_cgal_nef_polyhedron_backend(this, false); + return visitor.visit(state, *this); } -CGAL_Nef_polyhedron AbstractIntersectionNode::render_cgal_nef_polyhedron() const +Response AbstractIntersectionNode::accept(class State &state, Visitor &visitor) const { - return render_cgal_nef_polyhedron_backend(this, true); + return visitor.visit(state, *this); } -#endif /* ENABLE_CGAL */ - -static CSGTerm *render_csg_term_backend(const AbstractNode *that, bool intersect, double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) +Response AbstractPolyNode::accept(class State &state, Visitor &visitor) const { - CSGTerm *t1 = NULL; - foreach(AbstractNode *v, that->children) { - CSGTerm *t2 = v->render_csg_term(m, highlights, background); - if (t2 && !t1) { - t1 = t2; - } else if (t2 && t1) { - if (intersect) - t1 = new CSGTerm(CSGTerm::TYPE_INTERSECTION, t1, t2); - else - t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); - } - } - if (t1 && that->modinst->tag_highlight && highlights) - highlights->append(t1->link()); - if (t1 && that->modinst->tag_background && background) { - background->append(t1); - return NULL; - } - return t1; + return visitor.visit(state, *this); } -CSGTerm *AbstractNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +std::string AbstractNode::toString() const { - return render_csg_term_backend(this, false, m, highlights, background); + return this->name() + "()"; } -CSGTerm *AbstractIntersectionNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const +std::string AbstractNode::name() const { - return render_csg_term_backend(this, true, m, highlights, background); + return "group"; } -QString AbstractNode::dump(QString indent) const +std::string AbstractIntersectionNode::toString() const { - if (dump_cache.isEmpty()) { - QString text = indent + QString("n%1: group() {\n").arg(idx); - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; - } - return dump_cache; + return this->name() + "()"; } -QString AbstractIntersectionNode::dump(QString indent) const +std::string AbstractIntersectionNode::name() const { - if (dump_cache.isEmpty()) { - QString text = indent + QString("n%1: intersection() {\n").arg(idx); - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; - } - return dump_cache; + return "intersection_for"; } void AbstractNode::progress_prepare() @@ -183,52 +97,8 @@ void AbstractNode::progress_report() const progress_update(this, this->progress_mark); } -#ifdef ENABLE_CGAL - -CGAL_Nef_polyhedron AbstractPolyNode::render_cgal_nef_polyhedron() const +std::ostream &operator<<(std::ostream &stream, const AbstractNode &node) { - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) { - progress_report(); - PRINT(cgal_nef_cache[cache_id]->msg); - return cgal_nef_cache[cache_id]->N; - } - - print_messages_push(); - - PolySet *ps = render_polyset(RENDER_CGAL); - try { - CGAL_Nef_polyhedron N = ps->render_cgal_nef_polyhedron(); - cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight()); - print_messages_pop(); - progress_report(); - - ps->unlink(); - return N; - } - catch (...) { // Don't leak the PolySet on ProgressCancelException - ps->unlink(); - throw; - } + stream << node.toString(); + return stream; } - -#endif /* ENABLE_CGAL */ - -CSGTerm *AbstractPolyNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - PolySet *ps = render_polyset(RENDER_OPENCSG); - return render_csg_term_from_ps(m, highlights, background, ps, modinst, idx); -} - -CSGTerm *AbstractPolyNode::render_csg_term_from_ps(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background, PolySet *ps, const ModuleInstantiation *modinst, int idx) -{ - CSGTerm *t = new CSGTerm(ps, m, QString("n%1").arg(idx)); - if (modinst->tag_highlight && highlights) - highlights->append(t->link()); - if (modinst->tag_background && background) { - background->append(t); - return NULL; - } - return t; -} - @@ -4,9 +4,7 @@ #include <QCache> #include <QVector> -#ifdef ENABLE_CGAL -#include "cgal.h" -#endif +#include "traverser.h" extern int progress_report_count; extern void (*progress_report_f)(const class AbstractNode*, void*, int); @@ -15,64 +13,79 @@ extern void *progress_report_vp; void progress_report_prep(AbstractNode *root, void (*f)(const class AbstractNode *node, void *vp, int mark), void *vp); void progress_report_fin(); +/*! + + The node tree is the result of evaluation of a module instantiation + tree. Both the module tree and the node tree are regenerated from + scratch for each compile. + + */ class AbstractNode { - static int idx_counter; // Node instantiation index + // FIXME: the idx_counter/idx is mostly (only?) for debugging. + // We can hash on pointer value or smth. else. + // -> remove and + // use smth. else to display node identifier in CSG tree output? + static size_t idx_counter; // Node instantiation index public: + AbstractNode(const class ModuleInstantiation *mi); + virtual ~AbstractNode(); + virtual Response accept(class State &state, class Visitor &visitor) const; + virtual std::string toString() const; + /*! The 'OpenSCAD name' of this node, defaults to classname, but can be + overloaded to provide specialization for e.g. CSG nodes, primitive nodes etc. + Used for human-readable output. */ + virtual std::string name() const; + + // FIXME: Make return value a reference + const std::list<AbstractNode*> getChildren() const { + return this->children.toList().toStdList(); + } + size_t index() const { return this->idx; } + static void resetIndexCounter() { idx_counter = 1; } + // FIXME: Rewrite to STL container? + // FIXME: Make protected QVector<AbstractNode*> children; - const class ModuleInstantiation *modinst; + const ModuleInstantiation *modinst; + // progress_mark is a running number used for progress indication + // FIXME: Make all progress handling external, put it in the traverser class? int progress_mark; void progress_prepare(); void progress_report() const; - int idx; - QString dump_cache; - - AbstractNode(const ModuleInstantiation *mi); - virtual ~AbstractNode(); - virtual QString mk_cache_id() const; -#ifdef ENABLE_CGAL - struct cgal_nef_cache_entry { - CGAL_Nef_polyhedron N; - QString msg; - cgal_nef_cache_entry(const CGAL_Nef_polyhedron &N); - }; - static QCache<QString, cgal_nef_cache_entry> cgal_nef_cache; - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; - class CSGTerm *render_csg_term_from_nef(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background, const char *statement, int convexity) const; -#endif - virtual class CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; + int idx; // Node index (unique per tree) }; class AbstractIntersectionNode : public AbstractNode { public: AbstractIntersectionNode(const ModuleInstantiation *mi) : AbstractNode(mi) { }; -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - virtual CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; + virtual ~AbstractIntersectionNode() { }; + virtual Response accept(class State &state, class Visitor &visitor) const; + virtual std::string toString() const; + virtual std::string name() const; }; class AbstractPolyNode : public AbstractNode { public: + AbstractPolyNode(const ModuleInstantiation *mi) : AbstractNode(mi) { }; + virtual ~AbstractPolyNode() { }; + virtual Response accept(class State &state, class Visitor &visitor) const; + enum render_mode_e { RENDER_CGAL, RENDER_OPENCSG }; - AbstractPolyNode(const ModuleInstantiation *mi) : AbstractNode(mi) { }; - virtual class PolySet *render_polyset(render_mode_e mode) const = 0; -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - virtual CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - static CSGTerm *render_csg_term_from_ps(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background, PolySet *ps, const ModuleInstantiation *modinst, int idx); + /*! Should return a PolySet of the given geometry. It's normal to return an + empty PolySet if smth. is wrong, but don't return NULL unless we change the calling + strategy for this method. */ + virtual class PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *evaluator) const = 0; }; +std::ostream &operator<<(std::ostream &stream, const AbstractNode &node); + #endif diff --git a/src/nodecache.h b/src/nodecache.h new file mode 100644 index 0000000..cc3355e --- /dev/null +++ b/src/nodecache.h @@ -0,0 +1,46 @@ +#ifndef NODECACHE_H_ +#define NODECACHE_H_ + +#include <vector> +#include <string> +#include "node.h" + +/*! + Caches string values per node based on the node.index(). + The node index guaranteed to be unique per node tree since the index is reset + every time a new tree is generated. +*/ +class NodeCache +{ +public: + NodeCache() { } + virtual ~NodeCache() { } + + bool contains(const AbstractNode &node) const { + return !(*this)[node].empty(); + } + + const std::string & operator[](const AbstractNode &node) const { + if (this->cache.size() > node.index()) return this->cache[node.index()]; + else return this->nullvalue; + } + + void insert(const class AbstractNode &node, const std::string & value) { + if (this->cache.size() <= node.index()) this->cache.resize(node.index() + 1); + this->cache[node.index()] = value; + } + + void remove(const class AbstractNode &node) { + if (this->cache.size() > node.index()) this->cache[node.index()] = std::string(); + } + + void clear() { + this->cache.clear(); + } + +private: + std::vector<std::string> cache; + std::string nullvalue; +}; + +#endif diff --git a/src/nodedumper.cc b/src/nodedumper.cc new file mode 100644 index 0000000..d75c703 --- /dev/null +++ b/src/nodedumper.cc @@ -0,0 +1,103 @@ +#include "nodedumper.h" +#include <string> +#include <map> +#include <list> +#include "visitor.h" +#include "state.h" +#include "nodecache.h" + +#include <sstream> +#include <iostream> +#include <assert.h> + +/*! + \class NodeDumper + + A visitor responsible for creating a text dump of a node tree. Also + contains a cache for fast retrieval of the text representation of + any node or subtree. +*/ + +bool NodeDumper::isCached(const AbstractNode &node) const +{ + return !this->cache[node].empty(); +} + +/*! + Indent or deindent. Must be called before we output any children. +*/ +void NodeDumper::handleIndent(const State &state) +{ + if (state.isPrefix()) { + this->currindent += "\t"; + } + else if (state.isPostfix()) { + this->currindent.erase((this->currindent.length() >= 1) ? + this->currindent.length() - 1 : 0); + } +} + +/*! + Dumps the block of children contained in this->visitedchildren, + including braces and indentation. + All children are assumed to be cached already. + */ +string NodeDumper::dumpChildren(const AbstractNode &node) +{ + std::stringstream dump; + if (!this->visitedchildren[node.index()].empty()) { + dump << " {\n"; + + for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); + iter != this->visitedchildren[node.index()].end(); + iter++) { + assert(isCached(**iter)); + dump << this->cache[**iter] << "\n"; + } + + dump << this->currindent << "}"; + } + else { + dump << ";"; + } + return dump.str(); +} + +/*! + Called for each node in the tree. + Will abort traversal if we're cached +*/ +Response NodeDumper::visit(State &state, const AbstractNode &node) +{ + if (isCached(node)) return PruneTraversal; + + handleIndent(state); + if (state.isPostfix()) { + std::stringstream dump; + dump << this->currindent; + if (this->idprefix) dump << "n" << node.index() << ":"; + dump << node; + dump << dumpChildren(node); + this->cache.insert(node, dump.str()); + } + + handleVisitedChildren(state, node); + return ContinueTraversal; +} + +/*! + Adds this given node to its parent's child list. + Should be called for all nodes, including leaf nodes. +*/ +void NodeDumper::handleVisitedChildren(const State &state, const AbstractNode &node) +{ + if (state.isPostfix()) { + this->visitedchildren.erase(node.index()); + if (!state.parent()) { + this->root = &node; + } + else { + this->visitedchildren[state.parent()->index()].push_back(&node); + } + } +} diff --git a/src/nodedumper.h b/src/nodedumper.h new file mode 100644 index 0000000..efaf4fa --- /dev/null +++ b/src/nodedumper.h @@ -0,0 +1,40 @@ +#ifndef NODEDUMPER_H_ +#define NODEDUMPER_H_ + +#include <string> +#include <map> +#include <list> +#include "visitor.h" +#include "nodecache.h" + +using std::string; +using std::map; +using std::list; + +class NodeDumper : public Visitor +{ +public: + /*! If idPrefix is true, we will output "n<id>:" in front of each node, + which is useful for debugging. */ + NodeDumper(NodeCache &cache, bool idPrefix = false) : + cache(cache), idprefix(idPrefix), root(NULL) { } + virtual ~NodeDumper() {} + + virtual Response visit(State &state, const AbstractNode &node); + +private: + void handleVisitedChildren(const State &state, const AbstractNode &node); + bool isCached(const AbstractNode &node) const; + void handleIndent(const State &state); + string dumpChildren(const AbstractNode &node); + + NodeCache &cache; + bool idprefix; + + string currindent; + const AbstractNode *root; + typedef list<const AbstractNode *> ChildList; + map<int, ChildList> visitedchildren; +}; + +#endif diff --git a/src/opencsgrenderer.cc b/src/opencsgrenderer.cc index 768176c..2c9c3d0 100644 --- a/src/opencsgrenderer.cc +++ b/src/opencsgrenderer.cc @@ -24,6 +24,7 @@ * */ +#include <GL/glew.h> #include "opencsgrenderer.h" #include "polyset.h" #include "csgterm.h" @@ -53,11 +54,6 @@ OpenCSGRenderer::OpenCSGRenderer(CSGChain *root_chain, CSGChain *highlights_chai void OpenCSGRenderer::draw(bool showfaces, bool showedges) const { - static int glew_initialized = 0; - if (!glew_initialized) { - glew_initialized = 1; - glewInit(); - } if (this->root_chain) { GLint *shaderinfo = this->shaderinfo; if (!shaderinfo[0]) shaderinfo = NULL; @@ -76,18 +72,14 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, { std::vector<OpenCSG::Primitive*> primitives; int j = 0; - for (int i = 0;; i++) - { + for (int i = 0;; i++) { bool last = i == chain->polysets.size(); - - if (last || chain->types[i] == CSGTerm::TYPE_UNION) - { + if (last || chain->types[i] == CSGTerm::TYPE_UNION) { if (j+1 != i) { - OpenCSG::render(primitives); + OpenCSG::render(primitives); glDepthFunc(GL_EQUAL); } - if (shaderinfo) - glUseProgram(shaderinfo[0]); + if (shaderinfo) glUseProgram(shaderinfo[0]); for (; j < i; j++) { double *m = chain->matrices[j]; glPushMatrix(); @@ -112,8 +104,7 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, } glPopMatrix(); } - if (shaderinfo) - glUseProgram(0); + if (shaderinfo) glUseProgram(0); for (unsigned int k = 0; k < primitives.size(); k++) { delete primitives[k]; } @@ -121,18 +112,15 @@ void OpenCSGRenderer::renderCSGChain(CSGChain *chain, GLint *shaderinfo, primitives.clear(); } - if (last) - break; + if (last) break; OpenCSGPrim *prim = new OpenCSGPrim(chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? OpenCSG::Subtraction : OpenCSG::Intersection, chain->polysets[i]->convexity); prim->p = chain->polysets[i]; prim->m = chain->matrices[i]; prim->csgmode = chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; - if (highlight) - prim->csgmode += 20; - else if (background) - prim->csgmode += 10; + if (highlight) prim->csgmode += 20; + else if (background) prim->csgmode += 10; primitives.push_back(prim); } } diff --git a/src/openscad.cc b/src/openscad.cc index bf22246..f3aee76 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -32,6 +32,10 @@ #include "value.h" #include "export.h" #include "builtin.h" +#include "nodedumper.h" +#include "CGALEvaluator.h" +#include "PolySetCGALEvaluator.h" +#include "printutils.h" #include <string> #include <vector> @@ -46,7 +50,9 @@ #include <QDir> #include <QSet> #include <QSettings> +#include <QTextStream> #include <boost/program_options.hpp> + #ifdef Q_WS_MAC #include "EventFilter.h" #include "AppleEvents.h" @@ -249,6 +255,15 @@ int main(int argc, char **argv) librarydir = libdir.path(); } + // Initialize global visitors + NodeCache nodecache; + NodeDumper dumper(nodecache); + Tree tree; + // FIXME: enforce some maximum cache size (old version had 100K vertices as limit) + QHash<std::string, CGAL_Nef_polyhedron> cache; + CGALEvaluator cgalevaluator(cache, tree); + PolySetCGALEvaluator psevaluator(cgalevaluator); + if (stl_output_file || off_output_file || dxf_output_file) { if (!filename) @@ -265,9 +280,9 @@ int main(int argc, char **argv) Value zero3; zero3.type = Value::VECTOR; - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); root_ctx.set_variable("$vpt", zero3); root_ctx.set_variable("$vpr", zero3); @@ -299,8 +314,8 @@ int main(int argc, char **argv) AbstractNode::resetIndexCounter(); root_node = root_module->evaluate(&root_ctx, &root_inst); - CGAL_Nef_polyhedron *root_N; - root_N = new CGAL_Nef_polyhedron(root_node->render_cgal_nef_polyhedron()); + tree.setRoot(root_node); + CGAL_Nef_polyhedron root_N = cgalevaluator.evaluateCGALMesh(*tree.root()); QDir::setCurrent(original_path.absolutePath()); @@ -318,17 +333,43 @@ int main(int argc, char **argv) fclose(fp); } - if (stl_output_file) - export_stl(root_N, stl_output_file, NULL); + if (stl_output_file) { + QFile file(stl_output_file); + if (!file.open(QIODevice::ReadWrite)) { + PRINTA("Can't open file \"%1\" for export", stl_output_file); + } + else { + QTextStream fstream(&file); + export_stl(&root_N, fstream, NULL); + file.close(); + } + } - if (off_output_file) - export_off(root_N, off_output_file, NULL); + if (off_output_file) { + QFile file(stl_output_file); + if (!file.open(QIODevice::ReadWrite)) { + PRINTA("Can't open file \"%1\" for export", stl_output_file); + } + else { + QTextStream fstream(&file); + export_off(&root_N, fstream, NULL); + file.close(); + } + } - if (dxf_output_file) - export_dxf(root_N, dxf_output_file, NULL); + if (dxf_output_file) { + QFile file(stl_output_file); + if (!file.open(QIODevice::ReadWrite)) { + PRINTA("Can't open file \"%1\" for export", stl_output_file); + } + else { + QTextStream fstream(&file); + export_dxf(&root_N, fstream, NULL); + file.close(); + } + } delete root_node; - delete root_N; #else fprintf(stderr, "OpenSCAD has been compiled without CGAL support!\n"); exit(1); diff --git a/src/parser.y b/src/parser.y index aad5ba0..2bd425f 100644 --- a/src/parser.y +++ b/src/parser.y @@ -334,7 +334,7 @@ expr: TOK_STRING { $$ = new Expression(); $$->type = "C"; - $$->const_value = new Value(QString($1)); + $$->const_value = new Value(std::string($1)); free($1); } | TOK_NUMBER { diff --git a/src/polyset.cc b/src/polyset.cc index cccdaad..eda6304 100644 --- a/src/polyset.cc +++ b/src/polyset.cc @@ -26,7 +26,8 @@ #include "polyset.h" #include "printutils.h" -#include "Preferences.h" +// FIXME: Reenable/rewrite - don't be dependant on GUI +// #include "Preferences.h" #ifdef ENABLE_CGAL #include <CGAL/assertions_behaviour.h> #include <CGAL/exceptions.h> @@ -34,15 +35,6 @@ #include <Eigen/Core> #include <Eigen/LU> -QCache<QString,PolySet::ps_cache_entry> PolySet::ps_cache(100); - -PolySet::ps_cache_entry::ps_cache_entry(PolySet *ps) : - ps(ps), msg(print_messages_stack.last()) { } - -PolySet::ps_cache_entry::~ps_cache_entry() { - ps->unlink(); -} - PolySet::PolySet() : grid(GRID_FINE) { is2d = false; @@ -145,7 +137,9 @@ void PolySet::render_surface(colormode_e colormode, csgmode_e csgmode, double *m bool mirrored = m3f.determinant() < 0; if (colormode == COLORMODE_MATERIAL) { - const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_FRONT_COLOR); +// FIXME: Reenable/rewrite - don't be dependant on GUI +// const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_FRONT_COLOR); + const QColor &col = QColor(0xf9, 0xd7, 0x2c); glColor3f(col.redF(), col.greenF(), col.blueF()); #ifdef ENABLE_OPENCSG if (shaderinfo) { @@ -155,7 +149,9 @@ void PolySet::render_surface(colormode_e colormode, csgmode_e csgmode, double *m #endif /* ENABLE_OPENCSG */ } if (colormode == COLORMODE_CUTOUT) { - const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_BACK_COLOR); +// FIXME: Reenable/rewrite - don't be dependant on GUI +// const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_BACK_COLOR); + const QColor &col = QColor(0x9d, 0xcb, 0x51); glColor3f(col.redF(), col.greenF(), col.blueF()); #ifdef ENABLE_OPENCSG if (shaderinfo) { @@ -322,357 +318,3 @@ void PolySet::render_edges(colormode_e colormode, csgmode_e csgmode) const } } } - -#ifdef ENABLE_CGAL - -#undef GEN_SURFACE_DEBUG - -class CGAL_Build_PolySet : public CGAL::Modifier_base<CGAL_HDS> -{ -public: - typedef CGAL_HDS::Vertex::Point Point; - - const PolySet *ps; - CGAL_Build_PolySet(const PolySet *ps) : ps(ps) { } - - void operator()(CGAL_HDS& hds) - { - CGAL_Polybuilder B(hds, true); - - QList<PolySet::Point> vertices; - Grid3d<int> vertices_idx(GRID_FINE); - - for (int i = 0; i < ps->polygons.size(); i++) { - const PolySet::Polygon *poly = &ps->polygons[i]; - for (int j = 0; j < poly->size(); j++) { - const PolySet::Point *p = &poly->at(j); - if (!vertices_idx.has(p->x, p->y, p->z)) { - vertices_idx.data(p->x, p->y, p->z) = vertices.size(); - vertices.append(*p); - } - } - } - - B.begin_surface(vertices.size(), ps->polygons.size()); -#ifdef GEN_SURFACE_DEBUG - printf("=== CGAL Surface ===\n"); -#endif - - for (int i = 0; i < vertices.size(); i++) { - const PolySet::Point *p = &vertices[i]; - B.add_vertex(Point(p->x, p->y, p->z)); -#ifdef GEN_SURFACE_DEBUG - printf("%d: %f %f %f\n", i, p->x, p->y, p->z); -#endif - } - - for (int i = 0; i < ps->polygons.size(); i++) { - const PolySet::Polygon *poly = &ps->polygons[i]; - QHash<int,int> fc; - bool facet_is_degenerated = false; - for (int j = 0; j < poly->size(); j++) { - const PolySet::Point *p = &poly->at(j); - int v = vertices_idx.data(p->x, p->y, p->z); - if (fc[v]++ > 0) - facet_is_degenerated = true; - } - - if (!facet_is_degenerated) - B.begin_facet(); -#ifdef GEN_SURFACE_DEBUG - printf("F:"); -#endif - for (int j = 0; j < poly->size(); j++) { - const PolySet::Point *p = &poly->at(j); -#ifdef GEN_SURFACE_DEBUG - printf(" %d (%f,%f,%f)", vertices_idx.data(p->x, p->y, p->z), p->x, p->y, p->z); -#endif - if (!facet_is_degenerated) - B.add_vertex_to_facet(vertices_idx.data(p->x, p->y, p->z)); - } -#ifdef GEN_SURFACE_DEBUG - if (facet_is_degenerated) - printf(" (degenerated)"); - printf("\n"); -#endif - if (!facet_is_degenerated) - B.end_facet(); - } - -#ifdef GEN_SURFACE_DEBUG - printf("====================\n"); -#endif - B.end_surface(); - - #undef PointKey - } -}; - -CGAL_Nef_polyhedron PolySet::render_cgal_nef_polyhedron() const -{ - if (this->is2d) - { -#if 0 - // This version of the code causes problems in some cases. - // Example testcase: import_dxf("testdata/polygon8.dxf"); - // - typedef std::list<CGAL_Nef_polyhedron2::Point> point_list_t; - typedef point_list_t::iterator point_list_it; - std::list< point_list_t > pdata_point_lists; - std::list < std::pair < point_list_it, point_list_it > > pdata; - Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); - - for (int i = 0; i < this->polygons.size(); i++) { - pdata_point_lists.push_back(point_list_t()); - for (int j = 0; j < this->polygons[i].size(); j++) { - double x = this->polygons[i][j].x; - double y = this->polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - pdata_point_lists.back().push_back(p); - } - pdata.push_back(std::make_pair(pdata_point_lists.back().begin(), - pdata_point_lists.back().end())); - } - - CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return CGAL_Nef_polyhedron(N); -#endif -#if 0 - // This version of the code works fine but is pretty slow. - // - CGAL_Nef_polyhedron2 N; - Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); - - for (int i = 0; i < this->polygons.size(); i++) { - std::list<CGAL_Nef_polyhedron2::Point> plist; - for (int j = 0; j < this->polygons[i].size(); j++) { - double x = this->polygons[i][j].x; - double y = this->polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); -#endif -#if 1 - // This version of the code does essentially the same thing as the 2nd - // version but merges some triangles before sending them to CGAL. This adds - // complexity but speeds up things.. - // - struct PolyReducer - { - Grid2d<int> grid; - QHash< QPair<int,int>, QPair<int,int> > egde_to_poly; - QHash< int, CGAL_Nef_polyhedron2::Point > points; - QHash< int, QList<int> > polygons; - int poly_n; - - void add_edges(int pn) - { - for (int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair<int,int>(a, b)].first == 0) - this->egde_to_poly[QPair<int,int>(a, b)].first = pn; - else if (this->egde_to_poly[QPair<int,int>(a, b)].second == 0) - this->egde_to_poly[QPair<int,int>(a, b)].second = pn; - else - abort(); - } - } - - void del_poly(int pn) - { - for (int j = 1; j <= this->polygons[pn].size(); j++) { - int a = this->polygons[pn][j-1]; - int b = this->polygons[pn][j % this->polygons[pn].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair<int,int>(a, b)].first == pn) - this->egde_to_poly[QPair<int,int>(a, b)].first = 0; - if (this->egde_to_poly[QPair<int,int>(a, b)].second == pn) - this->egde_to_poly[QPair<int,int>(a, b)].second = 0; - } - this->polygons.remove(pn); - } - - PolyReducer(const PolySet *ps) : grid(GRID_COARSE), poly_n(1) - { - int point_n = 1; - for (int i = 0; i < ps->polygons.size(); i++) { - for (int j = 0; j < ps->polygons[i].size(); j++) { - double x = ps->polygons[i][j].x; - double y = ps->polygons[i][j].y; - if (this->grid.has(x, y)) { - this->polygons[this->poly_n].append(this->grid.data(x, y)); - } else { - this->grid.align(x, y) = point_n; - this->polygons[this->poly_n].append(point_n); - this->points[point_n] = CGAL_Nef_polyhedron2::Point(x, y); - point_n++; - } - } - add_edges(this->poly_n); - this->poly_n++; - } - } - - int merge(int p1, int p1e, int p2, int p2e) - { - for (int i = 1; i < this->polygons[p1].size(); i++) { - int j = (p1e + i) % this->polygons[p1].size(); - this->polygons[this->poly_n].append(this->polygons[p1][j]); - } - for (int i = 1; i < this->polygons[p2].size(); i++) { - int j = (p2e + i) % this->polygons[p2].size(); - this->polygons[this->poly_n].append(this->polygons[p2][j]); - } - del_poly(p1); - del_poly(p2); - add_edges(this->poly_n); - return this->poly_n++; - } - - void reduce() - { - QList<int> work_queue; - QHashIterator< int, QList<int> > it(polygons); - while (it.hasNext()) { - it.next(); - work_queue.append(it.key()); - } - while (!work_queue.isEmpty()) { - int poly1_n = work_queue.first(); - work_queue.removeFirst(); - if (!this->polygons.contains(poly1_n)) - continue; - for (int j = 1; j <= this->polygons[poly1_n].size(); j++) { - int a = this->polygons[poly1_n][j-1]; - int b = this->polygons[poly1_n][j % this->polygons[poly1_n].size()]; - if (a > b) { a = a^b; b = a^b; a = a^b; } - if (this->egde_to_poly[QPair<int,int>(a, b)].first != 0 && - this->egde_to_poly[QPair<int,int>(a, b)].second != 0) { - int poly2_n = this->egde_to_poly[QPair<int,int>(a, b)].first + - this->egde_to_poly[QPair<int,int>(a, b)].second - poly1_n; - int poly2_edge = -1; - for (int k = 1; k <= this->polygons[poly2_n].size(); k++) { - int c = this->polygons[poly2_n][k-1]; - int d = this->polygons[poly2_n][k % this->polygons[poly2_n].size()]; - if (c > d) { c = c^d; d = c^d; c = c^d; } - if (a == c && b == d) { - poly2_edge = k-1; - continue; - } - int poly3_n = this->egde_to_poly[QPair<int,int>(c, d)].first + - this->egde_to_poly[QPair<int,int>(c, d)].second - poly2_n; - if (poly3_n < 0) - continue; - if (poly3_n == poly1_n) - goto next_poly1_edge; - } - work_queue.append(merge(poly1_n, j-1, poly2_n, poly2_edge)); - goto next_poly1; - } - next_poly1_edge:; - } - next_poly1:; - } - } - - CGAL_Nef_polyhedron2 toNef() - { - CGAL_Nef_polyhedron2 N; - - QHashIterator< int, QList<int> > it(polygons); - while (it.hasNext()) { - it.next(); - std::list<CGAL_Nef_polyhedron2::Point> plist; - for (int j = 0; j < it.value().size(); j++) { - int p = it.value()[j]; - plist.push_back(points[p]); - } - N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return N; - } - }; - - PolyReducer pr(this); - // printf("Number of polygons before reduction: %d\n", pr.polygons.size()); - pr.reduce(); - // printf("Number of polygons after reduction: %d\n", pr.polygons.size()); - return CGAL_Nef_polyhedron(pr.toNef()); -#endif -#if 0 - // This is another experimental version. I should run faster than the above, - // is a lot simpler and has only one known weakness: Degenerate polygons, which - // get repaired by GLUTess, might trigger a CGAL crash here. The only - // known case for this is triangle-with-duplicate-vertex.dxf - // FIXME: If we just did a projection, we need to recreate the border! - if (this->polygons.size() > 0) assert(this->borders.size() > 0); - CGAL_Nef_polyhedron2 N; - Grid2d<CGAL_Nef_polyhedron2::Point> grid(GRID_COARSE); - - for (int i = 0; i < this->borders.size(); i++) { - std::list<CGAL_Nef_polyhedron2::Point> plist; - for (int j = 0; j < this->borders[i].size(); j++) { - double x = this->borders[i][j].x; - double y = this->borders[i][j].y; - CGAL_Nef_polyhedron2::Point p; - if (grid.has(x, y)) { - p = grid.data(x, y); - } else { - p = CGAL_Nef_polyhedron2::Point(x, y); - grid.data(x, y) = p; - } - plist.push_back(p); - } - // FIXME: If a border (path) has a duplicate vertex in dxf, - // the CGAL_Nef_polyhedron2 constructor will crash. - N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); - } - - return CGAL_Nef_polyhedron(N); - -#endif - } - else // not (this->is2d) - { - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - CGAL_Polyhedron P; - CGAL_Build_PolySet builder(this); - P.delegate(builder); -#if 0 - std::cout << P; -#endif - CGAL_Nef_polyhedron3 N(P); - return CGAL_Nef_polyhedron(N); - } - catch (CGAL::Assertion_exception e) { - PRINTF("ERROR: Illegal polygonal object - make sure all polygons are defined with the same winding order. Skipping affected object."); - CGAL::set_error_behaviour(old_behaviour); - return CGAL_Nef_polyhedron(); - } - CGAL::set_error_behaviour(old_behaviour); - } - return CGAL_Nef_polyhedron(); -} - -#endif /* ENABLE_CGAL */ - diff --git a/src/polyset.h b/src/polyset.h index 8712ff2..aae32f6 100644 --- a/src/polyset.h +++ b/src/polyset.h @@ -8,9 +8,6 @@ #ifdef ENABLE_OPENCSG # include <opencsg.h> #endif -#ifdef ENABLE_CGAL -# include "cgal.h" -#endif #include <QCache> @@ -62,22 +59,9 @@ public: CSGMODE_HIGHLIGHT_DIFFERENCE = 22 }; - struct ps_cache_entry { - PolySet *ps; - QString msg; - ps_cache_entry(PolySet *ps); - ~ps_cache_entry(); - }; - - static QCache<QString,ps_cache_entry> ps_cache; - void render_surface(colormode_e colormode, csgmode_e csgmode, double *m, GLint *shaderinfo = NULL) const; void render_edges(colormode_e colormode, csgmode_e csgmode) const; -#ifdef ENABLE_CGAL - CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - int refcount; PolySet *link(); void unlink(); diff --git a/src/primitives.cc b/src/primitives.cc index 5180c16..204bb11 100644 --- a/src/primitives.cc +++ b/src/primitives.cc @@ -33,6 +33,9 @@ #include "builtin.h" #include "printutils.h" #include <assert.h> +#include "visitor.h" +#include <sstream> +#include <assert.h> #define F_MINIMUM 0.01 @@ -57,20 +60,52 @@ public: class PrimitiveNode : public AbstractPolyNode { public: + PrimitiveNode(const ModuleInstantiation *mi, primitive_type_e type) : AbstractPolyNode(mi), type(type) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { + switch (this->type) { + case CUBE: + return "cube"; + break; + case SPHERE: + return "sphere"; + break; + case CYLINDER: + return "cylinder"; + break; + case POLYHEDRON: + return "polyhedron"; + break; + case SQUARE: + return "square"; + break; + case CIRCLE: + return "circle"; + break; + case POLYGON: + return "polygon"; + break; + default: + assert(false && "PrimitiveNode::name(): Unknown primitive type"); + return AbstractPolyNode::name(); + } + } + bool center; double x, y, z, h, r1, r2; double fn, fs, fa; primitive_type_e type; int convexity; Value points, paths, triangles; - PrimitiveNode(const ModuleInstantiation *mi, primitive_type_e type) : AbstractPolyNode(mi), type(type) { } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *) const; }; AbstractNode *PrimitiveModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { - PrimitiveNode *node = new PrimitiveNode(inst, type); + PrimitiveNode *node = new PrimitiveNode(inst, this->type); node->center = false; node->x = node->y = node->z = node->h = node->r1 = node->r2 = 1; @@ -78,26 +113,30 @@ AbstractNode *PrimitiveModule::evaluate(const Context *ctx, const ModuleInstanti QVector<QString> argnames; QVector<Expression*> argexpr; - if (type == CUBE) { + switch (this->type) { + case CUBE: argnames = QVector<QString>() << "size" << "center"; - } - if (type == SPHERE) { + break; + case SPHERE: argnames = QVector<QString>() << "r"; - } - if (type == CYLINDER) { + break; + case CYLINDER: argnames = QVector<QString>() << "h" << "r1" << "r2" << "center"; - } - if (type == POLYHEDRON) { + break; + case POLYHEDRON: argnames = QVector<QString>() << "points" << "triangles" << "convexity"; - } - if (type == SQUARE) { + break; + case SQUARE: argnames = QVector<QString>() << "size" << "center"; - } - if (type == CIRCLE) { + break; + case CIRCLE: argnames = QVector<QString>() << "r"; - } - if (type == POLYGON) { + break; + case POLYGON: argnames = QVector<QString>() << "points" << "paths" << "convexity"; + break; + default: + assert(false && "PrimitiveModule::evaluate(): Unknown node type"); } Context c(ctx); @@ -233,25 +272,25 @@ static void generate_circle(point2d *circle, double r, int fragments) } } -PolySet *PrimitiveNode::render_polyset(render_mode_e) const +PolySet *PrimitiveNode::evaluate_polyset(render_mode_e, class PolySetEvaluator *) const { PolySet *p = new PolySet(); - if (type == CUBE && x > 0 && y > 0 && z > 0) + if (this->type == CUBE && this->x > 0 && this->y > 0 && this->z > 0) { double x1, x2, y1, y2, z1, z2; - if (center) { - x1 = -x/2; - x2 = +x/2; - y1 = -y/2; - y2 = +y/2; - z1 = -z/2; - z2 = +z/2; + if (this->center) { + x1 = -this->x/2; + x2 = +this->x/2; + y1 = -this->y/2; + y2 = +this->y/2; + z1 = -this->z/2; + z2 = +this->z/2; } else { x1 = y1 = z1 = 0; - x2 = x; - y2 = y; - z2 = z; + x2 = this->x; + y2 = this->y; + z2 = this->z; } p->append_poly(); // top @@ -291,7 +330,7 @@ PolySet *PrimitiveNode::render_polyset(render_mode_e) const p->append_vertex(x1, y2, z2); } - if (type == SPHERE && r1 > 0) + if (this->type == SPHERE && this->r1 > 0) { struct ring_s { point2d *points; @@ -358,17 +397,18 @@ sphere_next_r2: delete[] ring; } - if (type == CYLINDER && h > 0 && r1 >=0 && r2 >= 0 && (r1 > 0 || r2 > 0)) + if (this->type == CYLINDER && + this->h > 0 && this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0)) { - int fragments = get_fragments_from_r(fmax(r1, r2), fn, fs, fa); + int fragments = get_fragments_from_r(fmax(this->r1, this->r2), this->fn, this->fs, this->fa); double z1, z2; - if (center) { - z1 = -h/2; - z2 = +h/2; + if (this->center) { + z1 = -this->h/2; + z2 = +this->h/2; } else { z1 = 0; - z2 = h; + z2 = this->h; } point2d *circle1 = new point2d[fragments]; @@ -401,13 +441,13 @@ sphere_next_r2: } } - if (r1 > 0) { + if (this->r1 > 0) { p->append_poly(); for (int i=0; i<fragments; i++) p->insert_vertex(circle1[i].x, circle1[i].y, z1); } - if (r2 > 0) { + if (this->r2 > 0) { p->append_poly(); for (int i=0; i<fragments; i++) p->append_vertex(circle2[i].x, circle2[i].y, z2); @@ -417,35 +457,35 @@ sphere_next_r2: delete[] circle2; } - if (type == POLYHEDRON) + if (this->type == POLYHEDRON) { - p->convexity = convexity; - for (int i=0; i<triangles.vec.size(); i++) + p->convexity = this->convexity; + for (size_t i=0; i<this->triangles.vec.size(); i++) { p->append_poly(); - for (int j=0; j<triangles.vec[i]->vec.size(); j++) { - int pt = triangles.vec[i]->vec[j]->num; - if (pt < points.vec.size()) { + for (size_t j=0; j<this->triangles.vec[i]->vec.size(); j++) { + int pt = this->triangles.vec[i]->vec[j]->num; + if (pt < this->points.vec.size()) { double px, py, pz; - if (points.vec[pt]->getv3(px, py, pz)) + if (this->points.vec[pt]->getv3(px, py, pz)) p->insert_vertex(px, py, pz); } } } } - if (type == SQUARE) + if (this->type == SQUARE) { double x1, x2, y1, y2; - if (center) { - x1 = -x/2; - x2 = +x/2; - y1 = -y/2; - y2 = +y/2; + if (this->center) { + x1 = -this->x/2; + x2 = +this->x/2; + y1 = -this->y/2; + y2 = +this->y/2; } else { x1 = y1 = 0; - x2 = x; - y2 = y; + x2 = this->x; + y2 = this->y; } p->is2d = true; @@ -456,37 +496,37 @@ sphere_next_r2: p->append_vertex(x1, y2); } - if (type == CIRCLE) + if (this->type == CIRCLE) { - int fragments = get_fragments_from_r(r1, fn, fs, fa); + int fragments = get_fragments_from_r(this->r1, this->fn, this->fs, this->fa); p->is2d = true; p->append_poly(); for (int i=0; i < fragments; i++) { double phi = (M_PI*2*i) / fragments; - p->append_vertex(r1*cos(phi), r1*sin(phi)); + p->append_vertex(this->r1*cos(phi), this->r1*sin(phi)); } } - if (type == POLYGON) + if (this->type == POLYGON) { DxfData dd; - for (int i=0; i<points.vec.size(); i++) { + for (size_t i=0; i<this->points.vec.size(); i++) { double x,y; - if (!points.vec[i]->getv2(x, y)) { + if (!this->points.vec[i]->getv2(x, y)) { PRINTF("ERROR: Unable to convert point at index %d to a vec2 of numbers", i); - // FIXME: Return NULL and make sure this is checked by all callers? - return p; + p->unlink(); + return NULL; } dd.points.append(DxfData::Point(x, y)); } - if (paths.vec.size() == 0) + if (this->paths.vec.size() == 0) { dd.paths.append(DxfData::Path()); - for (int i=0; i<points.vec.size(); i++) { + for (size_t i=0; i<this->points.vec.size(); i++) { assert(i < dd.points.size()); // FIXME: Not needed, but this used to be an 'if' DxfData::Point *p = &dd.points[i]; dd.paths.last().points.append(p); @@ -498,11 +538,11 @@ sphere_next_r2: } else { - for (int i=0; i<paths.vec.size(); i++) + for (size_t i=0; i<this->paths.vec.size(); i++) { dd.paths.append(DxfData::Path()); - for (int j=0; j<paths.vec[i]->vec.size(); j++) { - int idx = paths.vec[i]->vec[j]->num; + for (size_t j=0; j<this->paths.vec[i]->vec.size(); j++) { + int idx = this->paths.vec[i]->vec[j]->num; if (idx < dd.points.size()) { DxfData::Point *p = &dd.points[idx]; dd.paths.last().points.append(p); @@ -526,26 +566,45 @@ sphere_next_r2: return p; } -QString PrimitiveNode::dump(QString indent) const +std::string PrimitiveNode::toString() const { - if (dump_cache.isEmpty()) { - QString text; - if (type == CUBE) - text.sprintf("cube(size = [%g, %g, %g], center = %s);\n", x, y, z, center ? "true" : "false"); - if (type == SPHERE) - text.sprintf("sphere($fn = %g, $fa = %g, $fs = %g, r = %g);\n", fn, fa, fs, r1); - if (type == CYLINDER) - text.sprintf("cylinder($fn = %g, $fa = %g, $fs = %g, h = %g, r1 = %g, r2 = %g, center = %s);\n", fn, fa, fs, h, r1, r2, center ? "true" : "false"); - if (type == POLYHEDRON) - text.sprintf("polyhedron(points = %s, triangles = %s, convexity = %d);\n", points.dump().toAscii().data(), triangles.dump().toAscii().data(), convexity); - if (type == SQUARE) - text.sprintf("square(size = [%g, %g], center = %s);\n", x, y, center ? "true" : "false"); - if (type == CIRCLE) - text.sprintf("circle($fn = %g, $fa = %g, $fs = %g, r = %g);\n", fn, fa, fs, r1); - if (type == POLYGON) - text.sprintf("polygon(points = %s, paths = %s, convexity = %d);\n", points.dump().toAscii().data(), paths.dump().toAscii().data(), convexity); - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; + std::stringstream stream; + + stream << this->name(); + + switch (this->type) { + case CUBE: + stream << "(size = [" << this->x << ", " << this->y << ", " << this->z << "], " + << "center = " << (center ? "true" : "false") << ")"; + break; + case SPHERE: + stream << "($fn = " << this->fn << ", $fa = " << this->fa + << ", $fs = " << this->fs << ", r = " << this->r1 << ")"; + break; + case CYLINDER: + stream << "($fn = " << this->fn << ", $fa = " << this->fa + << ", $fs = " << this->fs << ", h = " << this->h << ", r1 = " << this->r1 + << ", r2 = " << this->r2 << ", center = " << (center ? "true" : "false") << ")"; + break; + case POLYHEDRON: + stream << "(points = " << this->points + << ", triangles = " << this->triangles + << ", convexity = " << this->convexity << ")"; + break; + case SQUARE: + stream << "(size = [" << this->x << ", " << this->y << "], " + << "center = " << (center ? "true" : "false") << ")"; + break; + case CIRCLE: + stream << "($fn = " << this->fn << ", $fa = " << this->fa + << ", $fs = " << this->fs << ", r = " << this->r1 << ")"; + break; + case POLYGON: + stream << "(points = " << this->points << ", paths = " << this->paths << ", convexity = " << this->convexity << ")"; + break; + default: + assert(false); } - return dump_cache; -} + return stream.str(); +} diff --git a/src/printutils.cc b/src/printutils.cc index 8830a8c..01fa06b 100644 --- a/src/printutils.cc +++ b/src/printutils.cc @@ -18,8 +18,7 @@ void print_messages_push() void print_messages_pop() { - QString msg = print_messages_stack.last(); - print_messages_stack.removeLast(); + QString msg = print_messages_stack.takeLast(); if (print_messages_stack.size() > 0 && !msg.isNull()) { if (!print_messages_stack.last().isEmpty()) print_messages_stack.last() += "\n"; @@ -49,3 +48,8 @@ void PRINT_NOCACHE(const QString &msg) outputhandler(msg, outputhandler_data); } } + +std::ostream &operator<<(std::ostream &os, const QFileInfo &fi) { + os << std::hex << (fi.exists()?fi.lastModified().toTime_t():0) << "." << fi.size(); + return os; +} diff --git a/src/printutils.h b/src/printutils.h index 7f2e828..0432622 100644 --- a/src/printutils.h +++ b/src/printutils.h @@ -3,6 +3,9 @@ #include <QString> #include <QList> +#include <iostream> +#include <QFileInfo> +#include <QDateTime> typedef void (OutputHandlerFunc)(const QString &msg, void *userdata); extern OutputHandlerFunc *outputhandler; @@ -22,4 +25,6 @@ void PRINT_NOCACHE(const QString &msg); #define PRINTF_NOCACHE(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT_NOCACHE(_m); } while (0) #define PRINTA_NOCACHE(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT_NOCACHE(_m); } while (0) +std::ostream &operator<<(std::ostream &os, const QFileInfo &fi); + #endif diff --git a/src/projection.cc b/src/projection.cc index f41ba56..8497405 100644 --- a/src/projection.cc +++ b/src/projection.cc @@ -24,8 +24,8 @@ * */ +#include "projectionnode.h" #include "module.h" -#include "node.h" #include "context.h" #include "printutils.h" #include "builtin.h" @@ -34,6 +34,8 @@ #include "polyset.h" #include "export.h" #include "progress.h" +#include "visitor.h" +#include "PolySetEvaluator.h" #ifdef ENABLE_CGAL # include <CGAL/assertions_behaviour.h> @@ -41,6 +43,7 @@ #endif #include <assert.h> +#include <sstream> #include <QApplication> #include <QTime> @@ -53,18 +56,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class ProjectionNode : public AbstractPolyNode -{ -public: - int convexity; - bool cut_mode; - ProjectionNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { - cut_mode = false; - } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *ProjectionModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { ProjectionNode *node = new ProjectionNode(inst); @@ -92,207 +83,35 @@ AbstractNode *ProjectionModule::evaluate(const Context *ctx, const ModuleInstant return node; } -void register_builtin_projection() -{ - builtin_modules["projection"] = new ProjectionModule(); -} - -#ifdef ENABLE_CGAL - -PolySet *ProjectionNode::render_polyset(render_mode_e) const +PolySet *ProjectionNode::evaluate_polyset(render_mode_e mode, PolySetEvaluator *evaluator) const { - QString key = mk_cache_id(); - if (PolySet::ps_cache.contains(key)) { - PRINT(PolySet::ps_cache[key]->msg); - return PolySet::ps_cache[key]->ps->link(); - } - - print_messages_push(); - - PolySet *ps = new PolySet(); - ps->convexity = this->convexity; - ps->is2d = true; - - CGAL_Nef_polyhedron N; - N.dim = 3; - CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); - try { - foreach(AbstractNode *v, this->children) { - if (v->modinst->tag_background) - continue; - N.p3 += v->render_cgal_nef_polyhedron().p3; - } - } - catch (CGAL::Assertion_exception e) { - PRINTF("ERROR: Illegal polygonal object - make sure all polygons are defined with the same winding order. Skipping affected object."); - CGAL::set_error_behaviour(old_behaviour); + if (!evaluator) { + PRINTF("WARNING: No suitable PolySetEvaluator found for %s module!", this->name().c_str()); + PolySet *ps = new PolySet(); + ps->is2d = true; return ps; } - CGAL::set_error_behaviour(old_behaviour); - - if (cut_mode) - { - PolySet *cube = new PolySet(); - double infval = 1e8, eps = 0.1; - double x1 = -infval, x2 = +infval, y1 = -infval, y2 = +infval, z1 = 0, z2 = eps; - - cube->append_poly(); // top - cube->append_vertex(x1, y1, z2); - cube->append_vertex(x2, y1, z2); - cube->append_vertex(x2, y2, z2); - cube->append_vertex(x1, y2, z2); - - cube->append_poly(); // bottom - cube->append_vertex(x1, y2, z1); - cube->append_vertex(x2, y2, z1); - cube->append_vertex(x2, y1, z1); - cube->append_vertex(x1, y1, z1); - - cube->append_poly(); // side1 - cube->append_vertex(x1, y1, z1); - cube->append_vertex(x2, y1, z1); - cube->append_vertex(x2, y1, z2); - cube->append_vertex(x1, y1, z2); - - cube->append_poly(); // side2 - cube->append_vertex(x2, y1, z1); - cube->append_vertex(x2, y2, z1); - cube->append_vertex(x2, y2, z2); - cube->append_vertex(x2, y1, z2); - - cube->append_poly(); // side3 - cube->append_vertex(x2, y2, z1); - cube->append_vertex(x1, y2, z1); - cube->append_vertex(x1, y2, z2); - cube->append_vertex(x2, y2, z2); - - cube->append_poly(); // side4 - cube->append_vertex(x1, y2, z1); - cube->append_vertex(x1, y1, z1); - cube->append_vertex(x1, y1, z2); - cube->append_vertex(x1, y2, z2); - CGAL_Nef_polyhedron Ncube = cube->render_cgal_nef_polyhedron(); - cube->unlink(); - - // N.p3 *= CGAL_Nef_polyhedron3(CGAL_Plane(0, 0, 1, 0), CGAL_Nef_polyhedron3::INCLUDED); - N.p3 *= Ncube.p3; - if (!N.p3.is_simple()) { - PRINTF("WARNING: Body of projection(cut = true) isn't valid 2-manifold! Modify your design.."); - goto cant_project_non_simple_polyhedron; - } - - PolySet *ps3 = new PolySet(); - cgal_nef3_to_polyset(ps3, &N); - Grid2d<int> conversion_grid(GRID_COARSE); - for (int i = 0; i < ps3->polygons.size(); i++) { - for (int j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j].x; - double y = ps3->polygons[i][j].y; - double z = ps3->polygons[i][j].z; - if (z != 0) - goto next_ps3_polygon_cut_mode; - if (conversion_grid.align(x, y) == i+1) - goto next_ps3_polygon_cut_mode; - conversion_grid.data(x, y) = i+1; - } - ps->append_poly(); - for (int j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j].x; - double y = ps3->polygons[i][j].y; - conversion_grid.align(x, y); - ps->insert_vertex(x, y); - } - next_ps3_polygon_cut_mode:; - } - ps3->unlink(); - } - else - { - if (!N.p3.is_simple()) { - PRINTF("WARNING: Body of projection(cut = false) isn't valid 2-manifold! Modify your design.."); - goto cant_project_non_simple_polyhedron; - } - PolySet *ps3 = new PolySet(); - cgal_nef3_to_polyset(ps3, &N); - CGAL_Nef_polyhedron np; - np.dim = 2; - for (int i = 0; i < ps3->polygons.size(); i++) - { - int min_x_p = -1; - double min_x_val = 0; - for (int j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j].x; - if (min_x_p < 0 || x < min_x_val) { - min_x_p = j; - min_x_val = x; - } - } - int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); - int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); - double ax = ps3->polygons[i][min_x_p1].x - ps3->polygons[i][min_x_p].x; - double ay = ps3->polygons[i][min_x_p1].y - ps3->polygons[i][min_x_p].y; - double at = atan2(ay, ax); - double bx = ps3->polygons[i][min_x_p2].x - ps3->polygons[i][min_x_p].x; - double by = ps3->polygons[i][min_x_p2].y - ps3->polygons[i][min_x_p].y; - double bt = atan2(by, bx); - - double eps = 0.000001; - if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || - (fabs(bx) < eps && fabs(by) < eps)) { - // this triangle is degenerated in projection - continue; - } + print_messages_push(); - std::list<CGAL_Nef_polyhedron2::Point> plist; - for (int j = 0; j < ps3->polygons[i].size(); j++) { - double x = ps3->polygons[i][j].x; - double y = ps3->polygons[i][j].y; - CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); - if (at > bt) - plist.push_front(p); - else - plist.push_back(p); - } - np.p2 += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), - CGAL_Nef_polyhedron2::INCLUDED); - } - DxfData dxf(np); - dxf_tesselate(ps, &dxf, 0, true, false, 0); - dxf_border_to_ps(ps, &dxf); - ps3->unlink(); - } + PolySet *ps = evaluator->evaluatePolySet(*this, mode); -cant_project_non_simple_polyhedron: - PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link())); print_messages_pop(); return ps; } -#else // ENABLE_CGAL - -PolySet *ProjectionNode::render_polyset(render_mode_e) const +std::string ProjectionNode::toString() const { - PRINT("WARNING: Found projection() statement but compiled without CGAL support!"); - PolySet *ps = new PolySet(); - ps->is2d = true; - return ps; -} + std::stringstream stream; -#endif // ENABLE_CGAL + stream << "projection(cut = " << (this->cut_mode ? "true" : "false") + << ", convexity = " << this->convexity << ")"; -QString ProjectionNode::dump(QString indent) const -{ - if (dump_cache.isEmpty()) { - QString text; - text.sprintf("projection(cut = %s, convexity = %d) {\n", - this->cut_mode ? "true" : "false", this->convexity); - foreach (AbstractNode *v, this->children) - text += v->dump(indent + QString("\t")); - text += indent + "}\n"; - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; - } - return dump_cache; + return stream.str(); } +void register_builtin_projection() +{ + builtin_modules["projection"] = new ProjectionModule(); +} diff --git a/src/projectionnode.h b/src/projectionnode.h new file mode 100644 index 0000000..0c32181 --- /dev/null +++ b/src/projectionnode.h @@ -0,0 +1,24 @@ +#ifndef PROJECTIONNODE_H_ +#define PROJECTIONNODE_H_ + +#include "node.h" +#include "visitor.h" + +class ProjectionNode : public AbstractPolyNode +{ +public: + ProjectionNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { + cut_mode = false; + } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "projection"; } + + int convexity; + bool cut_mode; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *evaluator) const; +}; + +#endif diff --git a/src/qhash.cc b/src/qhash.cc new file mode 100644 index 0000000..cec9adf --- /dev/null +++ b/src/qhash.cc @@ -0,0 +1,19 @@ +#include "myqhash.h" + +static uint hash(const uchar *p, int n) +{ + uint h = 0; + uint g; + + while (n--) { + h = (h << 4) + *p++; + if ((g = (h & 0xf0000000)) != 0) + h ^= g >> 23; + h &= ~g; + } + return h; +} + +uint qHash(const std::string &str) { + return hash(reinterpret_cast<const uchar *>(str.c_str()), str.length()); +} diff --git a/src/render-opencsg.cc.org b/src/render-opencsg.cc.org new file mode 100644 index 0000000..fe0fc60 --- /dev/null +++ b/src/render-opencsg.cc.org @@ -0,0 +1,87 @@ +#include "render-opencsg.h" +#include "polyset.h" +#include "csgterm.h" +#ifdef ENABLE_OPENCSG +# include <opencsg.h> +#endif + +class OpenCSGPrim : public OpenCSG::Primitive +{ +public: + OpenCSGPrim(OpenCSG::Operation operation, unsigned int convexity) : + OpenCSG::Primitive(operation, convexity) { } + PolySet *p; + double *m; + int csgmode; + virtual void render() { + glPushMatrix(); + glMultMatrixd(m); + p->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode), m); + glPopMatrix(); + } +}; + +void renderCSGChainviaOpenCSG(CSGChain *chain, GLint *shaderinfo, bool highlight, bool background) +{ + std::vector<OpenCSG::Primitive*> primitives; + int j = 0; + for (int i = 0;; i++) + { + bool last = i == chain->polysets.size(); + + if (last || chain->types[i] == CSGTerm::TYPE_UNION) + { + if (j+1 != i) { + OpenCSG::render(primitives); + glDepthFunc(GL_EQUAL); + } + if (shaderinfo) + glUseProgram(shaderinfo[0]); + for (; j < i; j++) { + double *m = chain->matrices[j]; + glPushMatrix(); + glMultMatrixd(m); + int csgmode = chain->types[j] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; + if (highlight) { + chain->polysets[j]->render_surface(PolySet::COLORMODE_HIGHLIGHT, PolySet::csgmode_e(csgmode + 20), m, shaderinfo); + } else if (background) { + chain->polysets[j]->render_surface(PolySet::COLORMODE_BACKGROUND, PolySet::csgmode_e(csgmode + 10), m, shaderinfo); + } else if (m[16] >= 0 || m[17] >= 0 || m[18] >= 0 || m[19] >= 0) { + // User-defined color from source + glColor4d(m[16], m[17], m[18], m[19]); + if (shaderinfo) { + glUniform4f(shaderinfo[1], m[16], m[17], m[18], m[19]); + glUniform4f(shaderinfo[2], (m[16]+1)/2, (m[17]+1)/2, (m[18]+1)/2, 1.0); + } + chain->polysets[j]->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode), m, shaderinfo); + } else if (chain->types[j] == CSGTerm::TYPE_DIFFERENCE) { + chain->polysets[j]->render_surface(PolySet::COLORMODE_CUTOUT, PolySet::csgmode_e(csgmode), m, shaderinfo); + } else { + chain->polysets[j]->render_surface(PolySet::COLORMODE_MATERIAL, PolySet::csgmode_e(csgmode), m, shaderinfo); + } + glPopMatrix(); + } + if (shaderinfo) + glUseProgram(0); + for (unsigned int k = 0; k < primitives.size(); k++) { + delete primitives[k]; + } + glDepthFunc(GL_LEQUAL); + primitives.clear(); + } + + if (last) + break; + + OpenCSGPrim *prim = new OpenCSGPrim(chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? + OpenCSG::Subtraction : OpenCSG::Intersection, chain->polysets[i]->convexity); + prim->p = chain->polysets[i]; + prim->m = chain->matrices[i]; + prim->csgmode = chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL; + if (highlight) + prim->csgmode += 20; + else if (background) + prim->csgmode += 10; + primitives.push_back(prim); + } +} diff --git a/src/render.cc b/src/render.cc index 9fa7ab6..cac03c3 100644 --- a/src/render.cc +++ b/src/render.cc @@ -24,8 +24,8 @@ * */ +#include "rendernode.h" #include "module.h" -#include "node.h" #include "polyset.h" #include "context.h" #include "dxfdata.h" @@ -34,13 +34,11 @@ #include "builtin.h" #include "printutils.h" #include "progress.h" -#ifdef ENABLE_CGAL -# include "cgal.h" -#endif +#include "visitor.h" -#include <QProgressDialog> #include <QApplication> #include <QTime> +#include <sstream> class RenderModule : public AbstractModule { @@ -49,18 +47,6 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class RenderNode : public AbstractNode -{ -public: - int convexity; - RenderNode(const ModuleInstantiation *mi) : AbstractNode(mi), convexity(1) { } -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; -}; - AbstractNode *RenderModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { RenderNode *node = new RenderNode(inst); @@ -89,176 +75,11 @@ void register_builtin_render() builtin_modules["render"] = new RenderModule(); } -#ifdef ENABLE_CGAL - -CGAL_Nef_polyhedron RenderNode::render_cgal_nef_polyhedron() const +std::string RenderNode::toString() const { - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) { - progress_report(); - PRINT(cgal_nef_cache[cache_id]->msg); - return cgal_nef_cache[cache_id]->N; - } + std::stringstream stream; - print_messages_push(); + stream << this->name() << "(convexity = " << convexity << ")"; - bool first = true; - CGAL_Nef_polyhedron N; - foreach(AbstractNode * v, children) - { - if (v->modinst->tag_background) - continue; - if (first) { - N = v->render_cgal_nef_polyhedron(); - if (N.dim != 0) - first = false; - } else if (N.dim == 2) { - N.p2 += v->render_cgal_nef_polyhedron().p2; - } else if (N.dim == 3) { - N.p3 += v->render_cgal_nef_polyhedron().p3; - } - v->progress_report(); - } - - cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight()); - print_messages_pop(); - progress_report(); - - return N; + return stream.str(); } - -CSGTerm *AbstractNode::render_csg_term_from_nef(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background, const char *statement, int convexity) const -{ - QString key = mk_cache_id(); - if (PolySet::ps_cache.contains(key)) { - PRINT(PolySet::ps_cache[key]->msg); - return AbstractPolyNode::render_csg_term_from_ps(m, highlights, background, - PolySet::ps_cache[key]->ps->link(), modinst, idx); - } - - print_messages_push(); - CGAL_Nef_polyhedron N; - - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) - { - PRINT(cgal_nef_cache[cache_id]->msg); - N = cgal_nef_cache[cache_id]->N; - } - else - { - PRINTF_NOCACHE("Processing uncached %s statement...", statement); - // PRINTA("Cache ID: %1", cache_id); - QApplication::processEvents(); - - QTime t; - t.start(); - - N = this->render_cgal_nef_polyhedron(); - - int s = t.elapsed() / 1000; - PRINTF_NOCACHE("..rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60); - } - - PolySet *ps = NULL; - - if (N.dim == 2) - { - DxfData dd(N); - ps = new PolySet(); - ps->is2d = true; - dxf_tesselate(ps, &dd, 0, true, false, 0); - dxf_border_to_ps(ps, &dd); - } - - if (N.dim == 3) - { - if (!N.p3.is_simple()) { - PRINTF("WARNING: Result of %s() isn't valid 2-manifold! Modify your design..", statement); - return NULL; - } - - ps = new PolySet(); - - CGAL_Polyhedron P; - N.p3.convert_to_Polyhedron(P); - - typedef CGAL_Polyhedron::Vertex Vertex; - typedef CGAL_Polyhedron::Vertex_const_iterator VCI; - typedef CGAL_Polyhedron::Facet_const_iterator FCI; - typedef CGAL_Polyhedron::Halfedge_around_facet_const_circulator HFCC; - - for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) { - HFCC hc = fi->facet_begin(); - HFCC hc_end = hc; - ps->append_poly(); - do { - Vertex v = *VCI((hc++)->vertex()); - double x = CGAL::to_double(v.point().x()); - double y = CGAL::to_double(v.point().y()); - double z = CGAL::to_double(v.point().z()); - ps->append_vertex(x, y, z); - } while (hc != hc_end); - } - } - - if (ps) - { - ps->convexity = convexity; - PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link())); - - CSGTerm *term = new CSGTerm(ps, m, QString("n%1").arg(idx)); - if (modinst->tag_highlight && highlights) - highlights->append(term->link()); - if (modinst->tag_background && background) { - background->append(term); - return NULL; - } - return term; - } - print_messages_pop(); - - return NULL; -} - -CSGTerm *RenderNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - return render_csg_term_from_nef(m, highlights, background, "render", this->convexity); -} - -#else - -CSGTerm *RenderNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - CSGTerm *t1 = NULL; - PRINT("WARNING: Found render() statement but compiled without CGAL support!"); - foreach(AbstractNode * v, children) { - CSGTerm *t2 = v->render_csg_term(m, highlights, background); - if (t2 && !t1) { - t1 = t2; - } else if (t2 && t1) { - t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); - } - } - if (modinst->tag_highlight && highlights) - highlights->append(t1->link()); - if (t1 && modinst->tag_background && background) { - background->append(t1); - return NULL; - } - return t1; -} - -#endif - -QString RenderNode::dump(QString indent) const -{ - if (dump_cache.isEmpty()) { - QString text = indent + QString("n%1: ").arg(idx) + QString("render(convexity = %1) {\n").arg(QString::number(convexity)); - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; - } - return dump_cache; -} - diff --git a/src/rendernode.h b/src/rendernode.h new file mode 100644 index 0000000..c5ebdae --- /dev/null +++ b/src/rendernode.h @@ -0,0 +1,20 @@ +#ifndef RENDERNODE_H_ +#define RENDERNODE_H_ + +#include "node.h" +#include "visitor.h" + +class RenderNode : public AbstractNode +{ +public: + RenderNode(const ModuleInstantiation *mi) : AbstractNode(mi), convexity(1) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "render"; } + + int convexity; +}; + +#endif diff --git a/src/state.h b/src/state.h new file mode 100644 index 0000000..ae25c0f --- /dev/null +++ b/src/state.h @@ -0,0 +1,36 @@ +#ifndef STATE_H_ +#define STATE_H_ + +class State +{ +public: + State(const class AbstractNode *parent) + : parentnode(parent), isprefix(false), ispostfix(false), numchildren(0) { + for (int i=0;i<16;i++) this->m[i] = i % 5 == 0 ? 1.0 : 0.0; + for (int i=16;i<20;i++) this->m[i] = -1.0; + } + virtual ~State() {} + + void setPrefix(bool on) { this->isprefix = on; } + void setPostfix(bool on) { this->ispostfix = on; } + void setNumChildren(unsigned int numc) { this->numchildren = numc; } + void setParent(const AbstractNode *parent) { this->parentnode = parent; } + void setMatrix(const double m[20]) { memcpy(this->m, m, 20*sizeof(m)); } + + bool isPrefix() const { return this->isprefix; } + bool isPostfix() const { return this->ispostfix; } + unsigned int numChildren() const { return this->numchildren; } + const AbstractNode *parent() const { return this->parentnode; } + const double *matrix() const { return this->m; } + +private: + const AbstractNode * parentnode; + bool isprefix; + bool ispostfix; + unsigned int numchildren; + + // Transformation matrix incl. color. FIXME: Generalize such state variables? + double m[20]; +}; + +#endif diff --git a/src/surface.cc b/src/surface.cc index 92b661f..94d039e 100644 --- a/src/surface.cc +++ b/src/surface.cc @@ -32,8 +32,10 @@ #include "dxftess.h" #include "printutils.h" #include "openscad.h" // handle_dep() +#include "visitor.h" #include <QFile> +#include <sstream> class SurfaceModule : public AbstractModule { @@ -45,12 +47,17 @@ public: class SurfaceNode : public AbstractPolyNode { public: + SurfaceNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "surface"; } + QString filename; bool center; int convexity; - SurfaceNode(const ModuleInstantiation *mi) : AbstractPolyNode(mi) { } - virtual PolySet *render_polyset(render_mode_e mode) const; - virtual QString dump(QString indent) const; + virtual PolySet *evaluate_polyset(render_mode_e mode, class PolySetEvaluator *) const; }; AbstractNode *SurfaceModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const @@ -65,7 +72,7 @@ AbstractNode *SurfaceModule::evaluate(const Context *ctx, const ModuleInstantiat Context c(ctx); c.args(argnames, argexpr, inst->argnames, inst->argvalues); - node->filename = c.get_absolute_path(c.lookup_variable("file").text); + node->filename = c.get_absolute_path(QString::fromStdString(c.lookup_variable("file").text)); Value center = c.lookup_variable("center", true); if (center.type == Value::BOOL) { @@ -85,14 +92,15 @@ void register_builtin_surface() builtin_modules["surface"] = new SurfaceModule(); } -PolySet *SurfaceNode::render_polyset(render_mode_e) const +PolySet *SurfaceNode::evaluate_polyset(render_mode_e, class PolySetEvaluator *) const { + PolySet *p = new PolySet(); handle_dep(filename); QFile f(filename); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - PRINTF("WARNING: Can't open DXF file `%s'.", filename.toAscii().data()); - return NULL; + PRINTF("WARNING: Can't open DAT file `%s'.", filename.toAscii().data()); + return p; } int lines = 0, columns = 0; @@ -119,7 +127,6 @@ PolySet *SurfaceNode::render_polyset(render_mode_e) const lines++; } - PolySet *p = new PolySet(); p->convexity = convexity; double ox = center ? -columns/2.0 : 0; @@ -198,14 +205,12 @@ PolySet *SurfaceNode::render_polyset(render_mode_e) const return p; } -QString SurfaceNode::dump(QString indent) const +std::string SurfaceNode::toString() const { - if (dump_cache.isEmpty()) { - QString text; - text.sprintf("surface(file = \"%s\", center = %s);\n", - filename.toAscii().data(), center ? "true" : "false"); - ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text; - } - return dump_cache; -} + std::stringstream stream; + stream << this->name() << "(file = \"" << this->filename + << "\", center = " << (this->center ? "true" : "false") << ")"; + + return stream.str(); +} diff --git a/src/transform.cc b/src/transform.cc index 7b15a7e..b22b766 100644 --- a/src/transform.cc +++ b/src/transform.cc @@ -24,8 +24,8 @@ * */ +#include "transformnode.h" #include "module.h" -#include "node.h" #include "context.h" #include "dxfdata.h" #include "csgterm.h" @@ -33,6 +33,10 @@ #include "dxftess.h" #include "builtin.h" #include "printutils.h" +#include "visitor.h" +#include <sstream> +#include <vector> +#include <assert.h> enum transform_type_e { SCALE, @@ -51,63 +55,80 @@ public: virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const; }; -class TransformNode : public AbstractNode +using std::string; +using std::vector; + +static vector<string> split(const string &str, const string &delim) { -public: - double m[20]; - TransformNode(const ModuleInstantiation *mi) : AbstractNode(mi) { } -#ifdef ENABLE_CGAL - virtual CGAL_Nef_polyhedron render_cgal_nef_polyhedron() const; -#endif - virtual CSGTerm *render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const; - virtual QString dump(QString indent) const; -}; + assert(delim.size() > 0); + + vector<string> strvec; + size_t start = 0, end = 0; + while (end != string::npos) { + end = str.find(delim, start); + // If at end, use length=maxLength. Else use length=end-start. + strvec.push_back(str.substr(start, (end == string::npos) ? string::npos : end - start)); + // If at end, use start=maxSize. Else use start=end+delimiter. + start = ((end > (string::npos - delim.size())) ? string::npos : end + delim.size()); + } + return strvec; +} + +template <class T> static bool from_string(T &t, const string &s) +{ + std::istringstream iss(s); + return !(iss >> t).fail(); +} AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const { TransformNode *node = new TransformNode(inst); for (int i = 0; i < 16; i++) - node->m[i] = i % 5 == 0 ? 1.0 : 0.0; + node->matrix[i] = i % 5 == 0 ? 1.0 : 0.0; for (int i = 16; i < 20; i++) - node->m[i] = -1; + node->matrix[i] = -1; QVector<QString> argnames; QVector<Expression*> argexpr; - if (type == SCALE) { + switch (this->type) { + case SCALE: argnames = QVector<QString>() << "v"; - } - if (type == ROTATE) { + break; + case ROTATE: argnames = QVector<QString>() << "a" << "v"; - } - if (type == MIRROR) { + break; + case MIRROR: argnames = QVector<QString>() << "v"; - } - if (type == TRANSLATE) { + break; + case TRANSLATE: argnames = QVector<QString>() << "v"; - } - if (type == MULTMATRIX) { + break; + case MULTMATRIX: argnames = QVector<QString>() << "m"; - } - if (type == COLOR) { + break; + case COLOR: argnames = QVector<QString>() << "c" << "alpha"; + break; + default: + assert(false); } Context c(ctx); c.args(argnames, argexpr, inst->argnames, inst->argvalues); - if (type == SCALE) + if (this->type == SCALE) { Value v = c.lookup_variable("v"); - v.getnum(node->m[0]); - v.getnum(node->m[5]); - v.getnum(node->m[10]); - v.getv3(node->m[0], node->m[5], node->m[10]); - if (node->m[10] <= 0) - node->m[10] = 1; + v.getnum(node->matrix[0]); + v.getnum(node->matrix[5]); + v.getnum(node->matrix[10]); + v.getv3(node->matrix[0], node->matrix[5], node->matrix[10]); + if (node->matrix[10] <= 0) + node->matrix[10] = 1; } - if (type == ROTATE) + else if (this->type == ROTATE) { Value val_a = c.lookup_variable("a"); if (val_a.type == Value::VECTOR) @@ -139,10 +160,10 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti { m[x+y*4] = 0; for (int i = 0; i < 4; i++) - m[x+y*4] += node->m[i+y*4] * mr[x+i*4]; + m[x+y*4] += node->matrix[i+y*4] * mr[x+i*4]; } for (int i = 0; i < 16; i++) - node->m[i] = m[i]; + node->matrix[i] = m[i]; } } else @@ -164,21 +185,21 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti double c = cos(a*M_PI/180.0); double s = sin(a*M_PI/180.0); - node->m[ 0] = x*x*(1-c)+c; - node->m[ 1] = y*x*(1-c)+z*s; - node->m[ 2] = z*x*(1-c)-y*s; + node->matrix[ 0] = x*x*(1-c)+c; + node->matrix[ 1] = y*x*(1-c)+z*s; + node->matrix[ 2] = z*x*(1-c)-y*s; - node->m[ 4] = x*y*(1-c)-z*s; - node->m[ 5] = y*y*(1-c)+c; - node->m[ 6] = z*y*(1-c)+x*s; + node->matrix[ 4] = x*y*(1-c)-z*s; + node->matrix[ 5] = y*y*(1-c)+c; + node->matrix[ 6] = z*y*(1-c)+x*s; - node->m[ 8] = x*z*(1-c)+y*s; - node->m[ 9] = y*z*(1-c)-x*s; - node->m[10] = z*z*(1-c)+c; + node->matrix[ 8] = x*z*(1-c)+y*s; + node->matrix[ 9] = y*z*(1-c)-x*s; + node->matrix[10] = z*z*(1-c)+c; } } } - if (type == MIRROR) + else if (this->type == MIRROR) { Value val_v = c.lookup_variable("v"); double x = 1, y = 0, z = 0; @@ -192,59 +213,62 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti if (x != 0.0 || y != 0.0 || z != 0.0) { - node->m[ 0] = 1-2*x*x; - node->m[ 1] = -2*y*x; - node->m[ 2] = -2*z*x; + node->matrix[ 0] = 1-2*x*x; + node->matrix[ 1] = -2*y*x; + node->matrix[ 2] = -2*z*x; - node->m[ 4] = -2*x*y; - node->m[ 5] = 1-2*y*y; - node->m[ 6] = -2*z*y; + node->matrix[ 4] = -2*x*y; + node->matrix[ 5] = 1-2*y*y; + node->matrix[ 6] = -2*z*y; - node->m[ 8] = -2*x*z; - node->m[ 9] = -2*y*z; - node->m[10] = 1-2*z*z; + node->matrix[ 8] = -2*x*z; + node->matrix[ 9] = -2*y*z; + node->matrix[10] = 1-2*z*z; } } - if (type == TRANSLATE) + else if (this->type == TRANSLATE) { Value v = c.lookup_variable("v"); - v.getv3(node->m[12], node->m[13], node->m[14]); + v.getv3(node->matrix[12], node->matrix[13], node->matrix[14]); } - if (type == MULTMATRIX) + else if (this->type == MULTMATRIX) { Value v = c.lookup_variable("m"); if (v.type == Value::VECTOR) { for (int i = 0; i < 16; i++) { int x = i / 4, y = i % 4; if (y < v.vec.size() && v.vec[y]->type == Value::VECTOR && x < v.vec[y]->vec.size()) - v.vec[y]->vec[x]->getnum(node->m[i]); + v.vec[y]->vec[x]->getnum(node->matrix[i]); } } } - if (type == COLOR) + else if (this->type == COLOR) { Value v = c.lookup_variable("c"); if (v.type == Value::VECTOR) { for (int i = 0; i < 4; i++) - node->m[16+i] = i < v.vec.size() ? v.vec[i]->num : 1.0; + node->matrix[16+i] = i < v.vec.size() ? v.vec[i]->num : 1.0; +// FIXME: Port to non-Qt +#if 0 } else if (v.type == Value::STRING) { QString colorname = v.text; QColor color; color.setNamedColor(colorname); if (color.isValid()) { - node->m[16+0] = color.redF(); - node->m[16+1] = color.greenF(); - node->m[16+2] = color.blueF(); + node->matrix[16+0] = color.redF(); + node->matrix[16+1] = color.greenF(); + node->matrix[16+2] = color.blueF(); } else { PRINTF_NOCACHE("WARNING: Color name \"%s\" unknown. Please see",v.text.toUtf8().data()); PRINTF_NOCACHE("WARNING: http://en.wikipedia.org/wiki/Web_colors"); } +#endif } Value alpha = c.lookup_variable("alpha"); if (alpha.type == Value::NUMBER) { - node->m[16+3] = alpha.num; + node->matrix[16+3] = alpha.num; } else { - node->m[16+3] = 1.0; + node->matrix[16+3] = 1.0; } } @@ -257,134 +281,34 @@ AbstractNode *TransformModule::evaluate(const Context *ctx, const ModuleInstanti return node; } -#ifdef ENABLE_CGAL - -CGAL_Nef_polyhedron TransformNode::render_cgal_nef_polyhedron() const +string TransformNode::toString() const { - QString cache_id = mk_cache_id(); - if (cgal_nef_cache.contains(cache_id)) { - progress_report(); - PRINT(cgal_nef_cache[cache_id]->msg); - return cgal_nef_cache[cache_id]->N; - } - - print_messages_push(); - - bool first = true; - CGAL_Nef_polyhedron N; + std::stringstream stream; - foreach (AbstractNode *v, children) { - if (v->modinst->tag_background) - continue; - if (first) { - N = v->render_cgal_nef_polyhedron(); - if (N.dim != 0) - first = false; - } else if (N.dim == 2) { - N.p2 += v->render_cgal_nef_polyhedron().p2; - } else if (N.dim == 3) { - N.p3 += v->render_cgal_nef_polyhedron().p3; - } - v->progress_report(); + if (this->matrix[16] >= 0 || this->matrix[17] >= 0 || this->matrix[18] >= 0 || this->matrix[19] >= 0) { + stream << "color([" << this->matrix[16] << ", " << this->matrix[17] << ", " << this->matrix[18] << ", " << this->matrix[19] << "])"; } - - if (N.dim == 2) - { - // Unfortunately CGAL provides no transform method for CGAL_Nef_polyhedron2 - // 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( - m[0], m[4], m[12], - m[1], m[5], m[13], m[15]); - - DxfData dd(N); - for (int i=0; i < dd.points.size(); i++) { - CGAL_Kernel2::Point_2 p = CGAL_Kernel2::Point_2(dd.points[i].x, dd.points[i].y); - p = t.transform(p); - dd.points[i].x = to_double(p.x()); - dd.points[i].y = to_double(p.y()); + else { + stream << "multmatrix(["; + for (int j=0;j<4;j++) { + stream << "["; + for (int i=0;i<4;i++) { + // FIXME: The 0 test is to avoid a leading minus before a single 0 (cosmetics) + stream << ((this->matrix[i*4+j]==0)?0:this->matrix[i*4+j]); + if (i != 3) stream << ", "; + } + stream << "]"; + if (j != 3) stream << ", "; } - - PolySet ps; - ps.is2d = true; - dxf_tesselate(&ps, &dd, 0, true, false, 0); - - N = ps.render_cgal_nef_polyhedron(); - ps.refcount = 0; - } - if (N.dim == 3) { - CGAL_Aff_transformation t( - m[0], m[4], m[ 8], m[12], - m[1], m[5], m[ 9], m[13], - m[2], m[6], m[10], m[14], m[15]); - N.p3.transform(t); - } - - cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight()); - print_messages_pop(); - progress_report(); - - return N; -} - -#endif /* ENABLE_CGAL */ - -CSGTerm *TransformNode::render_csg_term(double c[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const -{ - double x[20]; - - for (int i = 0; i < 16; i++) - { - int c_row = i%4; - int m_col = i/4; - x[i] = 0; - for (int j = 0; j < 4; j++) - x[i] += c[c_row + j*4] * m[m_col*4 + j]; + stream << "])"; } - for (int i = 16; i < 20; i++) - x[i] = m[i] < 0 ? c[i] : m[i]; - - CSGTerm *t1 = NULL; - foreach(AbstractNode *v, children) - { - CSGTerm *t2 = v->render_csg_term(x, highlights, background); - if (t2 && !t1) { - t1 = t2; - } else if (t2 && t1) { - t1 = new CSGTerm(CSGTerm::TYPE_UNION, t1, t2); - } - } - if (t1 && modinst->tag_highlight && highlights) - highlights->append(t1->link()); - if (t1 && modinst->tag_background && background) { - background->append(t1); - return NULL; - } - return t1; + return stream.str(); } -QString TransformNode::dump(QString indent) const +string TransformNode::name() const { - if (dump_cache.isEmpty()) { - QString text; - if (m[16] >= 0 || m[17] >= 0 || m[18] >= 0 || m[19] >= 0) - text.sprintf("n%d: color([%g, %g, %g, %g])", idx, - m[16], m[17], m[18], m[19]); - else - text.sprintf("n%d: multmatrix([[%g, %g, %g, %g], [%g, %g, %g, %g], " - "[%g, %g, %g, %g], [%g, %g, %g, %g]])", idx, - m[0], m[4], m[ 8], m[12], - m[1], m[5], m[ 9], m[13], - m[2], m[6], m[10], m[14], - m[3], m[7], m[11], m[15]); - text = indent + text + " {\n"; - foreach (AbstractNode *v, children) - text += v->dump(indent + QString("\t")); - ((AbstractNode*)this)->dump_cache = text + indent + "}\n"; - } - return dump_cache; + return "transform"; } void register_builtin_transform() @@ -396,4 +320,3 @@ void register_builtin_transform() builtin_modules["multmatrix"] = new TransformModule(MULTMATRIX); builtin_modules["color"] = new TransformModule(COLOR); } - diff --git a/src/transformnode.h b/src/transformnode.h new file mode 100644 index 0000000..9afa9be --- /dev/null +++ b/src/transformnode.h @@ -0,0 +1,20 @@ +#ifndef TRANSFORMNODE_H_ +#define TRANSFORMNODE_H_ + +#include "node.h" +#include "visitor.h" + +class TransformNode : public AbstractNode +{ +public: + TransformNode(const ModuleInstantiation *mi) : AbstractNode(mi) { } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const; + + double matrix[20]; +}; + +#endif diff --git a/src/traverser.cc b/src/traverser.cc new file mode 100644 index 0000000..27bb116 --- /dev/null +++ b/src/traverser.cc @@ -0,0 +1,40 @@ +#include "traverser.h" +#include "visitor.h" +#include "node.h" +#include "state.h" + +void Traverser::execute() +{ + State state(NULL); + traverse(this->root, state); +} + +void Traverser::traverse(const AbstractNode &node, const State &state) +{ + // FIXME: Handle abort + + State newstate = state; + newstate.setNumChildren(node.getChildren().size()); + + if (traversaltype == PREFIX || traversaltype == PRE_AND_POSTFIX) { + newstate.setPrefix(true); + newstate.setParent(state.parent()); + node.accept(newstate, this->visitor); + } + + newstate.setParent(&node); + const std::list<AbstractNode*> &children = node.getChildren(); + for (std::list<AbstractNode*>::const_iterator iter = children.begin(); + iter != children.end(); + iter++) { + + traverse(**iter, newstate); + } + + if (traversaltype == POSTFIX || traversaltype == PRE_AND_POSTFIX) { + newstate.setParent(state.parent()); + newstate.setPrefix(false); + newstate.setPostfix(true); + node.accept(newstate, this->visitor); + } +} diff --git a/src/traverser.h b/src/traverser.h new file mode 100644 index 0000000..a96b05b --- /dev/null +++ b/src/traverser.h @@ -0,0 +1,26 @@ +#ifndef TRAVERSER_H_ +#define TRAVERSER_H_ + +enum Response {ContinueTraversal, AbortTraversal, PruneTraversal}; + +class Traverser +{ +public: + enum TraversalType {PREFIX, POSTFIX, PRE_AND_POSTFIX}; + + Traverser(class Visitor &visitor, const class AbstractNode &root, TraversalType travtype) + : visitor(visitor), root(root), traversaltype(travtype) { + } + virtual ~Traverser() { } + + void execute(); +private: + // FIXME: reverse parameters + void traverse(const AbstractNode &node, const class State &state); + + Visitor &visitor; + const AbstractNode &root; + TraversalType traversaltype; +}; + +#endif diff --git a/src/value.cc b/src/value.cc index b0a79a4..139bd1c 100644 --- a/src/value.cc +++ b/src/value.cc @@ -26,6 +26,8 @@ #include "value.h" #include "mathc99.h" +#include <assert.h> +#include <sstream> Value::Value() { @@ -34,8 +36,7 @@ Value::Value() Value::~Value() { - for (int i = 0; i < this->vec.size(); i++) - delete this->vec[i]; + for (int i = 0; i < this->vec.size(); i++) delete this->vec[i]; this->vec.clear(); } @@ -53,7 +54,7 @@ Value::Value(double v) this->num = v; } -Value::Value(const QString &t) +Value::Value(const std::string &t) { reset_undef(); this->type = STRING; @@ -71,8 +72,9 @@ Value& Value::operator = (const Value &v) this->type = v.type; this->b = v.b; this->num = v.num; - for (int i = 0; i < v.vec.size(); i++) - this->vec.append(new Value(*v.vec[i])); + for (int i = 0; i < v.vec.size(); i++) { + this->vec.push_back(new Value(*v.vec[i])); + } this->range_begin = v.range_begin; this->range_step = v.range_step; this->range_end = v.range_end; @@ -110,7 +112,7 @@ Value Value::operator + (const Value &v) const Value r; r.type = VECTOR; for (int i = 0; i < this->vec.size() && i < v.vec.size(); i++) - r.vec.append(new Value(*this->vec[i] + *v.vec[i])); + r.vec.push_back(new Value(*this->vec[i] + *v.vec[i])); return r; } if (this->type == NUMBER && v.type == NUMBER) { @@ -125,7 +127,7 @@ Value Value::operator - (const Value &v) const Value r; r.type = VECTOR; for (int i = 0; i < this->vec.size() && i < v.vec.size(); i++) - r.vec.append(new Value(*this->vec[i] - *v.vec[i])); + r.vec.push_back(new Value(*this->vec[i] - *v.vec[i])); return r; } if (this->type == NUMBER && v.type == NUMBER) { @@ -140,14 +142,14 @@ Value Value::operator * (const Value &v) const Value r; r.type = VECTOR; for (int i = 0; i < this->vec.size(); i++) - r.vec.append(new Value(*this->vec[i] * v)); + r.vec.push_back(new Value(*this->vec[i] * v)); return r; } if (this->type == NUMBER && v.type == VECTOR) { Value r; r.type = VECTOR; for (int i = 0; i < v.vec.size(); i++) - r.vec.append(new Value(*this * *v.vec[i])); + r.vec.push_back(new Value(*this * *v.vec[i])); return r; } if (this->type == NUMBER && v.type == NUMBER) { @@ -162,14 +164,14 @@ Value Value::operator / (const Value &v) const Value r; r.type = VECTOR; for (int i = 0; i < this->vec.size(); i++) - r.vec.append(new Value(*this->vec[i] / v)); + r.vec.push_back(new Value(*this->vec[i] / v)); return r; } if (this->type == NUMBER && v.type == VECTOR) { Value r; r.type = VECTOR; for (int i = 0; i < v.vec.size(); i++) - r.vec.append(new Value(v / *v.vec[i])); + r.vec.push_back(new Value(v / *v.vec[i])); return r; } if (this->type == NUMBER && v.type == NUMBER) { @@ -255,7 +257,7 @@ Value Value::inv() const Value r; r.type = VECTOR; for (int i = 0; i < this->vec.size(); i++) - r.vec.append(new Value(this->vec[i]->inv())); + r.vec.push_back(new Value(this->vec[i]->inv())); return r; } if (this->type == NUMBER) @@ -307,46 +309,77 @@ bool Value::getv3(double &x, double &y, double &z) const return true; } -QString Value::dump() const -{ - if (this->type == STRING) { - return QString("\"") + this->text + QString("\""); - } - if (this->type == VECTOR) { - QString text = "["; - for (int i = 0; i < this->vec.size(); i++) { - if (i > 0) - text += ", "; - text += this->vec[i]->dump(); - } - return text + "]"; - } - if (this->type == RANGE) { - QString text; - text.sprintf("[ %g : %g : %g ]", this->range_begin, this->range_step, this->range_end); - return text; - } - if (this->type == NUMBER) { - QString text; - text.sprintf("%g", this->num); - return text; - } - if (this->type == BOOL) { - return QString(this->b ? "true" : "false"); - } - return QString("undef"); -} - void Value::reset_undef() { this->type = UNDEFINED; this->b = false; this->num = 0; - for (int i = 0; i < this->vec.size(); i++) - delete this->vec[i]; + for (int i = 0; i < this->vec.size(); i++) delete this->vec[i]; this->vec.clear(); this->range_begin = 0; this->range_step = 0; this->range_end = 0; - this->text = QString(); + this->text = ""; } + +std::string Value::toString() const +{ + std::stringstream stream; + stream.precision(16); + + switch (this->type) { + case STRING: + stream << '"' << this->text << '"'; + break; + case VECTOR: + stream << '['; + for (int i = 0; i < this->vec.size(); i++) { + if (i > 0) stream << ", "; + stream << *(this->vec[i]); + } + stream << ']'; + break; + case RANGE: + stream << "[ " + << this->range_begin + << " : " + << this->range_step + << " : " + << this->range_end + << " ]"; + break; + case NUMBER: + stream << this->num; + break; + case BOOL: + stream << this->b; + break; + default: + stream << "undef"; + } + + return stream.str(); +} + +/*! + Append a value to this vector. + This must be of type VECTOR. +*/ +void Value::append(Value *val) +{ + assert(this->type == VECTOR); + this->vec.push_back(val); +} + +std::ostream &operator<<(std::ostream &stream, const Value &value) +{ + stream << value.toString(); + return stream; +} + +std::ostream &operator<<(std::ostream &stream, const QString &str) +{ + stream << str.toStdString(); + return stream; +} + diff --git a/src/value.h b/src/value.h index 3491cbb..9140912 100644 --- a/src/value.h +++ b/src/value.h @@ -1,8 +1,8 @@ #ifndef VALUE_H_ #define VALUE_H_ -#include <QVector> -#include <QString> +#include <vector> +#include <string> class Value { @@ -20,18 +20,18 @@ public: bool b; double num; - QVector<Value*> vec; + std::vector<Value*> vec; double range_begin; double range_step; double range_end; - QString text; + std::string text; Value(); ~Value(); Value(bool v); Value(double v); - Value(const QString &t); + Value(const std::string &t); Value(const Value &v); Value& operator = (const Value &v); @@ -59,10 +59,18 @@ public: bool getv2(double &x, double &y) const; bool getv3(double &x, double &y, double &z) const; - QString dump() const; + std::string toString() const; + + void append(Value *val); private: void reset_undef(); }; +std::ostream &operator<<(std::ostream &stream, const Value &value); + +// FIXME: Doesn't belong here.. +#include <QString> +std::ostream &operator<<(std::ostream &stream, const QString &str); + #endif diff --git a/src/visitor.h b/src/visitor.h new file mode 100644 index 0000000..63f5d08 --- /dev/null +++ b/src/visitor.h @@ -0,0 +1,52 @@ +#ifndef VISITOR_H_ +#define VISITOR_H_ + +#include "traverser.h" + +class Visitor +{ +public: + Visitor() {} + virtual ~Visitor() {} + + virtual Response visit(class State &state, const class AbstractNode &node) = 0; + virtual Response visit(class State &state, const class AbstractIntersectionNode &node) { + return visit(state, (const class AbstractNode &)node); + } + virtual Response visit(class State &state, const class AbstractPolyNode &node) { + return visit(state, (const class AbstractNode &)node); + } + virtual Response visit(class State &state, const class CgaladvNode &node) { + return visit(state, (const class AbstractNode &)node); + } + virtual Response visit(class State &state, const class CsgNode &node) { + return visit(state, (const class AbstractNode &)node); + } + virtual Response visit(class State &state, const class DxfLinearExtrudeNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class DxfRotateExtrudeNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class ImportNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class PrimitiveNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class ProjectionNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class RenderNode &node) { + return visit(state, (const class AbstractNode &)node); + } + virtual Response visit(class State &state, const class SurfaceNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } + virtual Response visit(class State &state, const class TransformNode &node) { + return visit(state, (const class AbstractNode &)node); + } + // Add visit() methods for new visitable subtypes of AbstractNode here +}; + +#endif diff --git a/test-code/batch-cgal-tests.sh b/test-code/batch-cgal-tests.sh deleted file mode 100755 index 12b8fe1..0000000 --- a/test-code/batch-cgal-tests.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -cmd="./cgaltest" - -for f in ../testdata/scad/*-tests.scad; do - echo == `basename $f .scad` == - "$cmd" "$f" -done - diff --git a/test-code/batch-cgal.sh b/test-code/batch-cgal.sh deleted file mode 100755 index 031020d..0000000 --- a/test-code/batch-cgal.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -cmd="./cgaltest" - -if [ $# == 0 ]; then - dir=../testdata/scad -else - dir=$1 -fi - -for f in $dir/*.scad; do - echo == `basename $f` == - "$cmd" "$f" -done diff --git a/test-code/batch-dump.sh b/test-code/batch-dump.sh deleted file mode 100755 index e8c9cd5..0000000 --- a/test-code/batch-dump.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -cmd="./dumptest" - -if [ $# == 0 ]; then - dir=../testdata/scad -else - dir=$1 -fi - -echo $dir; - -for f in $dir/*.scad; do - echo `basename $f` - "$cmd" "$f" -done diff --git a/test-code/exportdxf.cc b/test-code/exportdxf.cc new file mode 100644 index 0000000..97d6f49 --- /dev/null +++ b/test-code/exportdxf.cc @@ -0,0 +1,182 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "myqhash.h" +#include "openscad.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "export.h" +#include "builtin.h" +#include "Tree.h" +#include "CGALRenderer.h" +#include "PolySetCGALRenderer.h" + +#include <QApplication> +#include <QFile> +#include <QDir> +#include <QSet> +#include <QTextStream> +#include <getopt.h> +#include <iostream> + +QString commandline_commands; +const char *make_command = NULL; +QSet<QString> dependencies; +QString currentdir; +QString examplesdir; +QString librarydir; + +using std::string; + +void handle_dep(QString filename) +{ + if (filename.startsWith("/")) + dependencies.insert(filename); + else + dependencies.insert(QDir::currentPath() + QString("/") + filename); + if (!QFile(filename).exists() && make_command) { + char buffer[4096]; + snprintf(buffer, 4096, "%s '%s'", make_command, filename.replace("'", "'\\''").toUtf8().data()); + system(buffer); // FIXME: Handle error + } +} + +// FIXME: enforce some maximum cache size (old version had 100K vertices as limit) +QHash<std::string, CGAL_Nef_polyhedron> cache; + +void cgalTree(Tree &tree) +{ + assert(tree.root()); + + CGALRenderer renderer(cache, tree); + Traverser render(renderer, *tree.root(), Traverser::PRE_AND_POSTFIX); + render.execute(); +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <file.scad>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + int rc = 0; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv, false); + QDir original_path = QDir::current(); + + currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.vec.append(new Value(0.0)); + zero3.vec.append(new Value(0.0)); + zero3.vec.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + QString text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text += buffer; + } + fclose(fp); + root_module = parse((text+commandline_commands).toAscii().data(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + Tree tree; + tree.setRoot(root_node); + + cgalTree(tree); + + CGAL_Nef_polyhedron N = cache[tree.getString(*root_node)]; + + QDir::setCurrent(original_path.absolutePath()); + QTextStream outstream(stdout); + export_dxf(&N, outstream, NULL); + + PolySetRenderer::setRenderer(NULL); + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return rc; +} diff --git a/test-code/dumptest.pro b/test-code/exportdxf.pro index a6729f7..62c2734 100644 --- a/test-code/dumptest.pro +++ b/test-code/exportdxf.pro @@ -1,38 +1,30 @@ DEFINES += OPENSCAD_VERSION=test TEMPLATE = app -OBJECTS_DIR = objects -MOC_DIR = objects -UI_DIR = objects -RCC_DIR = objects +OBJECTS_DIR = cgal-objects +MOC_DIR = cgal-objects +UI_DIR = cgal-objects +RCC_DIR = cgal-objects INCLUDEPATH += ../src -TARGET = dumptest macx { + macx { + DEPLOYDIR = $$(MACOSX_DEPLOY_DIR) + !isEmpty(DEPLOYDIR) { + INCLUDEPATH += $$DEPLOYDIR/include + LIBS += -L$$DEPLOYDIR/lib + } + } CONFIG -= app_bundle LIBS += -framework Carbon } CONFIG += qt QT += opengl +CONFIG += cgal -# Optionally specify location of Eigen2 using the -# EIGEN2DIR env. variable -EIGEN2_DIR = $$(EIGEN2DIR) -!isEmpty(EIGEN2_DIR) { - INCLUDEPATH += $$EIGEN2_DIR -} -else { - macx { - INCLUDEPATH += /opt/local/include/eigen2 - } - else { - INCLUDEPATH += /usr/include/eigen2 - } -} -FORMS += ../src/Preferences.ui -HEADERS += ../src/Preferences.h -SOURCES += ../src/Preferences.cc +include(../cgal.pri) +include(../eigen2.pri) LEXSOURCES += ../src/lexer.l YACCSOURCES += ../src/parser.y @@ -50,13 +42,32 @@ HEADERS += ../src/builtin.h \ ../src/grid.h \ ../src/module.h \ ../src/node.h \ + ../src/dxflinextrudenode.h \ + ../src/dxfrotextrudenode.h \ + ../src/projectionnode.h \ + ../src/importnode.h \ + ../src/csgnode.h \ + ../src/transformnode.h \ + ../src/rendernode.h \ ../src/openscad.h \ ../src/polyset.h \ ../src/printutils.h \ ../src/value.h \ - ../src/progress.h + ../src/progress.h \ + ../src/traverser.h \ + ../src/csgnode.h \ + ../src/visitor.h \ + ../src/nodedumper.h \ + ../src/CGALRenderer.h \ + ../src/nodecache.h \ + ../src/importnode.h \ + ../src/state.h \ + ../src/PolySetRenderer.h \ + ../src/PolySetCGALRenderer.h \ + ../src/myqhash.h \ + ../src/Tree.h -SOURCES += dumptest.cc \ +SOURCES += exportdxf.cc \ ../src/export.cc \ ../src/value.cc \ ../src/expr.cc \ @@ -71,11 +82,14 @@ SOURCES += dumptest.cc \ ../src/primitives.cc \ ../src/projection.cc \ ../src/cgaladv.cc \ + ../src/cgaladv_minkowski2.cc \ + ../src/cgaladv_minkowski3.cc \ ../src/surface.cc \ ../src/control.cc \ ../src/render.cc \ ../src/import.cc \ ../src/dxfdata.cc \ + ../src/nef2dxf.cc \ ../src/dxftess.cc \ ../src/dxftess-glu.cc \ ../src/dxftess-cgal.cc \ @@ -83,4 +97,11 @@ SOURCES += dumptest.cc \ ../src/dxflinextrude.cc \ ../src/dxfrotextrude.cc \ ../src/printutils.cc \ - ../src/progress.cc + ../src/progress.cc \ + ../src/nodedumper.cc \ + ../src/CGALRenderer.cc \ + ../src/traverser.cc \ + ../src/PolySetRenderer.cc \ + ../src/PolySetCGALRenderer.cc \ + ../src/qhash.cc \ + ../src/Tree.cc diff --git a/testdata/scad/polygon-illegal-winding.scad b/testdata/scad/bugs/polygon-illegal-winding.scad index 612154a..612154a 100644 --- a/testdata/scad/polygon-illegal-winding.scad +++ b/testdata/scad/bugs/polygon-illegal-winding.scad diff --git a/testdata/scad/polyset-reduce-crash.scad b/testdata/scad/bugs/polyset-reduce-crash.scad index ce9fae7..ce9fae7 100644 --- a/testdata/scad/polyset-reduce-crash.scad +++ b/testdata/scad/bugs/polyset-reduce-crash.scad diff --git a/testdata/scad/polyset-reduce-crash.txt b/testdata/scad/bugs/polyset-reduce-crash.txt index bc9e8d5..bc9e8d5 100644 --- a/testdata/scad/polyset-reduce-crash.txt +++ b/testdata/scad/bugs/polyset-reduce-crash.txt diff --git a/testdata/scad/difference-tests.scad b/testdata/scad/features/difference-tests.scad index f945246..f945246 100644 --- a/testdata/scad/difference-tests.scad +++ b/testdata/scad/features/difference-tests.scad diff --git a/testdata/scad/dim-all.dxf b/testdata/scad/features/dim-all.dxf index 6ae7610..6ae7610 100644 --- a/testdata/scad/dim-all.dxf +++ b/testdata/scad/features/dim-all.dxf diff --git a/testdata/scad/dim-all.scad b/testdata/scad/features/dim-all.scad index 454ed11..454ed11 100644 --- a/testdata/scad/dim-all.scad +++ b/testdata/scad/features/dim-all.scad diff --git a/testdata/scad/features/dxf-export.scad b/testdata/scad/features/dxf-export.scad new file mode 100644 index 0000000..7f4b8cb --- /dev/null +++ b/testdata/scad/features/dxf-export.scad @@ -0,0 +1,12 @@ +circle(r=5); + +translate([15,0,0]) square(size=[10,10], center=true); + +translate([30,0,0]) polygon(points=[[-5,-5],[5,-5],[0,5]], paths=[[0,1,2]]); + +translate([0,-15,0]) { + difference() { + circle(r=5); + translate([0,-6,0]) square([12,12], center=true); + } +}
\ No newline at end of file diff --git a/testdata/scad/convex_hull.scad b/testdata/scad/features/hull-tests.scad index 3114ac5..3114ac5 100644 --- a/testdata/scad/convex_hull.scad +++ b/testdata/scad/features/hull-tests.scad diff --git a/testdata/scad/import.stl b/testdata/scad/features/import.stl index c8dc5db..c8dc5db 100644 --- a/testdata/scad/import.stl +++ b/testdata/scad/features/import.stl diff --git a/testdata/scad/import_stl-tests.scad b/testdata/scad/features/import_stl-tests.scad index b634d12..b634d12 100644 --- a/testdata/scad/import_stl-tests.scad +++ b/testdata/scad/features/import_stl-tests.scad diff --git a/testdata/scad/include test6.scad b/testdata/scad/features/include test6.scad index 7a79456..7a79456 100644 --- a/testdata/scad/include test6.scad +++ b/testdata/scad/features/include test6.scad diff --git a/testdata/scad/include-test.scad b/testdata/scad/features/include-test.scad index 5db02d7..5db02d7 100644 --- a/testdata/scad/include-test.scad +++ b/testdata/scad/features/include-test.scad diff --git a/testdata/scad/include-test5.scad b/testdata/scad/features/include-test5.scad index 4f6e656..4f6e656 100644 --- a/testdata/scad/include-test5.scad +++ b/testdata/scad/features/include-test5.scad diff --git a/testdata/scad/intersection-tests.scad b/testdata/scad/features/intersection-tests.scad index 4101b03..4101b03 100644 --- a/testdata/scad/intersection-tests.scad +++ b/testdata/scad/features/intersection-tests.scad diff --git a/testdata/scad/linear_extrude-tests.scad b/testdata/scad/features/linear_extrude-tests.scad index af050fb..af050fb 100644 --- a/testdata/scad/linear_extrude-tests.scad +++ b/testdata/scad/features/linear_extrude-tests.scad diff --git a/testdata/scad/minkowski.scad b/testdata/scad/features/minkowski-tests.scad index 6d0dade..6d0dade 100644 --- a/testdata/scad/minkowski.scad +++ b/testdata/scad/features/minkowski-tests.scad diff --git a/testdata/scad/null-polygons.dxf b/testdata/scad/features/null-polygons.dxf index 390e42e..390e42e 100644 --- a/testdata/scad/null-polygons.dxf +++ b/testdata/scad/features/null-polygons.dxf diff --git a/testdata/scad/features/null-polygons.scad b/testdata/scad/features/null-polygons.scad new file mode 100644 index 0000000..4849c15 --- /dev/null +++ b/testdata/scad/features/null-polygons.scad @@ -0,0 +1,2 @@ +linear_extrude() import_dxf("null-polygons.dxf"); +linear_extrude("null-polygons.dxf"); diff --git a/testdata/scad/projection-tests.scad b/testdata/scad/features/projection-tests.scad index 619aa01..619aa01 100644 --- a/testdata/scad/projection-tests.scad +++ b/testdata/scad/features/projection-tests.scad diff --git a/testdata/scad/rotate_extrude-tests.scad b/testdata/scad/features/rotate_extrude-tests.scad index 7bbcef0..7bbcef0 100644 --- a/testdata/scad/rotate_extrude-tests.scad +++ b/testdata/scad/features/rotate_extrude-tests.scad diff --git a/testdata/scad/sphere-tests.scad b/testdata/scad/features/sphere-tests.scad index f87aa26..f87aa26 100644 --- a/testdata/scad/sphere-tests.scad +++ b/testdata/scad/features/sphere-tests.scad diff --git a/testdata/scad/string-test.scad b/testdata/scad/features/string-test.scad index 5ec4cfb..5ec4cfb 100644 --- a/testdata/scad/string-test.scad +++ b/testdata/scad/features/string-test.scad diff --git a/testdata/scad/sub1/sub2/sub3/include-test4.scad b/testdata/scad/features/sub1/sub2/sub3/include-test4.scad index 1cb7eab..1cb7eab 100644 --- a/testdata/scad/sub1/sub2/sub3/include-test4.scad +++ b/testdata/scad/features/sub1/sub2/sub3/include-test4.scad diff --git a/testdata/scad/sub1/sub2/sub3/sub4/include-test2.scad b/testdata/scad/features/sub1/sub2/sub3/sub4/include-test2.scad index 9f4c963..9f4c963 100644 --- a/testdata/scad/sub1/sub2/sub3/sub4/include-test2.scad +++ b/testdata/scad/features/sub1/sub2/sub3/sub4/include-test2.scad diff --git a/testdata/scad/sub1/sub2/sub3/sub4/include-test3.scad b/testdata/scad/features/sub1/sub2/sub3/sub4/include-test3.scad index 2f67e93..2f67e93 100644 --- a/testdata/scad/sub1/sub2/sub3/sub4/include-test3.scad +++ b/testdata/scad/features/sub1/sub2/sub3/sub4/include-test3.scad diff --git a/testdata/scad/surface-tests.scad b/testdata/scad/features/surface-tests.scad index 32072fa..32072fa 100644 --- a/testdata/scad/surface-tests.scad +++ b/testdata/scad/features/surface-tests.scad diff --git a/testdata/scad/surface.dat b/testdata/scad/features/surface.dat index 21d10af..21d10af 100644 --- a/testdata/scad/surface.dat +++ b/testdata/scad/features/surface.dat diff --git a/testdata/scad/allmodules.scad b/testdata/scad/minimal/allmodules.scad index 9d6635c..a940947 100644 --- a/testdata/scad/allmodules.scad +++ b/testdata/scad/minimal/allmodules.scad @@ -1,6 +1,7 @@ minkowski(); glide(); subdiv(); +hull(); child(); echo(); assign(); diff --git a/testdata/scad/assign.scad b/testdata/scad/minimal/assign.scad index e4dba58..e4dba58 100644 --- a/testdata/scad/assign.scad +++ b/testdata/scad/minimal/assign.scad diff --git a/testdata/scad/child.scad b/testdata/scad/minimal/child.scad index ba69caf..ba69caf 100644 --- a/testdata/scad/child.scad +++ b/testdata/scad/minimal/child.scad diff --git a/testdata/scad/circle.scad b/testdata/scad/minimal/circle.scad index c702f98..c702f98 100644 --- a/testdata/scad/circle.scad +++ b/testdata/scad/minimal/circle.scad diff --git a/testdata/scad/color.scad b/testdata/scad/minimal/color.scad index b0ae89c..b0ae89c 100644 --- a/testdata/scad/color.scad +++ b/testdata/scad/minimal/color.scad diff --git a/testdata/scad/cube.scad b/testdata/scad/minimal/cube.scad index 406bf16..406bf16 100644 --- a/testdata/scad/cube.scad +++ b/testdata/scad/minimal/cube.scad diff --git a/testdata/scad/cylinder.scad b/testdata/scad/minimal/cylinder.scad index 91c2c30..91c2c30 100644 --- a/testdata/scad/cylinder.scad +++ b/testdata/scad/minimal/cylinder.scad diff --git a/testdata/scad/difference.scad b/testdata/scad/minimal/difference.scad index c3fd0fe..c3fd0fe 100644 --- a/testdata/scad/difference.scad +++ b/testdata/scad/minimal/difference.scad diff --git a/testdata/scad/dxf_linear_extrude.scad b/testdata/scad/minimal/dxf_linear_extrude.scad index 06d6f33..06d6f33 100644 --- a/testdata/scad/dxf_linear_extrude.scad +++ b/testdata/scad/minimal/dxf_linear_extrude.scad diff --git a/testdata/scad/dxf_rotate_extrude.scad b/testdata/scad/minimal/dxf_rotate_extrude.scad index dee7f49..dee7f49 100644 --- a/testdata/scad/dxf_rotate_extrude.scad +++ b/testdata/scad/minimal/dxf_rotate_extrude.scad diff --git a/testdata/scad/echo.scad b/testdata/scad/minimal/echo.scad index 34fc70f..34fc70f 100644 --- a/testdata/scad/echo.scad +++ b/testdata/scad/minimal/echo.scad diff --git a/testdata/scad/for.scad b/testdata/scad/minimal/for.scad index 62356c0..62356c0 100644 --- a/testdata/scad/for.scad +++ b/testdata/scad/minimal/for.scad diff --git a/testdata/scad/glide.scad b/testdata/scad/minimal/glide.scad index 9a5f69d..9a5f69d 100644 --- a/testdata/scad/glide.scad +++ b/testdata/scad/minimal/glide.scad diff --git a/testdata/scad/group.scad b/testdata/scad/minimal/group.scad index 0a04719..0a04719 100644 --- a/testdata/scad/group.scad +++ b/testdata/scad/minimal/group.scad diff --git a/testdata/scad/minimal/hull.scad b/testdata/scad/minimal/hull.scad new file mode 100644 index 0000000..096b0b0 --- /dev/null +++ b/testdata/scad/minimal/hull.scad @@ -0,0 +1 @@ +hull(); diff --git a/testdata/scad/if.scad b/testdata/scad/minimal/if.scad index 9e5c706..9e5c706 100644 --- a/testdata/scad/if.scad +++ b/testdata/scad/minimal/if.scad diff --git a/testdata/scad/import_dxf.scad b/testdata/scad/minimal/import_dxf.scad index b8b8fd0..b8b8fd0 100644 --- a/testdata/scad/import_dxf.scad +++ b/testdata/scad/minimal/import_dxf.scad diff --git a/testdata/scad/import_off.scad b/testdata/scad/minimal/import_off.scad index 353597f..353597f 100644 --- a/testdata/scad/import_off.scad +++ b/testdata/scad/minimal/import_off.scad diff --git a/testdata/scad/import_stl.scad b/testdata/scad/minimal/import_stl.scad index 416ca6a..416ca6a 100644 --- a/testdata/scad/import_stl.scad +++ b/testdata/scad/minimal/import_stl.scad diff --git a/testdata/scad/intersection.scad b/testdata/scad/minimal/intersection.scad index 8340c00..8340c00 100644 --- a/testdata/scad/intersection.scad +++ b/testdata/scad/minimal/intersection.scad diff --git a/testdata/scad/intersection_for.scad b/testdata/scad/minimal/intersection_for.scad index 7b08d61..7b08d61 100644 --- a/testdata/scad/intersection_for.scad +++ b/testdata/scad/minimal/intersection_for.scad diff --git a/testdata/scad/linear_extrude.scad b/testdata/scad/minimal/linear_extrude.scad index a621959..a621959 100644 --- a/testdata/scad/linear_extrude.scad +++ b/testdata/scad/minimal/linear_extrude.scad diff --git a/testdata/scad/minimal/minkowski.scad b/testdata/scad/minimal/minkowski.scad new file mode 100644 index 0000000..26cd972 --- /dev/null +++ b/testdata/scad/minimal/minkowski.scad @@ -0,0 +1 @@ +minkowski(); diff --git a/testdata/scad/mirror.scad b/testdata/scad/minimal/mirror.scad index 8d74882..8d74882 100644 --- a/testdata/scad/mirror.scad +++ b/testdata/scad/minimal/mirror.scad diff --git a/testdata/scad/multmatrix.scad b/testdata/scad/minimal/multmatrix.scad index 7477fa2..7477fa2 100644 --- a/testdata/scad/multmatrix.scad +++ b/testdata/scad/minimal/multmatrix.scad diff --git a/testdata/scad/polygon.scad b/testdata/scad/minimal/polygon.scad index 6a1f288..6a1f288 100644 --- a/testdata/scad/polygon.scad +++ b/testdata/scad/minimal/polygon.scad diff --git a/testdata/scad/polyhedron.scad b/testdata/scad/minimal/polyhedron.scad index 941851f..941851f 100644 --- a/testdata/scad/polyhedron.scad +++ b/testdata/scad/minimal/polyhedron.scad diff --git a/testdata/scad/projection.scad b/testdata/scad/minimal/projection.scad index 43ea0a2..43ea0a2 100644 --- a/testdata/scad/projection.scad +++ b/testdata/scad/minimal/projection.scad diff --git a/testdata/scad/render.scad b/testdata/scad/minimal/render.scad index e035a6f..e035a6f 100644 --- a/testdata/scad/render.scad +++ b/testdata/scad/minimal/render.scad diff --git a/testdata/scad/rotate.scad b/testdata/scad/minimal/rotate.scad index e4acc9c..e4acc9c 100644 --- a/testdata/scad/rotate.scad +++ b/testdata/scad/minimal/rotate.scad diff --git a/testdata/scad/rotate_extrude.scad b/testdata/scad/minimal/rotate_extrude.scad index d11484c..d11484c 100644 --- a/testdata/scad/rotate_extrude.scad +++ b/testdata/scad/minimal/rotate_extrude.scad diff --git a/testdata/scad/scale.scad b/testdata/scad/minimal/scale.scad index 5e9baab..5e9baab 100644 --- a/testdata/scad/scale.scad +++ b/testdata/scad/minimal/scale.scad diff --git a/testdata/scad/sphere.scad b/testdata/scad/minimal/sphere.scad index 8e7ddc1..8e7ddc1 100644 --- a/testdata/scad/sphere.scad +++ b/testdata/scad/minimal/sphere.scad diff --git a/testdata/scad/square.scad b/testdata/scad/minimal/square.scad index 5e44b7d..5e44b7d 100644 --- a/testdata/scad/square.scad +++ b/testdata/scad/minimal/square.scad diff --git a/testdata/scad/subdiv.scad b/testdata/scad/minimal/subdiv.scad index 4c1eb74..4c1eb74 100644 --- a/testdata/scad/subdiv.scad +++ b/testdata/scad/minimal/subdiv.scad diff --git a/testdata/scad/surface.scad b/testdata/scad/minimal/surface.scad index c0b213a..c0b213a 100644 --- a/testdata/scad/surface.scad +++ b/testdata/scad/minimal/surface.scad diff --git a/testdata/scad/transform-insert.dxf b/testdata/scad/minimal/transform-insert.dxf index 40064c2..40064c2 100644 --- a/testdata/scad/transform-insert.dxf +++ b/testdata/scad/minimal/transform-insert.dxf diff --git a/testdata/scad/transform-insert.scad b/testdata/scad/minimal/transform-insert.scad index 1237bb2..1237bb2 100644 --- a/testdata/scad/transform-insert.scad +++ b/testdata/scad/minimal/transform-insert.scad diff --git a/testdata/scad/translate.scad b/testdata/scad/minimal/translate.scad index c7c0b0b..c7c0b0b 100644 --- a/testdata/scad/translate.scad +++ b/testdata/scad/minimal/translate.scad diff --git a/testdata/scad/union.scad b/testdata/scad/minimal/union.scad index e3fa0d5..e3fa0d5 100644 --- a/testdata/scad/union.scad +++ b/testdata/scad/minimal/union.scad diff --git a/testdata/scad/null-polygons.scad b/testdata/scad/null-polygons.scad deleted file mode 100644 index d945325..0000000 --- a/testdata/scad/null-polygons.scad +++ /dev/null @@ -1,2 +0,0 @@ -linear_extrude() import_dxf("null-polygons.dxf"); // doen's crash -linear_extrude("null-polygons.dxf"); // crashes diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8d409f8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,193 @@ +cmake_minimum_required(VERSION 2.8) +project(tests) + +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}") + +# Build debug build as default +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +# +# Build test apps +# + +# Mac OS X +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + FIND_LIBRARY(COCOA_LIBRARY Cocoa) +endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + +# Qt4 +find_package(OpenGL) +find_package(Qt4 COMPONENTS QtCore QtGui QtOpenGL REQUIRED) +include(${QT_USE_FILE}) + +# Eigen2 +if (NOT EIGEN2_INCLUDE_DIR) + find_path(EIGEN2_INCLUDE_DIR + Eigen/Core + PATHS ENV EIGEN2DIR /opt/local/include/eigen2 /usr/include/eigen2) + if (NOT EIGEN2_INCLUDE_DIR) + message(FATAL_ERROR "Eigen2 not found") + else() + message(STATUS "Eigen2 found in " ${EIGEN2_INCLUDE_DIR}) + endif() +endif() +include_directories(${EIGEN2_INCLUDE_DIR}) + +# OpenCSG +if (NOT $ENV{OPENCSG_DIR} STREQUAL "") + set(OPENCSG_DIR "$ENV{OPENCSG_DIR}") +elseif (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + set(OPENCSG_DIR "$ENV{MACOSX_DEPLOY_DIR}") +endif() +if (NOT OPENCSG_INCLUDE_DIR) + message(STATUS ${OPENCSG_DIR}) + find_path(OPENCSG_INCLUDE_DIR + opencsg.h + PATHS ${OPENCSG_DIR}/include) + find_library(OPENCSG_LIBRARY + opencsg + PATHS ${OPENCSG_DIR}/lib) + if (NOT OPENCSG_INCLUDE_DIR OR NOT OPENCSG_LIBRARY) + message(FATAL_ERROR "OpenCSG not found") + else() + message(STATUS "OpenCSG found in " ${OPENCSG_LIBRARY}) + endif() +endif() +include_directories(${OPENCSG_INCLUDE_DIR}) + +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + set(GLEW_DIR "$ENV{MACOSX_DEPLOY_DIR}") +endif() +find_package(GLEW REQUIRED) +include_directories(${GLEW_INCLUDE_PATH}) + +# Flex/Bison +find_package(BISON) +find_package(FLEX) +# The COMPILE_FLAGS and forced C++ compiler is just to be compatible with qmake +FLEX_TARGET(OpenSCADlexer ../src/lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp COMPILE_FLAGS "-Plexer") +BISON_TARGET(OpenSCADparser ../src/parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c COMPILE_FLAGS "-p parser") +ADD_FLEX_BISON_DEPENDENCY(OpenSCADlexer OpenSCADparser) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/parser_yacc.c PROPERTIES LANGUAGE "CXX") + + +# Internal includes +include_directories(../src) + +add_definitions(-DOPENSCAD_VERSION=test) + +set(COMMON_SOURCES + ../src/export.cc + ../src/value.cc + ../src/expr.cc + ../src/func.cc + ../src/module.cc + ../src/node.cc + ../src/context.cc + ../src/csgterm.cc + ../src/polyset.cc + ../src/csgops.cc + ../src/transform.cc + ../src/primitives.cc + ../src/projection.cc + ../src/cgaladv.cc + ../src/surface.cc + ../src/control.cc + ../src/render.cc + ../src/import.cc + ../src/dxfdata.cc + ../src/dxftess.cc + ../src/dxfdim.cc + ../src/dxflinextrude.cc + ../src/dxfrotextrude.cc + ../src/printutils.cc + ../src/progress.cc + ../src/nodedumper.cc + ../src/traverser.cc + ../src/PolySetEvaluator.cc + ../src/Tree.cc + ${FLEX_OpenSCADlexer_OUTPUTS} + ${BISON_OpenSCADparser_OUTPUTS}) + +# +# dumptest +# +add_executable(dumptest dumptest.cc ${COMMON_SOURCES}) +target_link_libraries(dumptest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + +# +# csgtexttest +# +add_executable(csgtexttest csgtexttest.cc CSGTextRenderer.cc CSGTextCache.cc ../src/qhash.cc ${COMMON_SOURCES}) +target_link_libraries(csgtexttest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + +# +# csgtermtest +# +add_executable(csgtermtest csgtermtest.cc ../src/CSGTermEvaluator.cc ${COMMON_SOURCES}) +target_link_libraries(csgtermtest ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + +if (NOT $ENV{MACOSX_DEPLOY_DIR} STREQUAL "") + set(CGAL_DIR "$ENV{MACOSX_DEPLOY_DIR}/lib/CGAL") + set(CMAKE_MODULE_PATH "${CGAL_DIR}") +endif() +find_package(CGAL REQUIRED) +include_directories(${CGAL_INCLUDE_DIRS}) + +# +# cgaltest +# +add_executable(cgaltest cgaltest.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc + ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/nef2dxf.cc + ../src/cgaladv_minkowski2.cc ../src/cgaladv_minkowski3.cc ${COMMON_SOURCES}) +set_target_properties(cgaltest PROPERTIES COMPILE_FLAGS "-DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") +target_link_libraries(cgaltest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENGL_LIBRARY}) + +# +# opencsgtest +# +add_executable(opencsgtest opencsgtest.cc OffscreenView.cc OffscreenContext.mm + ../src/opencsgrenderer.cc ../src/throwntogetherrenderer.cc ../src/CSGTermEvaluator.cc ../src/CGALEvaluator.cc + ../src/PolySetCGALEvaluator.cc ../src/qhash.cc ../src/nef2dxf.cc + ../src/cgaladv_minkowski2.cc ../src/cgaladv_minkowski3.cc + ${COMMON_SOURCES}) +set_target_properties(opencsgtest PROPERTIES COMPILE_FLAGS "-DENABLE_OPENCSG -DENABLE_CGAL ${CGAL_CXX_FLAGS_INIT}") +target_link_libraries(opencsgtest ${CGAL_LIBRARY} ${CGAL_3RD_PARTY_LIBRARIES} ${QT_LIBRARIES} ${OPENCSG_LIBRARY} ${GLEW_LIBRARY} ${COCOA_LIBRARY} ${OPENGL_LIBRARY}) + +# +# This functions adds cmd-line tests given files. +# Files are sent as the parameters following TESTSUFFIX +# +function(add_cmdline_test TESTCMD TESTSUFFIX) + get_filename_component(TESTCMD_NAME ${TESTCMD} NAME_WE) + foreach (SCADFILE ${ARGN}) + get_filename_component(TESTNAME ${SCADFILE} NAME_WE) + string(REPLACE " " "_" TESTNAME ${TESTNAME}) # Test names cannot include spaces + add_test("${TESTCMD_NAME}_${TESTNAME}" ${tests_SOURCE_DIR}/test_cmdline_tool.py -s ${TESTSUFFIX} ${CMAKE_BINARY_DIR}/${TESTCMD} "${SCADFILE}") + endforeach() +endfunction() + +enable_testing() + +# Find all scad files +file(GLOB MINIMAL_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/minimal/*.scad) +file(GLOB FEATURES_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/features/*.scad) +file(GLOB BUGS_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/*.scad) + +# Add dumptest tests to CTest +add_cmdline_test(dumptest txt ${MINIMAL_FILES}) +# Add csgtexttest tests to CTest +add_cmdline_test(csgtexttest txt ${SCAD_FILES}) +# Add csgtermtest tests to CTest +add_cmdline_test(csgtermtest txt ${SCAD_FILES}) +# Add cgaltest tests to CTest +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/cube.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/sphere.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/cylinder.scad) +LIST(APPEND CGALTEST_FILES ${CMAKE_SOURCE_DIR}/../examples/example001.scad) +add_cmdline_test(cgaltest stl ${CGALTEST_FILES}) + +# Add dxfexport tests to CTest +#add_cmdline_test(${CMAKE_SOURCE_DIR}/../test-code/exportdxf dxf ${SCAD_FILES}) diff --git a/tests/CSGTextCache.cc b/tests/CSGTextCache.cc new file mode 100644 index 0000000..4234c63 --- /dev/null +++ b/tests/CSGTextCache.cc @@ -0,0 +1,27 @@ +#include "CSGTextCache.h" + +bool CSGTextCache::contains(const AbstractNode &node) const +{ + return this->cache.contains(this->tree.getString(node)); +} + +// We cannot return a reference since the [] operator returns a temporary value +string CSGTextCache::operator[](const AbstractNode &node) const +{ + return this->cache[this->tree.getString(node)]; +} + +void CSGTextCache::insert(const class AbstractNode &node, const string & value) +{ + this->cache.insert(this->tree.getString(node), value); +} + +void CSGTextCache::remove(const class AbstractNode &node) +{ + this->cache.remove(this->tree.getString(node)); +} + +void CSGTextCache::clear() +{ + this->cache.clear(); +} diff --git a/tests/CSGTextCache.h b/tests/CSGTextCache.h new file mode 100644 index 0000000..57a6972 --- /dev/null +++ b/tests/CSGTextCache.h @@ -0,0 +1,27 @@ +#ifndef CSGTEXTCACHE_H_ +#define CSGTEXTCACHE_H_ + +#include "myqhash.h" +#include "Tree.h" +#include <string> + +using std::string; + +class CSGTextCache +{ +public: + CSGTextCache(const Tree &tree) : tree(tree) {} + ~CSGTextCache() {} + + bool contains(const AbstractNode &node) const; + string operator[](const AbstractNode &node) const; + void insert(const class AbstractNode &node, const string & value); + void remove(const class AbstractNode &node); + void clear(); + +private: + QHash<string, string> cache; + const Tree &tree; +}; + +#endif diff --git a/tests/CSGTextRenderer.cc b/tests/CSGTextRenderer.cc new file mode 100644 index 0000000..b55c88f --- /dev/null +++ b/tests/CSGTextRenderer.cc @@ -0,0 +1,185 @@ +#include "CSGTextRenderer.h" + +#include <string> +#include <map> +#include <list> +#include "visitor.h" +#include "state.h" +#include "module.h" // FIXME: Temporarily for ModuleInstantiation + +#include "csgnode.h" +#include "transformnode.h" + +#include <sstream> +#include <iostream> +#include <assert.h> +#include <QRegExp> + +bool CSGTextRenderer::isCached(const AbstractNode &node) +{ + return this->cache.contains(node); +} + +/*! + Modifies target by applying op to target and src: + target = target [op] src + */ +void +CSGTextRenderer::process(string &target, const string &src, CSGTextRenderer::CsgOp op) +{ +// if (target.dim != 2 && target.dim != 3) { +// assert(false && "Dimension of Nef polyhedron must be 2 or 3"); +// } + + switch (op) { + case UNION: + target += "+" + src; + break; + case INTERSECTION: + target += "*" + src; + break; + case DIFFERENCE: + target += "-" + src; + break; + case MINKOWSKI: + target += "M" + src; + break; + } +} + +void CSGTextRenderer::applyToChildren(const AbstractNode &node, CSGTextRenderer::CsgOp op) +{ + std::stringstream stream; + stream << node.name() << node.index(); + string N = stream.str(); + if (this->visitedchildren[node.index()].size() > 0) { + // FIXME: assert that cache contains nodes in code below + bool first = true; + for (ChildList::const_iterator iter = this->visitedchildren[node.index()].begin(); + iter != this->visitedchildren[node.index()].end(); + iter++) { + const AbstractNode *chnode = *iter; + assert(this->cache.contains(*chnode)); + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->tag_background) continue; + if (first) { + N += "(" + this->cache[*chnode]; +// if (N.dim != 0) first = false; // FIXME: when can this happen? + first = false; + } else { + process(N, this->cache[*chnode], op); + } + chnode->progress_report(); + } + N += ")"; + } + this->cache.insert(node, N); +} + +/* + Typical visitor behavior: + o In prefix: Check if we're cached -> prune + o In postfix: Check if we're cached -> don't apply operator to children + o In postfix: addToParent() + */ + +Response CSGTextRenderer::visit(State &state, const AbstractNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) applyToChildren(node, UNION); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTextRenderer::visit(State &state, const AbstractIntersectionNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) applyToChildren(node, INTERSECTION); + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTextRenderer::visit(State &state, const CsgNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + CsgOp op; + switch (node.type) { + case CSG_TYPE_UNION: + op = UNION; + break; + case CSG_TYPE_DIFFERENCE: + op = DIFFERENCE; + break; + case CSG_TYPE_INTERSECTION: + op = INTERSECTION; + break; + } + applyToChildren(node, op); + } + addToParent(state, node); + } + return ContinueTraversal; +} + +Response CSGTextRenderer::visit(State &state, const TransformNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + // First union all children + applyToChildren(node, UNION); + // FIXME: Then apply transform + } + addToParent(state, node); + } + return ContinueTraversal; +} + +// FIXME: RenderNode: Union over children + some magic +// FIXME: CgaladvNode: Iterate over children. Special operation + +// FIXME: Subtypes of AbstractPolyNode: +// ProjectionNode +// DxfLinearExtrudeNode +// DxfRotateExtrudeNode +// (SurfaceNode) +// (PrimitiveNode) +Response CSGTextRenderer::visit(State &state, const AbstractPolyNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + if (!isCached(node)) { + + // FIXME: Manage caching + // FIXME: Will generate one single Nef polyhedron (no csg ops necessary) + + string N = node.name(); + this->cache.insert(node, N); + +// std::cout << "Insert: " << N << "\n"; +// std::cout << "Node: " << cacheid.toStdString() << "\n\n"; + } + addToParent(state, node); + } + + return ContinueTraversal; +} + +/*! + Adds ourself to out parent's list of traversed children. + Call this for _every_ node which affects output during the postfix traversal. +*/ +void CSGTextRenderer::addToParent(const State &state, const AbstractNode &node) +{ + assert(state.isPostfix()); + this->visitedchildren.erase(node.index()); + if (state.parent()) { + this->visitedchildren[state.parent()->index()].push_back(&node); + } +} diff --git a/tests/CSGTextRenderer.h b/tests/CSGTextRenderer.h new file mode 100644 index 0000000..79a8a96 --- /dev/null +++ b/tests/CSGTextRenderer.h @@ -0,0 +1,40 @@ +#ifndef CSGTEXTRENDERER_H_ +#define CSGTEXTRENDERER_H_ + +#include "visitor.h" +#include "CSGTextCache.h" + +#include <map> +#include <list> + +using std::string; +using std::map; +using std::list; + +class CSGTextRenderer : public Visitor +{ +public: + CSGTextRenderer(CSGTextCache &cache) : cache(cache) {} + virtual ~CSGTextRenderer() {} + + virtual Response visit(State &state, const AbstractNode &node); + virtual Response visit(State &state, const AbstractIntersectionNode &node); + virtual Response visit(State &state, const CsgNode &node); + virtual Response visit(State &state, const TransformNode &node); + virtual Response visit(State &state, const AbstractPolyNode &node); + +private: + enum CsgOp {UNION, INTERSECTION, DIFFERENCE, MINKOWSKI}; + void addToParent(const State &state, const AbstractNode &node); + bool isCached(const AbstractNode &node); + void process(string &target, const string &src, CSGTextRenderer::CsgOp op); + void applyToChildren(const AbstractNode &node, CSGTextRenderer::CsgOp op); + + string currindent; + typedef list<const AbstractNode *> ChildList; + map<int, ChildList> visitedchildren; + + CSGTextCache &cache; +}; + +#endif diff --git a/tests/FindGLEW.cmake b/tests/FindGLEW.cmake new file mode 100644 index 0000000..bccb20a --- /dev/null +++ b/tests/FindGLEW.cmake @@ -0,0 +1,44 @@ +# +# Try to find GLEW library and include path. +# Once done this will define +# +# GLEW_FOUND +# GLEW_INCLUDE_PATH +# GLEW_LIBRARY +# + +IF (WIN32) + FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h + $ENV{PROGRAMFILES}/GLEW/include + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/include + DOC "The directory where GL/glew.h resides") + FIND_LIBRARY( GLEW_LIBRARY + NAMES glew GLEW glew32 glew32s + PATHS + $ENV{PROGRAMFILES}/GLEW/lib + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/bin + ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib + DOC "The GLEW library") +ELSE (WIN32) + message(${GLEW_DIR}) + FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h + PATHS + ${GLEW_DIR}/include + NO_DEFAULT_PATH + DOC "The directory where GL/glew.h resides") + FIND_LIBRARY( GLEW_LIBRARY + NAMES GLEW glew + PATHS + ${GLEW_DIR}/lib + NO_DEFAULT_PATH + DOC "The GLEW library") +ENDIF (WIN32) + +IF (GLEW_INCLUDE_PATH) + SET( GLEW_FOUND 1 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") + MESSAGE(STATUS "GLEW found in " ${GLEW_INCLUDE_PATH} " " ${GLEW_LIBRARY}) +ELSE (GLEW_INCLUDE_PATH) + SET( GLEW_FOUND 0 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise") +ENDIF (GLEW_INCLUDE_PATH) + +MARK_AS_ADVANCED( GLEW_FOUND ) diff --git a/tests/OffscreenContext.h b/tests/OffscreenContext.h new file mode 100644 index 0000000..0300bcb --- /dev/null +++ b/tests/OffscreenContext.h @@ -0,0 +1,14 @@ +#ifndef OFFSCREENCONTEXT_H_ +#define OFFSCREENCONTEXT_H_ + +#include <OpenGL/OpenGL.h> +#include <iostream> // for error output + +#define REPORTGLERROR(task) { GLenum tGLErr = glGetError(); if (tGLErr != GL_NO_ERROR) { std::cout << "OpenGL error " << tGLErr << " while " << task << "\n"; } } + +struct OffscreenContext *create_offscreen_context(int w, int h); +void bind_offscreen_context(OffscreenContext *ctx); +bool teardown_offscreen_context(OffscreenContext *ctx); +bool save_framebuffer(OffscreenContext *ctx, const char *filename); + +#endif diff --git a/tests/OffscreenContext.mm b/tests/OffscreenContext.mm new file mode 100644 index 0000000..c8d0df7 --- /dev/null +++ b/tests/OffscreenContext.mm @@ -0,0 +1,204 @@ +#include "OffscreenContext.h" + +#import <OpenGL/OpenGL.h> +#import <OpenGL/glu.h> // for gluCheckExtension +#import <AppKit/AppKit.h> // for NSOpenGL... + +// Simple error reporting macros to help keep the sample code clean +#define REPORT_ERROR_AND_EXIT(desc) { std::cout << desc << "\n"; return false; } +#define NULL_ERROR_EXIT(test, desc) { if (!test) REPORT_ERROR_AND_EXIT(desc); } + +struct OffscreenContext +{ + NSOpenGLContext *openGLContext; + NSAutoreleasePool *pool; + int width; + int height; + GLuint fbo; +}; + + +OffscreenContext *create_offscreen_context(int w, int h) +{ + OffscreenContext *ctx = new OffscreenContext; + ctx->width = w; + ctx->height = h; + + ctx->pool = [NSAutoreleasePool new]; + + /* + * Create an OpenGL context just so that OpenGL calls will work. I'm not + using it for actual rendering. + */ + + NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPFAPixelBuffer, + NSOpenGLPFANoRecovery, + NSOpenGLPFAAccelerated, + NSOpenGLPFADepthSize, 24, + (NSOpenGLPixelFormatAttribute) 0 + }; + NSOpenGLPixelFormat* pixFormat = [[[NSOpenGLPixelFormat + alloc] initWithAttributes:attributes] autorelease]; + // Create the OpenGL context to render with (with color and depth buffers) + ctx->openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixFormat + shareContext:nil]; + NULL_ERROR_EXIT(ctx->openGLContext, "Unable to create NSOpenGLContext"); + + [ctx->openGLContext makeCurrentContext]; + + /* + * Test if framebuffer objects are supported + */ + const GLubyte* strExt = glGetString(GL_EXTENSIONS); + GLboolean fboSupported = gluCheckExtension((const GLubyte*)"GL_EXT_framebuffer_object", strExt); + if (!fboSupported) + REPORT_ERROR_AND_EXIT("Your system does not support framebuffer extension - unable to render scene"); + /* + * Create an FBO + */ + GLuint renderBuffer = 0; + GLuint depthBuffer = 0; + // Depth buffer to use for depth testing - optional if you're not using depth testing + glGenRenderbuffersEXT(1, &depthBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, w, h); + REPORTGLERROR("creating depth render buffer"); + + // Render buffer to use for imaging + glGenRenderbuffersEXT(1, &renderBuffer); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h); + REPORTGLERROR("creating color render buffer"); + ctx->fbo = 0; + glGenFramebuffersEXT(1, &ctx->fbo); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); + REPORTGLERROR("binding framebuffer"); + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, + GL_RENDERBUFFER_EXT, renderBuffer); + REPORTGLERROR("specifying color render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != + GL_FRAMEBUFFER_COMPLETE_EXT) + REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying color render buffer."); + + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, + GL_RENDERBUFFER_EXT, depthBuffer); + REPORTGLERROR("specifying depth render buffer"); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != + GL_FRAMEBUFFER_COMPLETE_EXT) + REPORT_ERROR_AND_EXIT("Problem with OpenGL framebuffer after specifying depth render buffer."); + + return ctx; +} + +bool teardown_offscreen_context(OffscreenContext *ctx) +{ + // "un"bind my FBO + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + /* + * Cleanup + */ + [ctx->openGLContext clearDrawable]; + [ctx->openGLContext release]; + + [ctx->pool release]; + return true; +} + +bool save_framebuffer(OffscreenContext *ctx, const char *filename) +{ + /* + * Extract the resulting rendering as an image + */ + + int samplesPerPixel = 4; // R, G, B and A + int rowBytes = samplesPerPixel * ctx->width; + char* bufferData = (char*)malloc(rowBytes * ctx->height); + if (!bufferData) { + std::cerr << "Unable to allocate buffer for image extraction."; + return 1; + } + glReadPixels(0, 0, ctx->width, ctx->height, GL_BGRA, GL_UNSIGNED_BYTE, + bufferData); + REPORTGLERROR("reading pixels from framebuffer"); + + // Flip it vertically - images read from OpenGL buffers are upside-down + char* flippedBuffer = (char*)malloc(rowBytes * ctx->height); + if (!flippedBuffer) { + std::cout << "Unable to allocate flipped buffer for corrected image."; + return 1; + } + for (int i = 0 ; i < ctx->height ; i++) { + bcopy(bufferData + i * rowBytes, flippedBuffer + (ctx->height - i - 1) * + rowBytes, rowBytes); + } + + /* + * Output the image to a file + */ + CGColorSpaceRef colorSpace = + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipFirst | + kCGBitmapByteOrder32Little; // XRGB Little Endian + int bitsPerComponent = 8; + CGContextRef contextRef = CGBitmapContextCreate(flippedBuffer, + ctx->width, ctx->height, bitsPerComponent, rowBytes, + colorSpace, bitmapInfo); + if (!contextRef) { + std::cerr << "Unable to create CGContextRef."; + return false; + } + + CGImageRef imageRef = CGBitmapContextCreateImage(contextRef); + if (!imageRef) { + std::cerr << "Unable to create CGImageRef."; + return false; + } + Boolean isDirectory = false; + CFStringRef fname = CFStringCreateWithCString(kCFAllocatorDefault, filename, kCFStringEncodingUTF8); + CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + fname, kCFURLPOSIXPathStyle, isDirectory); + if (!fileURL) { + std::cerr << "Unable to create file URL ref."; + return false; + } + CFIndex fileImageIndex = 1; + CFMutableDictionaryRef fileDict = NULL; + CFStringRef fileUTType = kUTTypeJPEG; + // Create an image destination opaque reference for authoring an image file + CGImageDestinationRef imageDest = CGImageDestinationCreateWithURL(fileURL, + fileUTType, + fileImageIndex, + fileDict); + if (!imageDest) { + std::cerr << "Unable to create CGImageDestinationRef."; + return false; + } + CFIndex capacity = 1; + CFMutableDictionaryRef imageProps = + CFDictionaryCreateMutable(kCFAllocatorDefault, + capacity, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CGImageDestinationAddImage(imageDest, imageRef, imageProps); + CGImageDestinationFinalize(imageDest); + + free(flippedBuffer); + free(bufferData); + CFRelease(imageDest); + CFRelease(fileURL); + CFRelease(fname); + CFRelease(imageProps); + CGColorSpaceRelease( colorSpace ); + CGImageRelease(imageRef); + return true; +} + +void bind_offscreen_context(OffscreenContext *ctx) +{ + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ctx->fbo); +} diff --git a/tests/OffscreenView.cc b/tests/OffscreenView.cc new file mode 100644 index 0000000..3740c69 --- /dev/null +++ b/tests/OffscreenView.cc @@ -0,0 +1,232 @@ +#include <GL/glew.h> +#include "OffscreenView.h" +#include <opencsg.h> +#include "Renderer.h" +#include <math.h> + +#define FAR_FAR_AWAY 100000.0 + +OffscreenView::OffscreenView(size_t width, size_t height) + : orthomode(false), showaxes(true), showfaces(true), showedges(false), viewer_distance(500) +{ + for (int i = 0; i < 10; i++) this->shaderinfo[i] = 0; + this->ctx = create_offscreen_context(width, height); + initializeGL(); + resizeGL(width, height); +} + +OffscreenView::~OffscreenView() +{ + teardown_offscreen_context(this->ctx); +} + +void OffscreenView::setRenderer(Renderer* r) +{ + this->renderer = r; +} + +void OffscreenView::initializeGL() +{ + glEnable(GL_DEPTH_TEST); + glDepthRange(-FAR_FAR_AWAY, +FAR_FAR_AWAY); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + GLfloat light_diffuse[] = {1.0, 1.0, 1.0, 1.0}; + GLfloat light_position0[] = {-1.0, -1.0, +1.0, 0.0}; + GLfloat light_position1[] = {+1.0, +1.0, -1.0, 0.0}; + + glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT0, GL_POSITION, light_position0); + glEnable(GL_LIGHT0); + glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse); + glLightfv(GL_LIGHT1, GL_POSITION, light_position1); + glEnable(GL_LIGHT1); + glEnable(GL_LIGHTING); + glEnable(GL_NORMALIZE); + + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); + +#ifdef ENABLE_OPENCSG + const char *openscad_disable_gl20_env = getenv("OPENSCAD_DISABLE_GL20"); + if (openscad_disable_gl20_env && !strcmp(openscad_disable_gl20_env, "0")) + openscad_disable_gl20_env = NULL; + if (glewIsSupported("GL_VERSION_2_0") && openscad_disable_gl20_env == NULL) + { + const char *vs_source = + "uniform float xscale, yscale;\n" + "attribute vec3 pos_b, pos_c;\n" + "attribute vec3 trig, mask;\n" + "varying vec3 tp, tr;\n" + "varying float shading;\n" + "void main() {\n" + " vec4 p0 = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + " vec4 p1 = gl_ModelViewProjectionMatrix * vec4(pos_b, 1.0);\n" + " vec4 p2 = gl_ModelViewProjectionMatrix * vec4(pos_c, 1.0);\n" + " float a = distance(vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" + " float b = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p1.x/p1.w, yscale*p1.y/p1.w));\n" + " float c = distance(vec2(xscale*p0.x/p0.w, yscale*p0.y/p0.w), vec2(xscale*p2.x/p2.w, yscale*p2.y/p2.w));\n" + " float s = (a + b + c) / 2.0;\n" + " float A = sqrt(s*(s-a)*(s-b)*(s-c));\n" + " float ha = 2.0*A/a;\n" + " gl_Position = p0;\n" + " tp = mask * ha;\n" + " tr = trig;\n" + " vec3 normal, lightDir;\n" + " normal = normalize(gl_NormalMatrix * gl_Normal);\n" + " lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " shading = abs(dot(normal, lightDir));\n" + "}\n"; + + const char *fs_source = + "uniform vec4 color1, color2;\n" + "varying vec3 tp, tr, tmp;\n" + "varying float shading;\n" + "void main() {\n" + " gl_FragColor = vec4(color1.r * shading, color1.g * shading, color1.b * shading, color1.a);\n" + " if (tp.x < tr.x || tp.y < tr.y || tp.z < tr.z)\n" + " gl_FragColor = color2;\n" + "}\n"; + + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, (const GLchar**)&vs_source, NULL); + glCompileShader(vs); + + GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, (const GLchar**)&fs_source, NULL); + glCompileShader(fs); + + GLuint edgeshader_prog = glCreateProgram(); + glAttachShader(edgeshader_prog, vs); + glAttachShader(edgeshader_prog, fs); + glLinkProgram(edgeshader_prog); + + shaderinfo[0] = edgeshader_prog; + shaderinfo[1] = glGetUniformLocation(edgeshader_prog, "color1"); + shaderinfo[2] = glGetUniformLocation(edgeshader_prog, "color2"); + shaderinfo[3] = glGetAttribLocation(edgeshader_prog, "trig"); + shaderinfo[4] = glGetAttribLocation(edgeshader_prog, "pos_b"); + shaderinfo[5] = glGetAttribLocation(edgeshader_prog, "pos_c"); + shaderinfo[6] = glGetAttribLocation(edgeshader_prog, "mask"); + shaderinfo[7] = glGetUniformLocation(edgeshader_prog, "xscale"); + shaderinfo[8] = glGetUniformLocation(edgeshader_prog, "yscale"); + + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + fprintf(stderr, "OpenGL Error: %s\n", gluErrorString(err)); + } + + GLint status; + glGetProgramiv(edgeshader_prog, GL_LINK_STATUS, &status); + if (status == GL_FALSE) { + int loglen; + char logbuffer[1000]; + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + fprintf(stderr, "OpenGL Program Linker Error:\n%.*s", loglen, logbuffer); + } else { + int loglen; + char logbuffer[1000]; + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + if (loglen > 0) { + fprintf(stderr, "OpenGL Program Link OK:\n%.*s", loglen, logbuffer); + } + glValidateProgram(edgeshader_prog); + glGetProgramInfoLog(edgeshader_prog, sizeof(logbuffer), &loglen, logbuffer); + if (loglen > 0) { + fprintf(stderr, "OpenGL Program Validation results:\n%.*s", loglen, logbuffer); + } + } + } +#endif /* ENABLE_OPENCSG */ +} + +void OffscreenView::resizeGL(int w, int h) +{ +#ifdef ENABLE_OPENCSG + shaderinfo[9] = w; + shaderinfo[10] = h; +#endif + glViewport(0, 0, w, h); + w_h_ratio = sqrt((double)w / (double)h); + setupPerspective(); +} + +void OffscreenView::setupPerspective() +{ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-w_h_ratio, +w_h_ratio, -(1/w_h_ratio), +(1/w_h_ratio), +10.0, +FAR_FAR_AWAY); +} + +void OffscreenView::setupOrtho(double distance, bool offset) +{ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + if(offset) + glTranslated(-0.8, -0.8, 0); + double l = distance/10; + glOrtho(-w_h_ratio*l, +w_h_ratio*l, + -(1/w_h_ratio)*l, +(1/w_h_ratio)*l, + -FAR_FAR_AWAY, +FAR_FAR_AWAY); +} + +void OffscreenView::paintGL() +{ + glEnable(GL_LIGHTING); + + if (orthomode) + setupOrtho(viewer_distance); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearColor(1.0, 1.0, 0.92, 0.0); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + gluLookAt(0.0, -viewer_distance, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); + +// glTranslated(object_trans_x, object_trans_y, object_trans_z); + + // glRotated(object_rot_x, 1.0, 0.0, 0.0); + // glRotated(object_rot_y, 0.0, 1.0, 0.0); + // glRotated(object_rot_z, 0.0, 0.0, 1.0); + + // Large gray axis cross inline with the model + // FIXME: This is always gray - adjust color to keep contrast with background + if (showaxes) + { + glLineWidth(1); + glColor3d(0.5, 0.5, 0.5); + glBegin(GL_LINES); + double l = viewer_distance/10; + glVertex3d(-l, 0, 0); + glVertex3d(+l, 0, 0); + glVertex3d(0, -l, 0); + glVertex3d(0, +l, 0); + glVertex3d(0, 0, -l); + glVertex3d(0, 0, +l); + glEnd(); + } + + glDepthFunc(GL_LESS); + glCullFace(GL_BACK); + glDisable(GL_CULL_FACE); + + glLineWidth(2); + glColor3d(1.0, 0.0, 0.0); + + if (this->renderer) { +#ifdef ENABLE_OPENCSG + OpenCSG::setContext(0); +#endif + this->renderer->draw(showfaces, showedges); + } +} + +bool OffscreenView::save(const char *filename) +{ + return save_framebuffer(this->ctx, filename); +} diff --git a/tests/OffscreenView.h b/tests/OffscreenView.h new file mode 100644 index 0000000..52ee3af --- /dev/null +++ b/tests/OffscreenView.h @@ -0,0 +1,34 @@ +#ifndef OFFSCREENVIEW_H_ +#define OFFSCREENVIEW_H_ + +#include "OffscreenContext.h" +#include <stdint.h> + +class OffscreenView +{ +public: + OffscreenView(size_t width, size_t height); + ~OffscreenView(); + void setRenderer(class Renderer* r); + + void initializeGL(); + void resizeGL(int w, int h); + void setupPerspective(); + void setupOrtho(double distance,bool offset=false); + void paintGL(); + bool save(const char *filename); + + GLint shaderinfo[11]; + OffscreenContext *ctx; +private: + Renderer *renderer; + double w_h_ratio; + + bool orthomode; + bool showaxes; + bool showfaces; + bool showedges; + float viewer_distance; +}; + +#endif diff --git a/tests/cgaltest.cc b/tests/cgaltest.cc new file mode 100644 index 0000000..df03a43 --- /dev/null +++ b/tests/cgaltest.cc @@ -0,0 +1,179 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "myqhash.h" +#include "openscad.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "export.h" +#include "builtin.h" +#include "Tree.h" +#include "CGALEvaluator.h" +#include "PolySetCGALEvaluator.h" + +#include <QApplication> +#include <QFile> +#include <QDir> +#include <QSet> +#include <QTextStream> +#include <getopt.h> +#include <iostream> + +QString commandline_commands; +const char *make_command = NULL; +QSet<QString> dependencies; +QString currentdir; +QString examplesdir; +QString librarydir; + +using std::string; + +void handle_dep(QString filename) +{ + if (filename.startsWith("/")) + dependencies.insert(filename); + else + dependencies.insert(QDir::currentPath() + QString("/") + filename); + if (!QFile(filename).exists() && make_command) { + char buffer[4096]; + snprintf(buffer, 4096, "%s '%s'", make_command, filename.replace("'", "'\\''").toUtf8().data()); + system(buffer); // FIXME: Handle error + } +} + +// FIXME: enforce some maximum cache size (old version had 100K vertices as limit) +QHash<std::string, CGAL_Nef_polyhedron> cache; + +void cgalTree(Tree &tree) +{ + assert(tree.root()); + + CGALEvaluator evaluator(cache, tree); + Traverser evaluate(evaluator, *tree.root(), Traverser::PRE_AND_POSTFIX); + evaluate.execute(); +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <file.scad>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv, false); + QDir original_path = QDir::current(); + + currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + QString text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text += buffer; + } + fclose(fp); + root_module = parse((text+commandline_commands).toAscii().data(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + Tree tree(root_node); + + QHash<std::string, CGAL_Nef_polyhedron> cache; + CGALEvaluator cgalevaluator(cache, tree); + PolySetCGALEvaluator psevaluator(cgalevaluator); + + CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node); + + QDir::setCurrent(original_path.absolutePath()); + QTextStream outstream(stdout); + export_stl(&N, outstream, NULL); + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return 0; +} diff --git a/tests/csgtermtest.cc b/tests/csgtermtest.cc new file mode 100644 index 0000000..944a67a --- /dev/null +++ b/tests/csgtermtest.cc @@ -0,0 +1,181 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "CSGTermRenderer.h" +#include "CSGTextCache.h" +#include "openscad.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "export.h" +#include "builtin.h" +#include "Tree.h" +#include "csgterm.h" + +#include <QApplication> +#include <QFile> +#include <QDir> +#include <QSet> +#include <getopt.h> +#include <assert.h> +#include <iostream> + +using std::cout; + +QString commandline_commands; +const char *make_command = NULL; +QSet<QString> dependencies; +QString currentdir; +QString examplesdir; +QString librarydir; + +void handle_dep(QString filename) +{ + if (filename.startsWith("/")) + dependencies.insert(filename); + else + dependencies.insert(QDir::currentPath() + QString("/") + filename); + if (!QFile(filename).exists() && make_command) { + char buffer[4096]; + snprintf(buffer, 4096, "%s '%s'", make_command, filename.replace("'", "'\\''").toUtf8().data()); + system(buffer); // FIXME: Handle error + } +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <file.scad>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + int rc = 0; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv, false); + QDir original_path = QDir::current(); + + currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + QString text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text += buffer; + } + fclose(fp); + root_module = parse((text+commandline_commands).toAscii().data(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + Tree tree(root_node); + +// cout << tree.getString(*root_node) << "\n"; + + CSGTermRenderer renderer(tree); + CSGTerm *root_term = renderer.renderCSGTerm(*root_node, NULL, NULL); + + // cout << "Stored terms: " << renderer.stored_term.size() << "\n"; + // for (map<int, class CSGTerm*>::iterator iter = renderer.stored_term.begin(); + // iter != renderer.stored_term.end(); + // iter++) { + // cout << iter->first << ":" << (iter->second ? iter->second->label : "NULL") << "\n"; + // } + + // if (renderer.background) cout << "Background terms: " << renderer.background->size() << "\n"; + // if (renderer.highlights) cout << "Highlights terms: " << renderer.highlights->size() << "\n"; + + if (root_term) { + cout << root_term->dump() << "\n"; + } + else { + cout << "No top-level CSG object\n"; + } + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return rc; +} diff --git a/tests/csgtexttest.cc b/tests/csgtexttest.cc new file mode 100644 index 0000000..c259e2d --- /dev/null +++ b/tests/csgtexttest.cc @@ -0,0 +1,170 @@ +/* + * OpenSCAD (www.openscad.at) + * Copyright (C) 2009 Clifford Wolf <clifford@clifford.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "CSGTextRenderer.h" +#include "CSGTextCache.h" +#include "openscad.h" +#include "node.h" +#include "module.h" +#include "context.h" +#include "value.h" +#include "export.h" +#include "builtin.h" +#include "Tree.h" + +#include <QApplication> +#include <QFile> +#include <QDir> +#include <QSet> +#include <getopt.h> +#include <assert.h> +#include <iostream> + +QString commandline_commands; +const char *make_command = NULL; +QSet<QString> dependencies; +QString currentdir; +QString examplesdir; +QString librarydir; + +void handle_dep(QString filename) +{ + if (filename.startsWith("/")) + dependencies.insert(filename); + else + dependencies.insert(QDir::currentPath() + QString("/") + filename); + if (!QFile(filename).exists() && make_command) { + char buffer[4096]; + snprintf(buffer, 4096, "%s '%s'", make_command, filename.replace("'", "'\\''").toUtf8().data()); + system(buffer); // FIXME: Handle error + } +} + +void csgTree(CSGTextCache &cache, const AbstractNode &root) +{ + CSGTextRenderer renderer(cache); + Traverser render(renderer, root, Traverser::PRE_AND_POSTFIX); + render.execute(); +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <file.scad>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + int rc = 0; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv, false); + QDir original_path = QDir::current(); + + currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + QString text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text += buffer; + } + fclose(fp); + root_module = parse((text+commandline_commands).toAscii().data(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + Tree tree; + tree.setRoot(root_node); + CSGTextCache csgcache(tree); + + csgTree(csgcache, *root_node); +// std::cout << tree.getString(*root_node) << "\n"; + + std::cout << csgcache[*root_node] << "\n"; + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return rc; +} diff --git a/test-code/dumptest.cc b/tests/dumptest.cc index 2cb8f12..3f6068a 100644 --- a/test-code/dumptest.cc +++ b/tests/dumptest.cc @@ -31,13 +31,18 @@ #include "value.h" #include "export.h" #include "builtin.h" -//#include "nodedumper.h" +#include "nodedumper.h" +#include "Tree.h" #include <QApplication> #include <QFile> #include <QDir> #include <QSet> #include <getopt.h> +#include <assert.h> +#include <iostream> + +using std::string; QString commandline_commands; const char *make_command = NULL; @@ -107,9 +112,9 @@ int main(int argc, char **argv) Value zero3; zero3.type = Value::VECTOR; - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); - zero3.vec.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); root_ctx.set_variable("$vpt", zero3); root_ctx.set_variable("$vpr", zero3); @@ -145,21 +150,15 @@ int main(int argc, char **argv) root_node = root_module->evaluate(&root_ctx, &root_inst); // Cache test - QString dumpstr = root_node->dump(""); - QString dumpstr_cached = root_node->dump(""); - if (dumpstr != dumpstr_cached) rc = 1; - - printf(dumpstr.toUtf8()); - -#if 0 - NodeDumper dumper; - Traverser trav(dumper, *root_node, Traverser::PRE_AND_POSTFIX); - trav.execute(); - std::string dumpstdstr = dumper.getDump(); - trav.execute(); - std::string dumpstdstr_cached = dumper.getDump(); + QString teststr("test"); + Tree tree; + tree.setRoot(root_node); + + string dumpstdstr = tree.getString(*root_node); + string dumpstdstr_cached = tree.getString(*root_node); if (dumpstdstr != dumpstdstr_cached) rc = 1; -#endif + + std::cout << dumpstdstr << "\n"; destroy_builtin_functions(); destroy_builtin_modules(); diff --git a/tests/opencsgtest.cc b/tests/opencsgtest.cc new file mode 100644 index 0000000..7da15e8 --- /dev/null +++ b/tests/opencsgtest.cc @@ -0,0 +1,243 @@ +#include <GL/glew.h> +#include "openscad.h" +#include "builtin.h" +#include "context.h" +#include "node.h" +#include "module.h" +#include "polyset.h" +#include "Tree.h" +#include "CSGTermEvaluator.h" +#include "CGALEvaluator.h" +#include "PolySetCGALEvaluator.h" + +#include "OpenCSGRenderer.h" +#include "ThrownTogetherRenderer.h" + +#include "csgterm.h" +#include "OffscreenView.h" + +#include <QApplication> +#include <QFile> +#include <QDir> +#include <QSet> +#include <QTimer> + +using std::cerr; +using std::cout; + +QString commandline_commands; +QString librarydir; +QSet<QString> dependencies; +const char *make_command = NULL; + +void handle_dep(QString filename) +{ + if (filename.startsWith("/")) + dependencies.insert(filename); + else + dependencies.insert(QDir::currentPath() + QString("/") + filename); + if (!QFile(filename).exists() && make_command) { + char buffer[4096]; + snprintf(buffer, 4096, "%s '%s'", make_command, filename.replace("'", "'\\''").toUtf8().data()); + system(buffer); // FIXME: Handle error + } +} + +// static void renderfunc(void *vp) +// { +// glClearColor(1.0, 0.0, 0.0, 0.0); +// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +// } + +struct CsgInfo +{ + CSGTerm *root_norm_term; // Normalized CSG products + class CSGChain *root_chain; + std::vector<CSGTerm*> highlight_terms; + CSGChain *highlights_chain; + std::vector<CSGTerm*> background_terms; + CSGChain *background_chain; + OffscreenView *glview; +}; + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <file.scad>\n", argv[0]); + exit(1); + } + + const char *filename = argv[1]; + + initialize_builtin_functions(); + initialize_builtin_modules(); + + QApplication app(argc, argv); + + QDir original_path = QDir::current(); + + QString currentdir = QDir::currentPath(); + + QDir libdir(QApplication::instance()->applicationDirPath()); +#ifdef Q_WS_MAC + libdir.cd("../Resources"); // Libraries can be bundled + if (!libdir.exists("libraries")) libdir.cd("../../.."); +#elif defined(Q_OS_UNIX) + if (libdir.cd("../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../share/openscad/libraries")) { + librarydir = libdir.path(); + } else + if (libdir.cd("../../libraries")) { + librarydir = libdir.path(); + } else +#endif + if (libdir.cd("libraries")) { + librarydir = libdir.path(); + } + + Context root_ctx; + root_ctx.functions_p = &builtin_functions; + root_ctx.modules_p = &builtin_modules; + root_ctx.set_variable("$fn", Value(0.0)); + root_ctx.set_variable("$fs", Value(1.0)); + root_ctx.set_variable("$fa", Value(12.0)); + root_ctx.set_variable("$t", Value(0.0)); + + Value zero3; + zero3.type = Value::VECTOR; + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + zero3.append(new Value(0.0)); + root_ctx.set_variable("$vpt", zero3); + root_ctx.set_variable("$vpr", zero3); + + + AbstractModule *root_module; + ModuleInstantiation root_inst; + AbstractNode *root_node; + + QFileInfo fileInfo(filename); + handle_dep(filename); + FILE *fp = fopen(filename, "rt"); + if (!fp) { + fprintf(stderr, "Can't open input file `%s'!\n", filename); + exit(1); + } else { + QString text; + char buffer[513]; + int ret; + while ((ret = fread(buffer, 1, 512, fp)) > 0) { + buffer[ret] = 0; + text += buffer; + } + fclose(fp); + root_module = parse((text+commandline_commands).toAscii().data(), fileInfo.absolutePath().toLocal8Bit(), false); + if (!root_module) { + exit(1); + } + } + + QDir::setCurrent(fileInfo.absolutePath()); + + AbstractNode::resetIndexCounter(); + root_node = root_module->evaluate(&root_ctx, &root_inst); + + Tree tree(root_node); + + QHash<std::string, CGAL_Nef_polyhedron> cache; + CGALEvaluator cgalevaluator(cache, tree); + PolySetCGALEvaluator psevaluator(cgalevaluator); + CSGTermEvaluator evaluator(tree); + CSGTerm *root_raw_term = evaluator.evaluateCSGTerm(*root_node, NULL, NULL); + + if (!root_raw_term) { + cerr << "Error: CSG generation failed! (no top level object found)\n"; + return 1; + } + + CsgInfo csgInfo; + csgInfo.root_norm_term = root_raw_term->link(); + + // CSG normalization + while (1) { + CSGTerm *n = csgInfo.root_norm_term->normalize(); + csgInfo.root_norm_term->unlink(); + if (csgInfo.root_norm_term == n) + break; + csgInfo.root_norm_term = n; + } + + assert(csgInfo.root_norm_term); + + csgInfo.root_chain = new CSGChain(); + csgInfo.root_chain->import(csgInfo.root_norm_term); + + if (csgInfo.highlight_terms.size() > 0) { + cerr << "Compiling highlights (" << " CSG Trees)...\n"; + + csgInfo.highlights_chain = new CSGChain(); + for (unsigned int i = 0; i < csgInfo.highlight_terms.size(); i++) { + while (1) { + CSGTerm *n = csgInfo.highlight_terms[i]->normalize(); + csgInfo.highlight_terms[i]->unlink(); + if (csgInfo.highlight_terms[i] == n) + break; + csgInfo.highlight_terms[i] = n; + } + csgInfo.highlights_chain->import(csgInfo.highlight_terms[i]); + } + } + + if (csgInfo.background_terms.size() > 0) { + cerr << "Compiling background (" << csgInfo.background_terms.size() << " CSG Trees)...\n"; + + csgInfo.background_chain = new CSGChain(); + for (unsigned int i = 0; i < csgInfo.background_terms.size(); i++) { + while (1) { + CSGTerm *n = csgInfo.background_terms[i]->normalize(); + csgInfo.background_terms[i]->unlink(); + if (csgInfo.background_terms[i] == n) + break; + csgInfo.background_terms[i] = n; + } + csgInfo.background_chain->import(csgInfo.background_terms[i]); + } + } + + QDir::setCurrent(original_path.absolutePath()); + + csgInfo.glview = new OffscreenView(512,512); + + glewInit(); + cout << "GLEW version " << glewGetString(GLEW_VERSION) << "\n"; + cout << (const char *)glGetString(GL_RENDERER) << "(" << (const char *)glGetString(GL_VENDOR) << ")\n" + << "OpenGL version " << (const char *)glGetString(GL_VERSION) << "\n"; + cout << "Extensions: " << (const char *)glGetString(GL_EXTENSIONS) << "\n"; + + + if (GLEW_ARB_framebuffer_object) { + cout << "ARB_FBO supported\n"; + } + if (GLEW_EXT_framebuffer_object) { + cout << "EXT_FBO supported\n"; + } + if (GLEW_EXT_packed_depth_stencil) { + cout << "EXT_packed_depth_stencil\n"; + } + + OpenCSGRenderer opencsgRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain, csgInfo.glview->shaderinfo); + ThrownTogetherRenderer thrownTogetherRenderer(csgInfo.root_chain, csgInfo.highlights_chain, csgInfo.background_chain); +// csgInfo.glview->setRenderer(&thrownTogetherRenderer); + csgInfo.glview->setRenderer(&opencsgRenderer); + + csgInfo.glview->paintGL(); + + csgInfo.glview->save("out.png"); + + destroy_builtin_functions(); + destroy_builtin_modules(); + + return 0; +} diff --git a/tests/regression/csgtexttest/allmodules-expected.txt b/tests/regression/csgtexttest/allmodules-expected.txt new file mode 100644 index 0000000..722315b --- /dev/null +++ b/tests/regression/csgtexttest/allmodules-expected.txt @@ -0,0 +1 @@ +group1(minkowski2+glide3+subdiv4+group5+group5+group5+intersection_for8+group5+union10+difference11+intersection12+linear_extrude+linear_extrude+rotate_extrude+rotate_extrude+import_stl+import_off+import_dxf+group5+cube+sphere+cylinder+polyhedron+square+circle+polygon+projection+render29+surface+transform31+transform31+transform33+transform31+transform31+transform31) diff --git a/tests/regression/csgtexttest/assign-expected.txt b/tests/regression/csgtexttest/assign-expected.txt new file mode 100644 index 0000000..a08e59e --- /dev/null +++ b/tests/regression/csgtexttest/assign-expected.txt @@ -0,0 +1 @@ +group1(group2) diff --git a/tests/regression/csgtexttest/child-expected.txt b/tests/regression/csgtexttest/child-expected.txt new file mode 100644 index 0000000..331822f --- /dev/null +++ b/tests/regression/csgtexttest/child-expected.txt @@ -0,0 +1 @@ +group1 diff --git a/tests/regression/csgtexttest/circle-expected.txt b/tests/regression/csgtexttest/circle-expected.txt new file mode 100644 index 0000000..44ca16a --- /dev/null +++ b/tests/regression/csgtexttest/circle-expected.txt @@ -0,0 +1 @@ +group1(circle) diff --git a/tests/regression/csgtexttest/color-expected.txt b/tests/regression/csgtexttest/color-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/color-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/cube-expected.txt b/tests/regression/csgtexttest/cube-expected.txt new file mode 100644 index 0000000..07b688d --- /dev/null +++ b/tests/regression/csgtexttest/cube-expected.txt @@ -0,0 +1 @@ +group1(cube) diff --git a/tests/regression/csgtexttest/cylinder-expected.txt b/tests/regression/csgtexttest/cylinder-expected.txt new file mode 100644 index 0000000..3f8a41f --- /dev/null +++ b/tests/regression/csgtexttest/cylinder-expected.txt @@ -0,0 +1 @@ +group1(cylinder) diff --git a/tests/regression/csgtexttest/difference-expected.txt b/tests/regression/csgtexttest/difference-expected.txt new file mode 100644 index 0000000..551bb16 --- /dev/null +++ b/tests/regression/csgtexttest/difference-expected.txt @@ -0,0 +1 @@ +group1(difference2) diff --git a/tests/regression/csgtexttest/difference-tests-expected.txt b/tests/regression/csgtexttest/difference-tests-expected.txt new file mode 100644 index 0000000..6bbfe30 --- /dev/null +++ b/tests/regression/csgtexttest/difference-tests-expected.txt @@ -0,0 +1 @@ +group1(difference2(sphere-cube)) diff --git a/tests/regression/csgtexttest/dim-all-expected.txt b/tests/regression/csgtexttest/dim-all-expected.txt new file mode 100644 index 0000000..6c127a8 --- /dev/null +++ b/tests/regression/csgtexttest/dim-all-expected.txt @@ -0,0 +1 @@ +group1(group2+group2+group2+group2+group2+group2+group2+group2) diff --git a/tests/regression/csgtexttest/dxf_linear_extrude-expected.txt b/tests/regression/csgtexttest/dxf_linear_extrude-expected.txt new file mode 100644 index 0000000..fa1671c --- /dev/null +++ b/tests/regression/csgtexttest/dxf_linear_extrude-expected.txt @@ -0,0 +1 @@ +group1(linear_extrude) diff --git a/tests/regression/csgtexttest/dxf_rotate_extrude-expected.txt b/tests/regression/csgtexttest/dxf_rotate_extrude-expected.txt new file mode 100644 index 0000000..452f8e4 --- /dev/null +++ b/tests/regression/csgtexttest/dxf_rotate_extrude-expected.txt @@ -0,0 +1 @@ +group1(rotate_extrude) diff --git a/tests/regression/csgtexttest/echo-expected.txt b/tests/regression/csgtexttest/echo-expected.txt new file mode 100644 index 0000000..a08e59e --- /dev/null +++ b/tests/regression/csgtexttest/echo-expected.txt @@ -0,0 +1 @@ +group1(group2) diff --git a/tests/regression/csgtexttest/for-expected.txt b/tests/regression/csgtexttest/for-expected.txt new file mode 100644 index 0000000..a08e59e --- /dev/null +++ b/tests/regression/csgtexttest/for-expected.txt @@ -0,0 +1 @@ +group1(group2) diff --git a/tests/regression/csgtexttest/glide-expected.txt b/tests/regression/csgtexttest/glide-expected.txt new file mode 100644 index 0000000..a792750 --- /dev/null +++ b/tests/regression/csgtexttest/glide-expected.txt @@ -0,0 +1 @@ +group1(glide2) diff --git a/tests/regression/csgtexttest/group-expected.txt b/tests/regression/csgtexttest/group-expected.txt new file mode 100644 index 0000000..a08e59e --- /dev/null +++ b/tests/regression/csgtexttest/group-expected.txt @@ -0,0 +1 @@ +group1(group2) diff --git a/tests/regression/csgtexttest/if-expected.txt b/tests/regression/csgtexttest/if-expected.txt new file mode 100644 index 0000000..a08e59e --- /dev/null +++ b/tests/regression/csgtexttest/if-expected.txt @@ -0,0 +1 @@ +group1(group2) diff --git a/tests/regression/csgtexttest/import_dxf-expected.txt b/tests/regression/csgtexttest/import_dxf-expected.txt new file mode 100644 index 0000000..ffb96fc --- /dev/null +++ b/tests/regression/csgtexttest/import_dxf-expected.txt @@ -0,0 +1 @@ +group1(import_dxf) diff --git a/tests/regression/csgtexttest/import_off-expected.txt b/tests/regression/csgtexttest/import_off-expected.txt new file mode 100644 index 0000000..8a3bafa --- /dev/null +++ b/tests/regression/csgtexttest/import_off-expected.txt @@ -0,0 +1 @@ +group1(import_off) diff --git a/tests/regression/csgtexttest/import_stl-expected.txt b/tests/regression/csgtexttest/import_stl-expected.txt new file mode 100644 index 0000000..47c4707 --- /dev/null +++ b/tests/regression/csgtexttest/import_stl-expected.txt @@ -0,0 +1 @@ +group1(import_stl) diff --git a/tests/regression/csgtexttest/import_stl-tests-expected.txt b/tests/regression/csgtexttest/import_stl-tests-expected.txt new file mode 100644 index 0000000..47c4707 --- /dev/null +++ b/tests/regression/csgtexttest/import_stl-tests-expected.txt @@ -0,0 +1 @@ +group1(import_stl) diff --git a/tests/regression/csgtexttest/intersection-expected.txt b/tests/regression/csgtexttest/intersection-expected.txt new file mode 100644 index 0000000..0ce0967 --- /dev/null +++ b/tests/regression/csgtexttest/intersection-expected.txt @@ -0,0 +1 @@ +group1(intersection2) diff --git a/tests/regression/csgtexttest/intersection-tests-expected.txt b/tests/regression/csgtexttest/intersection-tests-expected.txt new file mode 100644 index 0000000..3077e22 --- /dev/null +++ b/tests/regression/csgtexttest/intersection-tests-expected.txt @@ -0,0 +1 @@ +group1(intersection2(sphere*cube)) diff --git a/tests/regression/csgtexttest/intersection_for-expected.txt b/tests/regression/csgtexttest/intersection_for-expected.txt new file mode 100644 index 0000000..b503644 --- /dev/null +++ b/tests/regression/csgtexttest/intersection_for-expected.txt @@ -0,0 +1 @@ +group1(intersection_for2) diff --git a/tests/regression/csgtexttest/linear_extrude-expected.txt b/tests/regression/csgtexttest/linear_extrude-expected.txt new file mode 100644 index 0000000..fa1671c --- /dev/null +++ b/tests/regression/csgtexttest/linear_extrude-expected.txt @@ -0,0 +1 @@ +group1(linear_extrude) diff --git a/tests/regression/csgtexttest/linear_extrude-tests-expected.txt b/tests/regression/csgtexttest/linear_extrude-tests-expected.txt new file mode 100644 index 0000000..f9639c9 --- /dev/null +++ b/tests/regression/csgtexttest/linear_extrude-tests-expected.txt @@ -0,0 +1 @@ +group1(linear_extrude+transform4(linear_extrude)+transform7(linear_extrude)+transform10(linear_extrude)+transform13(linear_extrude)+transform16(linear_extrude)) diff --git a/tests/regression/csgtexttest/minkowski-expected.txt b/tests/regression/csgtexttest/minkowski-expected.txt new file mode 100644 index 0000000..e601bac --- /dev/null +++ b/tests/regression/csgtexttest/minkowski-expected.txt @@ -0,0 +1 @@ +group1(minkowski2) diff --git a/tests/regression/csgtexttest/mirror-expected.txt b/tests/regression/csgtexttest/mirror-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/mirror-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/multmatrix-expected.txt b/tests/regression/csgtexttest/multmatrix-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/multmatrix-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/null-polygons-expected.txt b/tests/regression/csgtexttest/null-polygons-expected.txt new file mode 100644 index 0000000..4afd2e4 --- /dev/null +++ b/tests/regression/csgtexttest/null-polygons-expected.txt @@ -0,0 +1 @@ +group1(linear_extrude+linear_extrude) diff --git a/tests/regression/csgtexttest/polygon-expected.txt b/tests/regression/csgtexttest/polygon-expected.txt new file mode 100644 index 0000000..e308e30 --- /dev/null +++ b/tests/regression/csgtexttest/polygon-expected.txt @@ -0,0 +1 @@ +group1(polygon) diff --git a/tests/regression/csgtexttest/polygon-illegal-winding-expected.txt b/tests/regression/csgtexttest/polygon-illegal-winding-expected.txt new file mode 100644 index 0000000..994b16d --- /dev/null +++ b/tests/regression/csgtexttest/polygon-illegal-winding-expected.txt @@ -0,0 +1 @@ +group1(polyhedron) diff --git a/tests/regression/csgtexttest/polyhedron-expected.txt b/tests/regression/csgtexttest/polyhedron-expected.txt new file mode 100644 index 0000000..994b16d --- /dev/null +++ b/tests/regression/csgtexttest/polyhedron-expected.txt @@ -0,0 +1 @@ +group1(polyhedron) diff --git a/tests/regression/csgtexttest/polyset-reduce-crash-expected.txt b/tests/regression/csgtexttest/polyset-reduce-crash-expected.txt new file mode 100644 index 0000000..93ab028 --- /dev/null +++ b/tests/regression/csgtexttest/polyset-reduce-crash-expected.txt @@ -0,0 +1 @@ +group1(transform2(union3(transform4(polygon+polygon+polygon+polygon+polygon+polygon+polygon+polygon)+circle))) diff --git a/tests/regression/csgtexttest/projection-expected.txt b/tests/regression/csgtexttest/projection-expected.txt new file mode 100644 index 0000000..38b6abf --- /dev/null +++ b/tests/regression/csgtexttest/projection-expected.txt @@ -0,0 +1 @@ +group1(projection) diff --git a/tests/regression/csgtexttest/projection-tests-expected.txt b/tests/regression/csgtexttest/projection-tests-expected.txt new file mode 100644 index 0000000..aa9c079 --- /dev/null +++ b/tests/regression/csgtexttest/projection-tests-expected.txt @@ -0,0 +1 @@ +group1(linear_extrude+transform5(linear_extrude)+transform10(linear_extrude)) diff --git a/tests/regression/csgtexttest/render-expected.txt b/tests/regression/csgtexttest/render-expected.txt new file mode 100644 index 0000000..b53708e --- /dev/null +++ b/tests/regression/csgtexttest/render-expected.txt @@ -0,0 +1 @@ +group1(render2) diff --git a/tests/regression/csgtexttest/rotate-expected.txt b/tests/regression/csgtexttest/rotate-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/rotate-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/rotate_extrude-expected.txt b/tests/regression/csgtexttest/rotate_extrude-expected.txt new file mode 100644 index 0000000..452f8e4 --- /dev/null +++ b/tests/regression/csgtexttest/rotate_extrude-expected.txt @@ -0,0 +1 @@ +group1(rotate_extrude) diff --git a/tests/regression/csgtexttest/rotate_extrude-tests-expected.txt b/tests/regression/csgtexttest/rotate_extrude-tests-expected.txt new file mode 100644 index 0000000..452f8e4 --- /dev/null +++ b/tests/regression/csgtexttest/rotate_extrude-tests-expected.txt @@ -0,0 +1 @@ +group1(rotate_extrude) diff --git a/tests/regression/csgtexttest/scale-expected.txt b/tests/regression/csgtexttest/scale-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/scale-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/sphere-expected.txt b/tests/regression/csgtexttest/sphere-expected.txt new file mode 100644 index 0000000..2bf6a72 --- /dev/null +++ b/tests/regression/csgtexttest/sphere-expected.txt @@ -0,0 +1 @@ +group1(sphere) diff --git a/tests/regression/csgtexttest/sphere-tests-expected.txt b/tests/regression/csgtexttest/sphere-tests-expected.txt new file mode 100644 index 0000000..94323fd --- /dev/null +++ b/tests/regression/csgtexttest/sphere-tests-expected.txt @@ -0,0 +1 @@ +group1(sphere+transform3(sphere)+transform5(sphere)+transform7(sphere)) diff --git a/tests/regression/csgtexttest/square-expected.txt b/tests/regression/csgtexttest/square-expected.txt new file mode 100644 index 0000000..e235806 --- /dev/null +++ b/tests/regression/csgtexttest/square-expected.txt @@ -0,0 +1 @@ +group1(square) diff --git a/tests/regression/csgtexttest/subdiv-expected.txt b/tests/regression/csgtexttest/subdiv-expected.txt new file mode 100644 index 0000000..b096229 --- /dev/null +++ b/tests/regression/csgtexttest/subdiv-expected.txt @@ -0,0 +1 @@ +group1(subdiv2) diff --git a/tests/regression/csgtexttest/surface-expected.txt b/tests/regression/csgtexttest/surface-expected.txt new file mode 100644 index 0000000..7c822d3 --- /dev/null +++ b/tests/regression/csgtexttest/surface-expected.txt @@ -0,0 +1 @@ +group1(surface) diff --git a/tests/regression/csgtexttest/surface-tests-expected.txt b/tests/regression/csgtexttest/surface-tests-expected.txt new file mode 100644 index 0000000..7c822d3 --- /dev/null +++ b/tests/regression/csgtexttest/surface-tests-expected.txt @@ -0,0 +1 @@ +group1(surface) diff --git a/tests/regression/csgtexttest/transform-insert-expected.txt b/tests/regression/csgtexttest/transform-insert-expected.txt new file mode 100644 index 0000000..ffb96fc --- /dev/null +++ b/tests/regression/csgtexttest/transform-insert-expected.txt @@ -0,0 +1 @@ +group1(import_dxf) diff --git a/tests/regression/csgtexttest/translate-expected.txt b/tests/regression/csgtexttest/translate-expected.txt new file mode 100644 index 0000000..80024d2 --- /dev/null +++ b/tests/regression/csgtexttest/translate-expected.txt @@ -0,0 +1 @@ +group1(transform2) diff --git a/tests/regression/csgtexttest/union-expected.txt b/tests/regression/csgtexttest/union-expected.txt new file mode 100644 index 0000000..1884c0f --- /dev/null +++ b/tests/regression/csgtexttest/union-expected.txt @@ -0,0 +1 @@ +group1(union2) diff --git a/tests/regression/dumptest/allmodules-expected.txt b/tests/regression/dumptest/allmodules-expected.txt new file mode 100644 index 0000000..b51fae3 --- /dev/null +++ b/tests/regression/dumptest/allmodules-expected.txt @@ -0,0 +1,38 @@ +group() { + minkowski(convexity = 0); + glide(path = undef, convexity = 0); + subdiv(level = 1, convexity = 0); + hull(); + group(); + group(); + group(); + intersection_for(); + group(); + union(); + difference(); + intersection(); + linear_extrude(file = "", cache = "0.0", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + linear_extrude(file = "", cache = "0.0", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + rotate_extrude(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + rotate_extrude(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + import_stl(file = "", cache = "0.0", convexity = 1); + import_off(file = "", cache = "0.0", convexity = 1); + import_dxf(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + group(); + cube(size = [1, 1, 1], center = false); + sphere($fn = 0, $fa = 12, $fs = 1, r = 1); + cylinder($fn = 0, $fa = 12, $fs = 1, h = 1, r1 = 1, r2 = 1, center = false); + polyhedron(points = undef, triangles = undef, convexity = 1); + square(size = [1, 1], center = false); + circle($fn = 0, $fa = 12, $fs = 1, r = 1); + polygon(points = undef, paths = undef, convexity = 1); + projection(cut = false, convexity = 0); + render(convexity = 1); + surface(file = "", center = false); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); + multmatrix([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/assign-expected.txt b/tests/regression/dumptest/assign-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/assign-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/child-expected.txt b/tests/regression/dumptest/child-expected.txt new file mode 100644 index 0000000..0a04719 --- /dev/null +++ b/tests/regression/dumptest/child-expected.txt @@ -0,0 +1 @@ +group(); diff --git a/tests/regression/dumptest/circle-expected.txt b/tests/regression/dumptest/circle-expected.txt new file mode 100644 index 0000000..1060d3c --- /dev/null +++ b/tests/regression/dumptest/circle-expected.txt @@ -0,0 +1,3 @@ +group() { + circle($fn = 0, $fa = 12, $fs = 1, r = 1); +} diff --git a/tests/regression/dumptest/color-expected.txt b/tests/regression/dumptest/color-expected.txt new file mode 100644 index 0000000..87e28e2 --- /dev/null +++ b/tests/regression/dumptest/color-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/cube-expected.txt b/tests/regression/dumptest/cube-expected.txt new file mode 100644 index 0000000..91dc5fc --- /dev/null +++ b/tests/regression/dumptest/cube-expected.txt @@ -0,0 +1,3 @@ +group() { + cube(size = [1, 1, 1], center = false); +} diff --git a/tests/regression/dumptest/cylinder-expected.txt b/tests/regression/dumptest/cylinder-expected.txt new file mode 100644 index 0000000..a26a533 --- /dev/null +++ b/tests/regression/dumptest/cylinder-expected.txt @@ -0,0 +1,3 @@ +group() { + cylinder($fn = 0, $fa = 12, $fs = 1, h = 1, r1 = 1, r2 = 1, center = false); +} diff --git a/tests/regression/dumptest/difference-expected.txt b/tests/regression/dumptest/difference-expected.txt new file mode 100644 index 0000000..09b977f --- /dev/null +++ b/tests/regression/dumptest/difference-expected.txt @@ -0,0 +1,3 @@ +group() { + difference(); +} diff --git a/tests/regression/dumptest/difference-tests-expected.txt b/tests/regression/dumptest/difference-tests-expected.txt new file mode 100644 index 0000000..bc59e45 --- /dev/null +++ b/tests/regression/dumptest/difference-tests-expected.txt @@ -0,0 +1,6 @@ +group() { + difference() { + sphere($fn = 0, $fa = 12, $fs = 1, r = 3); + cube(size = [3, 3, 8], center = true); + } +} diff --git a/tests/regression/dumptest/dim-all-expected.txt b/tests/regression/dumptest/dim-all-expected.txt new file mode 100644 index 0000000..8d088d9 --- /dev/null +++ b/tests/regression/dumptest/dim-all-expected.txt @@ -0,0 +1,10 @@ +group() { + group(); + group(); + group(); + group(); + group(); + group(); + group(); + group(); +} diff --git a/tests/regression/dumptest/dxf-export-expected.txt b/tests/regression/dumptest/dxf-export-expected.txt new file mode 100644 index 0000000..692526c --- /dev/null +++ b/tests/regression/dumptest/dxf-export-expected.txt @@ -0,0 +1,17 @@ +group() { + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + multmatrix([[1, 0, 0, 15], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [10, 10], center = true); + } + multmatrix([[1, 0, 0, 30], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + polygon(points = [[-5, -5], [5, -5], [0, 5]], paths = [[0, 1, 2]], convexity = 1); + } + multmatrix([[1, 0, 0, 0], [0, 1, 0, -15], [0, 0, 1, 0], [0, 0, 0, 1]]) { + difference() { + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + multmatrix([[1, 0, 0, 0], [0, 1, 0, -6], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [12, 12], center = true); + } + } + } +} diff --git a/tests/regression/dumptest/dxf_linear_extrude-expected.txt b/tests/regression/dumptest/dxf_linear_extrude-expected.txt new file mode 100644 index 0000000..fd6535d --- /dev/null +++ b/tests/regression/dumptest/dxf_linear_extrude-expected.txt @@ -0,0 +1,3 @@ +group() { + linear_extrude(file = "", cache = "0.0", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/dxf_rotate_extrude-expected.txt b/tests/regression/dumptest/dxf_rotate_extrude-expected.txt new file mode 100644 index 0000000..2d65d35 --- /dev/null +++ b/tests/regression/dumptest/dxf_rotate_extrude-expected.txt @@ -0,0 +1,3 @@ +group() { + rotate_extrude(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/echo-expected.txt b/tests/regression/dumptest/echo-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/echo-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/for-expected.txt b/tests/regression/dumptest/for-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/for-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/glide-expected.txt b/tests/regression/dumptest/glide-expected.txt new file mode 100644 index 0000000..67e40b8 --- /dev/null +++ b/tests/regression/dumptest/glide-expected.txt @@ -0,0 +1,3 @@ +group() { + glide(path = undef, convexity = 0); +} diff --git a/tests/regression/dumptest/group-expected.txt b/tests/regression/dumptest/group-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/group-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/hull-expected.txt b/tests/regression/dumptest/hull-expected.txt new file mode 100644 index 0000000..6f777b0 --- /dev/null +++ b/tests/regression/dumptest/hull-expected.txt @@ -0,0 +1,3 @@ +group() { + hull(); +} diff --git a/tests/regression/dumptest/hull-tests-expected.txt b/tests/regression/dumptest/hull-tests-expected.txt new file mode 100644 index 0000000..fd204b6 --- /dev/null +++ b/tests/regression/dumptest/hull-tests-expected.txt @@ -0,0 +1,46 @@ +group() { + group() { + hull() { + multmatrix([[1, 0, 0, 15], [0, 1, 0, 10], [0, 0, 1, 0], [0, 0, 0, 1]]) { + circle($fn = 0, $fa = 12, $fs = 1, r = 10); + } + difference() { + circle($fn = 0, $fa = 12, $fs = 1, r = 10); + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + } + } + } + multmatrix([[1, 0, 0, 40], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + hull() { + multmatrix([[1, 0, 0, 15], [0, 1, 0, 10], [0, 0, 1, 0], [0, 0, 0, 1]]) { + circle($fn = 0, $fa = 12, $fs = 1, r = 10); + } + circle($fn = 0, $fa = 12, $fs = 1, r = 10); + } + } + } + multmatrix([[1, 0, 0, 0], [0, 1, 0, 40], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + hull() { + multmatrix([[1, 0, 0, 15], [0, 1, 0, 10], [0, 0, 1, 0], [0, 0, 0, 1]]) { + cylinder($fn = 0, $fa = 12, $fs = 1, h = 10, r1 = 1, r2 = 1, center = false); + } + difference() { + cylinder($fn = 0, $fa = 12, $fs = 1, h = 10, r1 = 1, r2 = 1, center = false); + cylinder($fn = 0, $fa = 12, $fs = 1, h = 5, r1 = 1, r2 = 1, center = false); + } + } + } + } + multmatrix([[1, 0, 0, 40], [0, 1, 0, 40], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + hull() { + multmatrix([[1, 0, 0, 15], [0, 1, 0, 10], [0, 0, 1, 0], [0, 0, 0, 1]]) { + cylinder($fn = 0, $fa = 12, $fs = 1, h = 1, r1 = 10, r2 = 10, center = false); + } + cylinder($fn = 0, $fa = 12, $fs = 1, h = 1, r1 = 10, r2 = 10, center = false); + } + } + } +} diff --git a/tests/regression/dumptest/if-expected.txt b/tests/regression/dumptest/if-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/if-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/import_dxf-expected.txt b/tests/regression/dumptest/import_dxf-expected.txt new file mode 100644 index 0000000..08238d2 --- /dev/null +++ b/tests/regression/dumptest/import_dxf-expected.txt @@ -0,0 +1,3 @@ +group() { + import_dxf(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/import_off-expected.txt b/tests/regression/dumptest/import_off-expected.txt new file mode 100644 index 0000000..51293c9 --- /dev/null +++ b/tests/regression/dumptest/import_off-expected.txt @@ -0,0 +1,3 @@ +group() { + import_off(file = "", cache = "0.0", convexity = 1); +} diff --git a/tests/regression/dumptest/import_stl-expected.txt b/tests/regression/dumptest/import_stl-expected.txt new file mode 100644 index 0000000..2da7d35 --- /dev/null +++ b/tests/regression/dumptest/import_stl-expected.txt @@ -0,0 +1,3 @@ +group() { + import_stl(file = "", cache = "0.0", convexity = 1); +} diff --git a/tests/regression/dumptest/import_stl-tests-expected.txt b/tests/regression/dumptest/import_stl-tests-expected.txt new file mode 100644 index 0000000..bc27c6a --- /dev/null +++ b/tests/regression/dumptest/import_stl-tests-expected.txt @@ -0,0 +1,3 @@ +group() { + import_stl(file = "/Users/kintel/code/metalab/checkout/OpenSCAD/openscad-visitor/testdata/scad/import.stl", cache = "4c34b4bc.2506", convexity = 1); +} diff --git a/tests/regression/dumptest/include-test-expected.txt b/tests/regression/dumptest/include-test-expected.txt new file mode 100644 index 0000000..871e45d --- /dev/null +++ b/tests/regression/dumptest/include-test-expected.txt @@ -0,0 +1,20 @@ +group() { + group() { + group() { + group(); + } + group() { + group(); + } + group() { + group(); + } + group() { + group(); + } + group() { + group(); + } + sphere($fn = 0, $fa = 12, $fs = 1, r = 1); + } +} diff --git a/tests/regression/dumptest/intersection-expected.txt b/tests/regression/dumptest/intersection-expected.txt new file mode 100644 index 0000000..409e196 --- /dev/null +++ b/tests/regression/dumptest/intersection-expected.txt @@ -0,0 +1,3 @@ +group() { + intersection(); +} diff --git a/tests/regression/dumptest/intersection-tests-expected.txt b/tests/regression/dumptest/intersection-tests-expected.txt new file mode 100644 index 0000000..90349a3 --- /dev/null +++ b/tests/regression/dumptest/intersection-tests-expected.txt @@ -0,0 +1,6 @@ +group() { + intersection() { + sphere($fn = 0, $fa = 12, $fs = 1, r = 3); + cube(size = [3, 3, 8], center = true); + } +} diff --git a/tests/regression/dumptest/intersection_for-expected.txt b/tests/regression/dumptest/intersection_for-expected.txt new file mode 100644 index 0000000..8bb5d2d --- /dev/null +++ b/tests/regression/dumptest/intersection_for-expected.txt @@ -0,0 +1,3 @@ +group() { + intersection_for(); +} diff --git a/tests/regression/dumptest/linear_extrude-expected.txt b/tests/regression/dumptest/linear_extrude-expected.txt new file mode 100644 index 0000000..fd6535d --- /dev/null +++ b/tests/regression/dumptest/linear_extrude-expected.txt @@ -0,0 +1,3 @@ +group() { + linear_extrude(file = "", cache = "0.0", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/linear_extrude-tests-expected.txt b/tests/regression/dumptest/linear_extrude-tests-expected.txt new file mode 100644 index 0000000..8fe9928 --- /dev/null +++ b/tests/regression/dumptest/linear_extrude-tests-expected.txt @@ -0,0 +1,30 @@ +group() { + linear_extrude(file = "", cache = "0.0", layer = "", height = 10, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + square(size = [10, 10], center = false); + } + multmatrix([[1, 0, 0, 19], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 10, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + } + } + multmatrix([[1, 0, 0, 31.5], [0, 1, 0, 2.5], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 10, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + polygon(points = [[-5, -2.5], [5, -2.5], [0, 2.5]], paths = undef, convexity = 1); + } + } + multmatrix([[1, 0, 0, 0], [0, 1, 0, -12], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, twist = 45, slices = 3, $fn = 0, $fa = 12, $fs = 1) { + square(size = [10, 10], center = false); + } + } + multmatrix([[1, 0, 0, 19], [0, 1, 0, -7], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, twist = 90, slices = 7, $fn = 0, $fa = 12, $fs = 1) { + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + } + } + multmatrix([[1, 0, 0, 31.5], [0, 1, 0, -9.5], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, twist = 180, slices = 15, $fn = 0, $fa = 12, $fs = 1) { + polygon(points = [[-5, -2.5], [5, -2.5], [0, 2.5]], paths = undef, convexity = 1); + } + } +} diff --git a/tests/regression/dumptest/minkowski-expected.txt b/tests/regression/dumptest/minkowski-expected.txt new file mode 100644 index 0000000..ecab5f3 --- /dev/null +++ b/tests/regression/dumptest/minkowski-expected.txt @@ -0,0 +1,3 @@ +group() { + minkowski(convexity = 0); +} diff --git a/tests/regression/dumptest/minkowski-tests-expected.txt b/tests/regression/dumptest/minkowski-tests-expected.txt new file mode 100644 index 0000000..7435769 --- /dev/null +++ b/tests/regression/dumptest/minkowski-tests-expected.txt @@ -0,0 +1,66 @@ +group() { + multmatrix([[1, 0, 0, -25], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + difference() { + square(size = [10, 10], center = false); + multmatrix([[1, 0, 0, 2], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [6, 6], center = false); + } + } + circle($fn = 0, $fa = 12, $fs = 1, r = 2); + } + } + } + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + difference() { + square(size = [10, 10], center = false); + square(size = [5, 5], center = false); + } + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + } + } + } + multmatrix([[1, 0, 0, 25], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + square(size = [10, 10], center = false); + circle($fn = 0, $fa = 12, $fs = 1, r = 5); + } + } + } + multmatrix([[1, 0, 0, -25], [0, 1, 0, 25], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + difference() { + cube(size = [10, 10, 5], center = false); + multmatrix([[1, 0, 0, 2], [0, 1, 0, 2], [0, 0, 1, -2], [0, 0, 0, 1]]) { + cube(size = [6, 6, 10], center = false); + } + } + cylinder($fn = 0, $fa = 12, $fs = 1, h = 1, r1 = 2, r2 = 2, center = false); + } + } + } + multmatrix([[1, 0, 0, 0], [0, 1, 0, 25], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + difference() { + cube(size = [10, 10, 5], center = false); + cube(size = [5, 5, 5], center = false); + } + cylinder($fn = 0, $fa = 12, $fs = 1, h = 5, r1 = 5, r2 = 5, center = false); + } + } + } + multmatrix([[1, 0, 0, 25], [0, 1, 0, 25], [0, 0, 1, 0], [0, 0, 0, 1]]) { + group() { + minkowski(convexity = 0) { + cube(size = [10, 10, 5], center = false); + cylinder($fn = 0, $fa = 12, $fs = 1, h = 5, r1 = 5, r2 = 5, center = false); + } + } + } +} diff --git a/tests/regression/dumptest/mirror-expected.txt b/tests/regression/dumptest/mirror-expected.txt new file mode 100644 index 0000000..3ad73cb --- /dev/null +++ b/tests/regression/dumptest/mirror-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/multmatrix-expected.txt b/tests/regression/dumptest/multmatrix-expected.txt new file mode 100644 index 0000000..87e28e2 --- /dev/null +++ b/tests/regression/dumptest/multmatrix-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/null-polygons-expected.txt b/tests/regression/dumptest/null-polygons-expected.txt new file mode 100644 index 0000000..bfceaff --- /dev/null +++ b/tests/regression/dumptest/null-polygons-expected.txt @@ -0,0 +1,6 @@ +group() { + linear_extrude(file = "", cache = "0.0", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + import_dxf(file = "/Users/kintel/code/metalab/checkout/OpenSCAD/openscad-visitor/testdata/scad/null-polygons.dxf", cache = "4c34b4bc.3124", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); + } + linear_extrude(file = "/Users/kintel/code/metalab/checkout/OpenSCAD/openscad-visitor/testdata/scad/null-polygons.dxf", cache = "4c34b4bc.3124", layer = "", height = 100, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/polygon-expected.txt b/tests/regression/dumptest/polygon-expected.txt new file mode 100644 index 0000000..d04aec9 --- /dev/null +++ b/tests/regression/dumptest/polygon-expected.txt @@ -0,0 +1,3 @@ +group() { + polygon(points = undef, paths = undef, convexity = 1); +} diff --git a/tests/regression/dumptest/polygon-illegal-winding-expected.txt b/tests/regression/dumptest/polygon-illegal-winding-expected.txt new file mode 100644 index 0000000..3a4c32b --- /dev/null +++ b/tests/regression/dumptest/polygon-illegal-winding-expected.txt @@ -0,0 +1,3 @@ +group() { + polyhedron(points = [[0, -10, 60], [0, 10, 60], [0, 10, 0], [0, -10, 0], [60, -10, 60], [60, 10, 60], [10, -10, 50], [10, 10, 50], [10, 10, 30], [10, -10, 30], [30, -10, 50], [30, 10, 50]], triangles = [[0, 2, 3], [0, 1, 2], [0, 4, 5], [0, 5, 1], [5, 4, 2], [2, 4, 3], [6, 8, 9], [6, 7, 8], [6, 10, 11], [6, 11, 7], [10, 8, 11], [10, 9, 8], [0, 3, 9], [9, 0, 6], [10, 6, 0], [0, 4, 10], [3, 9, 10], [3, 10, 4], [1, 7, 11], [1, 11, 5], [1, 7, 8], [1, 8, 2], [2, 8, 11], [2, 11, 5]], convexity = 1); +} diff --git a/tests/regression/dumptest/polyhedron-expected.txt b/tests/regression/dumptest/polyhedron-expected.txt new file mode 100644 index 0000000..c37a529 --- /dev/null +++ b/tests/regression/dumptest/polyhedron-expected.txt @@ -0,0 +1,3 @@ +group() { + polyhedron(points = undef, triangles = undef, convexity = 1); +} diff --git a/tests/regression/dumptest/polyset-reduce-crash-expected.txt b/tests/regression/dumptest/polyset-reduce-crash-expected.txt new file mode 100644 index 0000000..50253ff --- /dev/null +++ b/tests/regression/dumptest/polyset-reduce-crash-expected.txt @@ -0,0 +1,17 @@ +group() { + multmatrix([[0.809017, -0.587785, 0, 0], [0.587785, 0.809017, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + union() { + multmatrix([[1, 0, 0, 1], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + polygon(points = [[18.7661, -2.97225], [16.5303, -3.96857], [18.475, -4.43546]], paths = undef, convexity = 1); + polygon(points = [[18.7661, -2.97225], [16.7907, -2.65939], [16.5303, -3.96857]], paths = undef, convexity = 1); + polygon(points = [[19, 0], [17, 0], [16.7907, -2.65939]], paths = undef, convexity = 1); + polygon(points = [[19, 0], [16.7907, -2.65939], [18.7661, -2.97225]], paths = undef, convexity = 1); + polygon(points = [[19, 0], [17, 0], [16.7907, 2.65939]], paths = undef, convexity = 1); + polygon(points = [[19, 0], [16.7907, 2.65939], [18.7661, 2.97225]], paths = undef, convexity = 1); + polygon(points = [[18.7661, 2.97225], [16.7907, 2.65939], [16.5303, 3.96857]], paths = undef, convexity = 1); + polygon(points = [[18.7661, 2.97225], [16.5303, 3.96857], [18.475, 4.43546]], paths = undef, convexity = 1); + } + circle($fn = 0, $fa = 12, $fs = 1, r = 20); + } + } +} diff --git a/tests/regression/dumptest/projection-expected.txt b/tests/regression/dumptest/projection-expected.txt new file mode 100644 index 0000000..d776c2e --- /dev/null +++ b/tests/regression/dumptest/projection-expected.txt @@ -0,0 +1,3 @@ +group() { + projection(cut = false, convexity = 0); +} diff --git a/tests/regression/dumptest/projection-tests-expected.txt b/tests/regression/dumptest/projection-tests-expected.txt new file mode 100644 index 0000000..836c115 --- /dev/null +++ b/tests/regression/dumptest/projection-tests-expected.txt @@ -0,0 +1,25 @@ +group() { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + projection(cut = false, convexity = 0) { + sphere($fn = 0, $fa = 12, $fs = 1, r = 10); + } + } + multmatrix([[1, 0, 0, 22], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + projection(cut = true, convexity = 0) { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 9], [0, 0, 0, 1]]) { + sphere($fn = 0, $fa = 12, $fs = 1, r = 10); + } + } + } + } + multmatrix([[1, 0, 0, 44], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + linear_extrude(file = "", cache = "0.0", layer = "", height = 20, origin = [ 0 0 ], scale = 1, center = false, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + projection(cut = true, convexity = 0) { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 7], [0, 0, 0, 1]]) { + sphere($fn = 0, $fa = 12, $fs = 1, r = 10); + } + } + } + } +} diff --git a/tests/regression/dumptest/render-expected.txt b/tests/regression/dumptest/render-expected.txt new file mode 100644 index 0000000..c599c6b --- /dev/null +++ b/tests/regression/dumptest/render-expected.txt @@ -0,0 +1,3 @@ +group() { + render(convexity = 1); +} diff --git a/tests/regression/dumptest/rotate-expected.txt b/tests/regression/dumptest/rotate-expected.txt new file mode 100644 index 0000000..87e28e2 --- /dev/null +++ b/tests/regression/dumptest/rotate-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/rotate_extrude-expected.txt b/tests/regression/dumptest/rotate_extrude-expected.txt new file mode 100644 index 0000000..2d65d35 --- /dev/null +++ b/tests/regression/dumptest/rotate_extrude-expected.txt @@ -0,0 +1,3 @@ +group() { + rotate_extrude(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/rotate_extrude-tests-expected.txt b/tests/regression/dumptest/rotate_extrude-tests-expected.txt new file mode 100644 index 0000000..068d32c --- /dev/null +++ b/tests/regression/dumptest/rotate_extrude-tests-expected.txt @@ -0,0 +1,7 @@ +group() { + rotate_extrude(file = "", cache = "0.0", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1) { + multmatrix([[1, 0, 0, 20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + circle($fn = 0, $fa = 12, $fs = 1, r = 10); + } + } +} diff --git a/tests/regression/dumptest/scale-expected.txt b/tests/regression/dumptest/scale-expected.txt new file mode 100644 index 0000000..87e28e2 --- /dev/null +++ b/tests/regression/dumptest/scale-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/sphere-expected.txt b/tests/regression/dumptest/sphere-expected.txt new file mode 100644 index 0000000..bc8bc7b --- /dev/null +++ b/tests/regression/dumptest/sphere-expected.txt @@ -0,0 +1,3 @@ +group() { + sphere($fn = 0, $fa = 12, $fs = 1, r = 1); +} diff --git a/tests/regression/dumptest/sphere-tests-expected.txt b/tests/regression/dumptest/sphere-tests-expected.txt new file mode 100644 index 0000000..a672c23 --- /dev/null +++ b/tests/regression/dumptest/sphere-tests-expected.txt @@ -0,0 +1,12 @@ +group() { + sphere($fn = 0, $fa = 12, $fs = 1, r = 5); + multmatrix([[1, 0, 0, 0], [0, 1, 0, 12], [0, 0, 1, 0], [0, 0, 0, 1]]) { + sphere($fn = 0, $fa = 5, $fs = 0.5, r = 5); + } + multmatrix([[1, 0, 0, 12], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + sphere($fn = 0, $fa = 12, $fs = 1, r = 6); + } + multmatrix([[1, 0, 0, 12], [0, 1, 0, 12], [0, 0, 1, 0], [0, 0, 0, 1]]) { + sphere($fn = 5, $fa = 12, $fs = 1, r = 6); + } +} diff --git a/tests/regression/dumptest/square-expected.txt b/tests/regression/dumptest/square-expected.txt new file mode 100644 index 0000000..a4a24d8 --- /dev/null +++ b/tests/regression/dumptest/square-expected.txt @@ -0,0 +1,3 @@ +group() { + square(size = [1, 1], center = false); +} diff --git a/tests/regression/dumptest/string-test-expected.txt b/tests/regression/dumptest/string-test-expected.txt new file mode 100644 index 0000000..434cc8f --- /dev/null +++ b/tests/regression/dumptest/string-test-expected.txt @@ -0,0 +1,3 @@ +group() { + group(); +} diff --git a/tests/regression/dumptest/subdiv-expected.txt b/tests/regression/dumptest/subdiv-expected.txt new file mode 100644 index 0000000..bc40dc2 --- /dev/null +++ b/tests/regression/dumptest/subdiv-expected.txt @@ -0,0 +1,3 @@ +group() { + subdiv(level = 1, convexity = 0); +} diff --git a/tests/regression/dumptest/surface-expected.txt b/tests/regression/dumptest/surface-expected.txt new file mode 100644 index 0000000..4c9b5b8 --- /dev/null +++ b/tests/regression/dumptest/surface-expected.txt @@ -0,0 +1,3 @@ +group() { + surface(file = "", center = false); +} diff --git a/tests/regression/dumptest/surface-tests-expected.txt b/tests/regression/dumptest/surface-tests-expected.txt new file mode 100644 index 0000000..9786d85 --- /dev/null +++ b/tests/regression/dumptest/surface-tests-expected.txt @@ -0,0 +1,3 @@ +group() { + surface(file = "/Users/kintel/code/metalab/checkout/OpenSCAD/openscad-visitor/testdata/scad/surface.dat", center = false); +} diff --git a/tests/regression/dumptest/transform-insert-expected.txt b/tests/regression/dumptest/transform-insert-expected.txt new file mode 100644 index 0000000..7755901 --- /dev/null +++ b/tests/regression/dumptest/transform-insert-expected.txt @@ -0,0 +1,3 @@ +group() { + import_dxf(file = "/Users/kintel/code/OpenSCAD/openscad-visitor/testdata/scad/minimal/transform-insert.dxf", cache = "4c34b4bc.4329", layer = "", origin = [ 0 0 ], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 1); +} diff --git a/tests/regression/dumptest/translate-expected.txt b/tests/regression/dumptest/translate-expected.txt new file mode 100644 index 0000000..87e28e2 --- /dev/null +++ b/tests/regression/dumptest/translate-expected.txt @@ -0,0 +1,3 @@ +group() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]); +} diff --git a/tests/regression/dumptest/union-expected.txt b/tests/regression/dumptest/union-expected.txt new file mode 100644 index 0000000..34f53c2 --- /dev/null +++ b/tests/regression/dumptest/union-expected.txt @@ -0,0 +1,3 @@ +group() { + union(); +} diff --git a/tests/test_cmdline_tool.py b/tests/test_cmdline_tool.py new file mode 100755 index 0000000..8fe4890 --- /dev/null +++ b/tests/test_cmdline_tool.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +# +# Regression test driver for cmd-line tools +# +# Usage: test_cmdline_tool.py [<options>] <tool> <arguments> +# +# If the -g option is given or the TEST_GENERATE environment variable is set to 1, +# *-expected.<suffix> files will be generated instead of running the tests. +# +# Any generated output is written to the file `basename <argument`-actual.<suffix> +# Any warning or errors are written to stderr. +# +# Returns 0 on passed test +# 1 on error +# 2 on invalid cmd-line options +# +# Author: Marius Kintel <marius@kintel.net> +# + +import sys +import os +import glob +import subprocess +import re +import getopt + +def initialize_environment(): + if not options.generate: options.generate = bool(os.getenv("TEST_GENERATE")) + return True + +def init_expected_filename(testname, cmd): + global expecteddir, expectedfilename + expecteddir = os.path.join(options.regressiondir, os.path.split(cmd)[1]) + expectedfilename = os.path.join(expecteddir, testname + "-expected" + options.suffix) + +def verify_test(testname, cmd): + global expectedfilename + if not options.generate: + if not os.path.isfile(expectedfilename): + print >> sys.stderr, "Error: test '%s' is missing expected output in %s" % (testname, expectedfilename) + return False + return True + +def execute_and_redirect(cmd, params, outfile): + proc = subprocess.Popen([cmd] + params, stdout=outfile) + retval = proc.wait() + return retval + +def get_normalized_text(filename): + text = open(filename).read() + return text.strip("\r\n").replace("\r\n", "\n") + "\n" + +def compare_text(expected, actual): + return get_normalized_text(expected) == get_normalized_text(actual) + +def compare_with_expected(resultfilename): + if not options.generate: + if not compare_text(expectedfilename, resultfilename): + execute_and_redirect("diff", [expectedfilename, resultfilename], sys.stderr) + return False + return True + +def run_test(testname, cmd, args): + cmdname = os.path.split(options.cmd)[1] + + outputdir = os.path.join(os.getcwd(), cmdname + "-output") + actualfilename = os.path.join(outputdir, testname + "-actual" + options.suffix) + + if options.generate: + if not os.path.exists(expecteddir): os.makedirs(expecteddir) + outputname = expectedfilename + else: + if not os.path.exists(outputdir): os.makedirs(outputdir) + outputname = actualfilename + outfile = open(outputname, "wb") + try: + proc = subprocess.Popen([cmd] + args, stdout=outfile, stderr=subprocess.PIPE) + errtext = proc.communicate()[1] + if errtext != None and len(errtext) > 0: + print >> sys.stderr, "Error output: " + errtext + outfile.close() + if proc.returncode != 0: + print >> sys.stderr, "Error: %s failed with return code %d" % (cmdname, proc.returncode) + return None + + return outputname + except OSError, err: + print >> sys.stderr, "Error: %s \"%s\"" % (err.strerror, cmd) + return None + +class Options: + def __init__(self): + self.__dict__['options'] = {} + def __setattr__(self, name, value): + self.options[name] = value + def __getattr__(self, name): + return self.options[name] + +def usage(): + print >> sys.stderr, "Usage: " + sys.argv[0] + " [<options>] <cmdline-tool> <argument>" + print >> sys.stderr, "Options:" + print >> sys.stderr, " -g, --generate Generate expected output for the given tests" + print >> sys.stderr, " -s, --suffix=<suffix> Write -expected and -actual files with the given suffix instead of .txt" + print >> sys.stderr, " -t, --test=<name> Specify test name instead of deducting it from the argument" + +if __name__ == '__main__': + # Handle command-line arguments + try: + opts, args = getopt.getopt(sys.argv[1:], "gs:t:", ["generate", "suffix=", "test="]) + except getopt.GetoptError, err: + usage() + sys.exit(2) + + global options + options = Options() + options.regressiondir = os.path.join(os.path.split(sys.argv[0])[0], "regression") + options.generate = False + options.suffix = ".txt" + for o, a in opts: + if o in ("-g", "--generate"): options.generate = True + elif o in ("-s", "--suffix"): + if a[0] == '.': options.suffix = "" + else: options.suffix = "." + options.suffix += a + elif o in ("-t", "--test"): + options.testname = a + + # <cmdline-tool> and <argument> + if len(args) < 2: + usage() + sys.exit(2) + options.cmd = args[0] + + # If only one test file, we can usually deduct the test name from the file + if len(args) == 2: + basename = os.path.splitext(args[1])[0] + path, options.testname = os.path.split(basename) + + if not hasattr(options, "testname"): + print >> sys.stderr, "Test name cannot be deducted from arguments. Specify test name using the -t option" + sys.exit(2) + + # Initialize and verify run-time environment + if not initialize_environment(): sys.exit(1) + + init_expected_filename(options.testname, options.cmd) + + # Verify test environment + verification = verify_test(options.testname, options.cmd) + + resultfile = run_test(options.testname, options.cmd, args[1:]) + + if not verification or not compare_with_expected(resultfile): exit(1) |