Implemented rotate_extrude, basic support for 2D CSG, fixed some linear_extrude issues

customizer
Marius Kintel 2013-11-05 01:20:27 -05:00
parent 064ee8f98a
commit d9ad3a60a0
12 changed files with 191 additions and 106 deletions

View File

@ -59,36 +59,36 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const
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, CGALEvaluator::CsgOp op)
void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op)
{
if (target.dim != 2 && target.dim != 3) {
assert(false && "Dimension of Nef polyhedron must be 2 or 3");
}
if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0])
if (target.isEmpty() && op != CGE_UNION) return; // empty op <something> => empty
if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op <something> => empty
if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects
CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
try {
switch (op) {
case CGE_UNION:
case OPENSCAD_UNION:
if (target.isEmpty()) target = src.copy();
else target += src;
break;
case CGE_INTERSECTION:
case OPENSCAD_INTERSECTION:
target *= src;
break;
case CGE_DIFFERENCE:
case OPENSCAD_DIFFERENCE:
target -= src;
break;
case CGE_MINKOWSKI:
case OPENSCAD_MINKOWSKI:
target.minkowski(src);
break;
}
}
catch (const CGAL::Failure_exception &e) {
// union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad
std::string opstr = op == CGE_UNION ? "union" : op == CGE_INTERSECTION ? "intersection" : op == CGE_DIFFERENCE ? "difference" : op == CGE_MINKOWSKI ? "minkowski" : "UNKNOWN";
std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN";
PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what());
// Errors can result in corrupt polyhedrons, so put back the old one
@ -99,7 +99,7 @@ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedr
/*!
*/
CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op)
CGAL_Nef_polyhedron CGALEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op)
{
CGAL_Nef_polyhedron N;
BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) {
@ -201,7 +201,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node)
{
// Based on resize() in Giles Bathgate's RapCAD (but not exactly)
CGAL_Nef_polyhedron N;
N = applyToChildren(node, CGE_UNION);
N = applyToChildren(node, OPENSCAD_UNION);
if ( N.isNull() || N.isEmpty() ) return N;
@ -277,7 +277,7 @@ Response CGALEvaluator::visit(State &state, const AbstractNode &node)
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
CGAL_Nef_polyhedron N;
if (!isCached(node)) N = applyToChildren(node, CGE_UNION);
if (!isCached(node)) N = applyToChildren(node, OPENSCAD_UNION);
else N = CGALCache::instance()->get(this->tree.getIdString(node));
addToParent(state, node, N);
}
@ -289,7 +289,7 @@ Response CGALEvaluator::visit(State &state, const AbstractIntersectionNode &node
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
CGAL_Nef_polyhedron N;
if (!isCached(node)) N = applyToChildren(node, CGE_INTERSECTION);
if (!isCached(node)) N = applyToChildren(node, OPENSCAD_INTERSECTION);
else N = CGALCache::instance()->get(this->tree.getIdString(node));
addToParent(state, node, N);
}
@ -302,21 +302,7 @@ Response CGALEvaluator::visit(State &state, const CsgNode &node)
if (state.isPostfix()) {
CGAL_Nef_polyhedron N;
if (!isCached(node)) {
CGALEvaluator::CsgOp op = CGE_UNION;
switch (node.type) {
case CSG_TYPE_UNION:
op = CGE_UNION;
break;
case CSG_TYPE_DIFFERENCE:
op = CGE_DIFFERENCE;
break;
case CSG_TYPE_INTERSECTION:
op = CGE_INTERSECTION;
break;
default:
assert(false);
}
N = applyToChildren(node, op);
N = applyToChildren(node, node.type);
}
else {
N = CGALCache::instance()->get(this->tree.getIdString(node));
@ -333,7 +319,7 @@ Response CGALEvaluator::visit(State &state, const TransformNode &node)
CGAL_Nef_polyhedron N;
if (!isCached(node)) {
// First union all children
N = applyToChildren(node, CGE_UNION);
N = applyToChildren(node, OPENSCAD_UNION);
if ( matrix_contains_infinity( node.matrix ) || matrix_contains_nan( node.matrix ) ) {
// due to the way parse/eval works we can't currently distinguish between NaN and Inf
PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object.");
@ -377,10 +363,10 @@ Response CGALEvaluator::visit(State &state, const CgaladvNode &node)
if (state.isPostfix()) {
CGAL_Nef_polyhedron N;
if (!isCached(node)) {
CGALEvaluator::CsgOp op;
OpenSCADOperator op;
switch (node.type) {
case MINKOWSKI:
op = CGE_MINKOWSKI;
op = OPENSCAD_MINKOWSKI;
N = applyToChildren(node, op);
break;
case GLIDE:

View File

@ -2,6 +2,7 @@
#define CGALEVALUATOR_H_
#include "visitor.h"
#include "enums.h"
#include "CGAL_Nef_polyhedron.h"
#include <string>
@ -30,8 +31,8 @@ public:
private:
void addToParent(const State &state, const AbstractNode &node, const CGAL_Nef_polyhedron &N);
bool isCached(const AbstractNode &node) const;
void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, CGALEvaluator::CsgOp op);
CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, CGALEvaluator::CsgOp op);
void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op);
CGAL_Nef_polyhedron applyToChildren(const AbstractNode &node, OpenSCADOperator op);
CGAL_Nef_polyhedron applyHull(const CgaladvNode &node);
CGAL_Nef_polyhedron applyResize(const CgaladvNode &node);

View File

@ -130,13 +130,13 @@ Response CSGTermEvaluator::visit(State &state, const CsgNode &node)
if (state.isPostfix()) {
CsgOp op = CSGT_UNION;
switch (node.type) {
case CSG_TYPE_UNION:
case OPENSCAD_UNION:
op = CSGT_UNION;
break;
case CSG_TYPE_DIFFERENCE:
case OPENSCAD_DIFFERENCE:
op = CSGT_DIFFERENCE;
break;
case CSG_TYPE_INTERSECTION:
case OPENSCAD_INTERSECTION:
op = CSGT_INTERSECTION;
break;
default:

View File

@ -7,10 +7,13 @@
#include "state.h"
#include "transformnode.h"
#include "linearextrudenode.h"
#include "rotateextrudenode.h"
#include "csgnode.h"
#include "clipper-utils.h"
#include "CGALEvaluator.h"
#include "CGALCache.h"
#include "PolySet.h"
#include "openscad.h" // get_fragments_from_r()
#include <boost/foreach.hpp>
@ -93,10 +96,26 @@ Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADO
chnode->progress_report();
}
ClipperLib::ClipType clipType;
switch (op) {
case OPENSCAD_UNION:
clipType = ClipperLib::ctUnion;
break;
case OPENSCAD_INTERSECTION:
clipType = ClipperLib::ctIntersection;
break;
case OPENSCAD_DIFFERENCE:
clipType = ClipperLib::ctDifference;
break;
default:
PRINTB("Error: Unknown boolean operation %d", int(op));
return NULL;
break;
}
ClipperLib::Clipper clipper;
clipper.AddPolygons(ClipperUtils::fromPolygon2d(sum), ClipperLib::ptSubject);
ClipperLib::Polygons result;
clipper.Execute(ClipperLib::ctUnion, result);
clipper.Execute(clipType, result);
if (result.size() == 0) return NULL;
@ -168,28 +187,49 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node)
return PruneTraversal;
}
Response GeometryEvaluator::visit(State &state, const CsgNode &node)
{
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
shared_ptr<const class Geometry> geom;
if (!isCached(node)) {
shared_ptr<const class Geometry> geom(applyToChildren(node, node.type));
shared_ptr<const Polygon2d> polygons = dynamic_pointer_cast<const Polygon2d>(geom);
assert(polygons);
addToParent(state, node, geom);
}
// FIXME: if 3d node, CGAL?
}
return ContinueTraversal;
}
Response GeometryEvaluator::visit(State &state, const TransformNode &node)
{
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
shared_ptr<const class Geometry> geom;
if (!isCached(node)) {
// First union all children
geom.reset(applyToChildren(node, CGE_UNION));
if (matrix_contains_infinity(node.matrix) || matrix_contains_nan(node.matrix)) {
// due to the way parse/eval works we can't currently distinguish between NaN and Inf
PRINT("Warning: Transformation matrix contains Not-a-Number and/or Infinity - removing object.");
geom.reset();
}
//FIXME: Handle 2D vs. 3D
shared_ptr<const Polygon2d> polygons = dynamic_pointer_cast<const Polygon2d>(geom);
if (polygons) {
// FIXME: Convert from mat3 to mat2: Transform2d mat2(node.matrix);
Transform2d mat2;
// polygons->transform(mat2);
}
else {
// FIXME: Handle 3D transfer
// First union all children
Geometry *geometry = applyToChildren(node, OPENSCAD_UNION);
Polygon2d *polygons = dynamic_cast<Polygon2d*>(geometry);
//FIXME: Handle 2D vs. 3D
if (polygons) {
Transform2d mat2;
mat2.matrix() <<
node.matrix(0,0), node.matrix(0,1), node.matrix(0,3),
node.matrix(1,0), node.matrix(1,1), node.matrix(1,3),
node.matrix(3,0), node.matrix(3,1), node.matrix(3,3);
polygons->transform(mat2);
geom.reset(polygons);
}
else {
// FIXME: Handle 3D transform
}
}
}
else {
@ -200,20 +240,11 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node)
return ContinueTraversal;
}
static Vector2d transform(const Vector2d &v, double rot, const Vector2d &scale)
{
return Vector2d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)),
scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180)));
}
static void transform_PolySet(PolySet &ps,
double height, double rot, const Vector2d &scale)
static void translate_PolySet(PolySet &ps, const Vector3d &translation)
{
BOOST_FOREACH(PolySet::Polygon &p, ps.polygons) {
BOOST_FOREACH(Vector3d &v, p) {
v = Vector3d(scale[0] * (v[0] * cos(rot*M_PI/180) + v[1] * sin(rot*M_PI/180)),
scale[1] * (v[0] * -sin(rot*M_PI/180) + v[1] * cos(rot*M_PI/180)),
height);
v += translation;
}
}
}
@ -224,20 +255,23 @@ static void add_slice(PolySet *ps, const Polygon2d &poly,
const Vector2d &scale1,
const Vector2d &scale2)
{
Eigen::Affine2d trans1(Eigen::Scaling(scale1) * Eigen::Rotation2D<double>(rot1*M_PI/180));
Eigen::Affine2d trans2(Eigen::Scaling(scale2) * Eigen::Rotation2D<double>(rot2*M_PI/180));
// FIXME: If scale2 == 0 we need to handle tessellation separately
bool splitfirst = sin(rot2 - rot1) >= 0.0;
BOOST_FOREACH(const Outline2d &o, poly.outlines()) {
Vector2d prev1 = transform(o[0], rot1, scale1);
Vector2d prev2 = transform(o[0], rot2, scale2);
Vector2d prev1 = trans1 * o[0];
Vector2d prev2 = trans2 * o[0];
for (size_t i=1;i<=o.size();i++) {
Vector2d curr1 = transform(o[i % o.size()], rot1, scale1);
Vector2d curr2 = transform(o[i % o.size()], rot2, scale2);
Vector2d curr1 = trans1 * o[i % o.size()];
Vector2d curr2 = trans2 * o[i % o.size()];
ps->append_poly();
if (splitfirst) {
ps->insert_vertex(prev1[0], prev1[1], h1);
ps->insert_vertex(curr1[0], curr1[1], h1);
ps->insert_vertex(curr2[0], curr2[1], h2);
ps->insert_vertex(curr1[0], curr1[1], h1);
if (scale2[0] > 0 || scale2[1] > 0) {
ps->append_poly();
ps->insert_vertex(curr2[0], curr2[1], h2);
@ -247,16 +281,16 @@ static void add_slice(PolySet *ps, const Polygon2d &poly,
}
else {
ps->insert_vertex(prev1[0], prev1[1], h1);
ps->insert_vertex(curr1[0], curr1[1], h1);
ps->insert_vertex(prev2[0], prev2[1], h2);
ps->insert_vertex(curr1[0], curr1[1], h1);
if (scale2[0] > 0 || scale2[1] > 0) {
ps->append_poly();
ps->insert_vertex(prev2[0], prev2[1], h2);
ps->insert_vertex(curr1[0], curr1[1], h1);
ps->insert_vertex(curr2[0], curr2[1], h2);
ps->insert_vertex(curr1[0], curr1[1], h1);
}
}
prev1 = curr1;
prev1 = curr1;
prev2 = curr2;
}
}
@ -286,8 +320,12 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d &
ps->append(*ps_bottom);
delete ps_bottom;
if (node.scale_x > 0 || node.scale_y > 0) {
PolySet *ps_top = poly.tessellate(); // top
transform_PolySet(*ps_top, h2, node.twist, Vector2d(node.scale_x, node.scale_y));
Polygon2d top_poly(poly);
Eigen::Affine2d trans(Eigen::Scaling(node.scale_x, node.scale_y) *
Eigen::Rotation2D<double>(node.twist*M_PI/180));
top_poly.transform(trans); // top
PolySet *ps_top = top_poly.tessellate();
translate_PolySet(*ps_top, Vector3d(0,0,h2));
ps->append(*ps_top);
delete ps_top;
}
@ -312,7 +350,7 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node)
{
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
shared_ptr<const class Geometry> geom(applyToChildren(node, CGE_UNION));
shared_ptr<const class Geometry> geom(applyToChildren(node, OPENSCAD_UNION));
shared_ptr<const Polygon2d> polygons = dynamic_pointer_cast<const Polygon2d>(geom);
assert(polygons);
Geometry *extruded = extrudePolygon(node, *polygons);
@ -322,6 +360,67 @@ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node)
return ContinueTraversal;
}
static void fill_ring(std::vector<Vector3d> &ring, const Outline2d &o, double a)
{
for (int i=0;i<o.size();i++) {
ring[i][0] = o[i][0] * sin(a);
ring[i][1] = o[i][0] * cos(a);
ring[i][2] = o[i][1];
}
}
/*!
Input to extrude should be clean. This means non-intersecting, correct winding order
etc., the input coming from a library like Clipper.
*/
static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &poly)
{
PolySet *ps = new PolySet();
ps->convexity = node.convexity;
BOOST_FOREACH(const Outline2d &o, poly.outlines()) {
double max_x = 0;
BOOST_FOREACH(const Vector2d &v, o) max_x = fmax(max_x, v[0]);
int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa);
std::vector<Vector3d> rings[2];
rings[0].reserve(o.size());
rings[1].reserve(o.size());
fill_ring(rings[0], o, -M_PI/2); // first ring
for (int j = 0; j <= fragments; j++) {
double a = ((j+1)*2*M_PI) / fragments - M_PI/2; // start on the X axis
fill_ring(rings[(j+1)%2], o, a);
for (size_t i=0;i<o.size();i++) {
ps->append_poly();
ps->insert_vertex(rings[j%2][i]);
ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]);
ps->insert_vertex(rings[j%2][(i+1)%o.size()]);
ps->append_poly();
ps->insert_vertex(rings[j%2][i]);
ps->insert_vertex(rings[(j+1)%2][i]);
ps->insert_vertex(rings[(j+1)%2][(i+1)%o.size()]);
}
}
}
return ps;
}
Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node)
{
if (state.isPrefix() && isCached(node)) return PruneTraversal;
if (state.isPostfix()) {
shared_ptr<const class Geometry> geom(applyToChildren(node, OPENSCAD_UNION));
shared_ptr<const Polygon2d> polygons = dynamic_pointer_cast<const Polygon2d>(geom);
assert(polygons);
Geometry *rotated = rotatePolygon(node, *polygons);
assert(rotated);
addToParent(state, node, shared_ptr<const class Geometry>(rotated));
}
return ContinueTraversal;
}
/*!
Handles non-leaf PolyNodes; extrusions, projection
*/

View File

@ -21,8 +21,10 @@ public:
virtual Response visit(State &state, const AbstractNode &node);
virtual Response visit(State &state, const AbstractPolyNode &node);
virtual Response visit(State &state, const LinearExtrudeNode &node);
virtual Response visit(State &state, const RotateExtrudeNode &node);
virtual Response visit(State &state, const LeafNode &node);
virtual Response visit(State &state, const TransformNode &node);
virtual Response visit(State &state, const CsgNode &node);
const Tree &getTree() const { return this->tree; }

View File

@ -7,7 +7,6 @@
typedef std::vector<Vector2d> Outline2d;
class Polygon2d : public Geometry
{
public:

View File

@ -3,18 +3,13 @@
#include "node.h"
#include "visitor.h"
enum csg_type_e {
CSG_TYPE_UNION,
CSG_TYPE_DIFFERENCE,
CSG_TYPE_INTERSECTION
};
#include "enums.h"
class CsgNode : public AbstractNode
{
public:
csg_type_e type;
CsgNode(const ModuleInstantiation *mi, csg_type_e type) : AbstractNode(mi), type(type) { }
OpenSCADOperator type;
CsgNode(const ModuleInstantiation *mi, OpenSCADOperator type) : AbstractNode(mi), type(type) { }
virtual Response accept(class State &state, Visitor &visitor) const {
return visitor.visit(state, *this);
}

View File

@ -36,8 +36,8 @@
class CsgModule : public AbstractModule
{
public:
csg_type_e type;
CsgModule(csg_type_e type) : type(type) { }
OpenSCADOperator type;
CsgModule(OpenSCADOperator type) : type(type) { }
virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const;
};
@ -57,13 +57,13 @@ std::string CsgNode::toString() const
std::string CsgNode::name() const
{
switch (this->type) {
case CSG_TYPE_UNION:
case OPENSCAD_UNION:
return "union";
break;
case CSG_TYPE_DIFFERENCE:
case OPENSCAD_DIFFERENCE:
return "difference";
break;
case CSG_TYPE_INTERSECTION:
case OPENSCAD_INTERSECTION:
return "intersection";
break;
default:
@ -74,8 +74,8 @@ std::string CsgNode::name() const
void register_builtin_csgops()
{
Builtins::init("union", new CsgModule(CSG_TYPE_UNION));
Builtins::init("difference", new CsgModule(CSG_TYPE_DIFFERENCE));
Builtins::init("intersection", new CsgModule(CSG_TYPE_INTERSECTION));
Builtins::init("union", new CsgModule(OPENSCAD_UNION));
Builtins::init("difference", new CsgModule(OPENSCAD_DIFFERENCE));
Builtins::init("intersection", new CsgModule(OPENSCAD_INTERSECTION));
}

View File

@ -2,10 +2,10 @@
#define ENUMS_H_
enum OpenSCADOperator {
CGE_UNION,
CGE_INTERSECTION,
CGE_DIFFERENCE,
CGE_MINKOWSKI
OPENSCAD_UNION,
OPENSCAD_INTERSECTION,
OPENSCAD_DIFFERENCE,
OPENSCAD_MINKOWSKI
};
#endif

View File

@ -88,14 +88,24 @@ void PolySet::append_poly()
void PolySet::append_vertex(double x, double y, double z)
{
grid.align(x, y, z);
polygons.back().push_back(Vector3d(x, y, z));
append_vertex(Vector3d(x, y, z));
}
void PolySet::append_vertex(Vector3d v)
{
grid.align(v[0], v[1], v[2]);
polygons.back().push_back(v);
}
void PolySet::insert_vertex(double x, double y, double z)
{
grid.align(x, y, z);
polygons.back().insert(polygons.back().begin(), Vector3d(x, y, z));
insert_vertex(Vector3d(x, y, z));
}
void PolySet::insert_vertex(Vector3d v)
{
grid.align(v[0], v[1], v[2]);
polygons.back().insert(polygons.back().begin(), v);
}
static void gl_draw_triangle(GLint *shaderinfo, const Vector3d &p0, const Vector3d &p1, const Vector3d &p2, bool e0, bool e1, bool e2, double z, bool mirrored)

View File

@ -32,7 +32,9 @@ public:
bool empty() const { return polygons.size() == 0; }
void append_poly();
void append_vertex(double x, double y, double z = 0.0);
void append_vertex(Vector3d v);
void insert_vertex(double x, double y, double z = 0.0);
void insert_vertex(Vector3d v);
void append(const PolySet &ps);
void render_surface(Renderer::csgmode_e csgmode, const Transform3d &m, GLint *shaderinfo = NULL) const;

View File

@ -36,6 +36,7 @@
#include "builtin.h"
#include "Tree.h"
#include "CGAL_Nef_polyhedron.h"
#include "GeometryEvaluator.h"
#include "CGALEvaluator.h"
#include "PolySetCGALEvaluator.h"
#include "CGALCache.h"
@ -59,15 +60,6 @@ std::string currentdir;
using std::string;
void cgalTree(Tree &tree)
{
assert(tree.root());
CGALEvaluator evaluator(tree);
Traverser evaluate(evaluator, *tree.root(), Traverser::PRE_AND_POSTFIX);
evaluate.execute();
}
po::variables_map parse_options(int argc, char *argv[])
{
po::options_description desc("Allowed options");
@ -152,15 +144,14 @@ int main(int argc, char **argv)
Tree tree(root_node);
CGALEvaluator cgalevaluator(tree);
PolySetCGALEvaluator psevaluator(cgalevaluator);
GeometryEvaluator geomevaluator(tree);
print_messages_push();
std::cout << "First evaluation:\n";
CGAL_Nef_polyhedron N = cgalevaluator.evaluateCGALMesh(*root_node);
CGAL_Nef_polyhedron N = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node);
std::cout << "Second evaluation:\n";
CGAL_Nef_polyhedron N2 = cgalevaluator.evaluateCGALMesh(*root_node);
CGAL_Nef_polyhedron N2 = geomevaluator.cgalevaluator->evaluateCGALMesh(*root_node);
// FIXME:
// Evaluate again to make cache kick in
// Record printed output and compare it