summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CGAL_renderer.h660
-rw-r--r--src/EventFilter.h28
-rw-r--r--src/GLView.h81
-rw-r--r--src/MainWindow.h142
-rw-r--r--src/Preferences.cc208
-rw-r--r--src/Preferences.h54
-rw-r--r--src/context.cc103
-rw-r--r--src/control.cc166
-rw-r--r--src/csgops.cc162
-rw-r--r--src/csgterm.cc207
-rw-r--r--src/dxfdata.cc514
-rw-r--r--src/dxfdim.cc187
-rw-r--r--src/dxflinextrude.cc359
-rw-r--r--src/dxfrotextrude.cc251
-rw-r--r--src/dxftess-cgal.cc103
-rw-r--r--src/dxftess-glu.cc376
-rw-r--r--src/dxftess.cc54
-rw-r--r--src/export.cc151
-rw-r--r--src/expr.cc179
-rw-r--r--src/func.cc247
-rw-r--r--src/glview.cc491
-rw-r--r--src/highlighter.cc46
-rw-r--r--src/import.cc223
-rw-r--r--src/mainwin.cc1800
-rw-r--r--src/module.cc374
-rw-r--r--src/nef2dxf.cc67
-rw-r--r--src/openscad.cc257
-rw-r--r--src/openscad.h738
-rw-r--r--src/polyset.cc706
-rw-r--r--src/primitives.cc529
-rw-r--r--src/printutils.cc44
-rw-r--r--src/printutils.h19
-rw-r--r--src/qtcolorbutton/README.txt2
-rw-r--r--src/qtcolorbutton/qtcolorbutton.cpp278
-rw-r--r--src/qtcolorbutton/qtcolorbutton.h86
-rw-r--r--src/qtcolorbutton/qtcolorbutton.pri4
-rw-r--r--src/render.cc264
-rw-r--r--src/surface.cc201
-rw-r--r--src/transform.cc369
-rw-r--r--src/value.cc346
40 files changed, 11076 insertions, 0 deletions
diff --git a/src/CGAL_renderer.h b/src/CGAL_renderer.h
new file mode 100644
index 0000000..3c36db4
--- /dev/null
+++ b/src/CGAL_renderer.h
@@ -0,0 +1,660 @@
+// Copyright (c) 1997-2002 Max-Planck-Institute Saarbruecken (Germany).
+// All rights reserved.
+//
+// This file is part of CGAL (www.cgal.org); you may redistribute it under
+// the terms of the Q Public License version 1.0.
+// See the file LICENSE.QPL distributed with CGAL.
+//
+// Licensees holding a valid commercial license may use this file in
+// accordance with the commercial license agreement provided with the software.
+//
+// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// $URL: svn+ssh://scm.gforge.inria.fr/svn/cgal/branches/CGAL-3.5-branch/Nef_3/include/CGAL/Nef_3/OGL_helper.h $
+// $Id: OGL_helper.h 44713 2008-08-01 15:38:58Z hachenb $
+//
+//
+// Author(s) : Peter Hachenberger <hachenberger@mpi-sb.mpg.de>
+
+#ifndef CGAL_NEF_OPENGL_HELPER_H
+#define CGAL_NEF_OPENGL_HELPER_H
+
+#include <CGAL/Nef_S2/OGL_base_object.h>
+#include <CGAL/Simple_cartesian.h>
+#include <CGAL/Nef_3/SNC_decorator.h>
+#include <qgl.h>
+#include <cstdlib>
+
+#ifdef _WIN32
+#define CGAL_GLU_TESS_CALLBACK CALLBACK
+#else
+#define CGAL_GLU_TESS_CALLBACK
+#endif
+
+#ifdef __APPLE__
+# include <AvailabilityMacros.h>
+#endif
+
+#if defined __APPLE__ && !defined MAC_OS_X_VERSION_10_5
+#define CGAL_GLU_TESS_DOTS ...
+#else
+#define CGAL_GLU_TESS_DOTS
+#endif
+
+using namespace CGAL;
+using namespace CGAL::OGL;
+
+namespace OpenSCAD {
+
+ namespace OGL {
+
+// ----------------------------------------------------------------------------
+// Drawable double types:
+// ----------------------------------------------------------------------------
+
+ typedef CGAL::Simple_cartesian<double> DKernel;
+ typedef DKernel::Point_3 Double_point;
+ typedef DKernel::Vector_3 Double_vector;
+ typedef DKernel::Segment_3 Double_segment;
+ typedef DKernel::Aff_transformation_3 Affine_3;
+
+ // DPoint = a double point including a mark
+ class DPoint : public Double_point {
+ bool m_;
+ public:
+ DPoint() {}
+ DPoint(const Double_point& p, bool m) : Double_point(p) { m_ = m; }
+ DPoint(const DPoint& p) : Double_point(p) { m_ = p.m_; }
+ DPoint& operator=(const DPoint& p)
+ { Double_point::operator=(p); m_ = p.m_; return *this; }
+ bool mark() const { return m_; }
+ };
+
+ // DSegment = a double segment including a mark
+ class DSegment : public Double_segment {
+ bool m_;
+ public:
+ DSegment() {}
+ DSegment(const Double_segment& s, bool m) : Double_segment(s) { m_ = m; }
+ DSegment(const DSegment& s) : Double_segment(s) { m_ = s.m_; }
+ DSegment& operator=(const DSegment& s)
+ { Double_segment::operator=(s); m_ = s.m_; return *this; }
+ bool mark() const { return m_; }
+ };
+
+ // Double_triple = a class that stores a triple of double
+ // coordinates; we need a pointer to the coordinates in a C array
+ // for OpenGL
+ class Double_triple {
+ typedef double* double_ptr;
+ typedef const double* const_double_ptr;
+ double coords_[3];
+ public:
+ Double_triple()
+ { coords_[0]=coords_[1]=coords_[2]=0.0; }
+ Double_triple(double x, double y, double z)
+ { coords_[0]=x; coords_[1]=y; coords_[2]=z; }
+ Double_triple(const Double_triple& t)
+ { coords_[0]=t.coords_[0];
+ coords_[1]=t.coords_[1];
+ coords_[2]=t.coords_[2];
+ }
+ Double_triple& operator=(const Double_triple& t)
+ { coords_[0]=t.coords_[0];
+ coords_[1]=t.coords_[1];
+ coords_[2]=t.coords_[2];
+ return *this; }
+ operator double_ptr() const
+ { return const_cast<Double_triple&>(*this).coords_; }
+ double operator[](unsigned i)
+ { CGAL_assertion(i<3); return coords_[i]; }
+ }; // Double_triple
+
+ static std::ostream& operator << (std::ostream& os,
+ const Double_triple& t)
+ { os << "(" << t[0] << "," << t[1] << "," << t[2] << ")";
+ return os; }
+
+
+ // DFacet stores the facet cycle vertices in a continuus C array
+ // of three double components, this is necessary due to the OpenGL
+ // tesselator input format !
+ class DFacet {
+ typedef std::vector<Double_triple> Coord_vector;
+ typedef std::vector<unsigned> Cycle_vector;
+ Coord_vector coords_; // stores all vertex coordinates
+ Cycle_vector fc_ends_; // stores entry points of facet cycles
+ Double_triple normal_; // stores normal and mark of facet
+ bool mark_;
+
+ public:
+ typedef Coord_vector::iterator Coord_iterator;
+ typedef Coord_vector::const_iterator Coord_const_iterator;
+
+ DFacet() {}
+
+ void push_back_vertex(double x, double y, double z)
+ { coords_.push_back(Double_triple(x,y,z)); }
+
+ DFacet(const DFacet& f)
+ { coords_ = f.coords_;
+ fc_ends_ = f.fc_ends_;
+ normal_ = f.normal_;
+ mark_ = f.mark_;
+ }
+
+ DFacet& operator=(const DFacet& f)
+ { coords_ = f.coords_;
+ fc_ends_ = f.fc_ends_;
+ normal_ = f.normal_;
+ mark_ = f.mark_;
+ return *this;
+ }
+
+ ~DFacet()
+ { coords_.clear(); fc_ends_.clear(); }
+
+ void push_back_vertex(const Double_point& p)
+ { push_back_vertex(p.x(),p.y(),p.z()); }
+
+ void set_normal(double x, double y, double z, bool m)
+ { double l = sqrt(x*x + y*y + z*z);
+ normal_ = Double_triple(x/l,y/l,z/l); mark_ = m; }
+
+ double dx() const { return normal_[0]; }
+ double dy() const { return normal_[1]; }
+ double dz() const { return normal_[2]; }
+ bool mark() const { return mark_; }
+ double* normal() const
+ { return static_cast<double*>(normal_); }
+
+ void new_facet_cycle()
+ { fc_ends_.push_back(coords_.size()); }
+
+ unsigned number_of_facet_cycles() const
+ { return fc_ends_.size(); }
+
+ Coord_iterator facet_cycle_begin(unsigned i)
+ { CGAL_assertion(i<number_of_facet_cycles());
+ if (i==0) return coords_.begin();
+ else return coords_.begin()+fc_ends_[i]; }
+
+ Coord_iterator facet_cycle_end(unsigned i)
+ { CGAL_assertion(i<number_of_facet_cycles());
+ if (i<fc_ends_.size()-1) return coords_.begin()+fc_ends_[i+1];
+ else return coords_.end(); }
+
+ Coord_const_iterator facet_cycle_begin(unsigned i) const
+ { CGAL_assertion(i<number_of_facet_cycles());
+ if (i==0) return coords_.begin();
+ else return coords_.begin()+fc_ends_[i]; }
+
+ Coord_const_iterator facet_cycle_end(unsigned i) const
+ { CGAL_assertion(i<number_of_facet_cycles());
+ if (i<fc_ends_.size()-1) return coords_.begin()+fc_ends_[i+1];
+ else return coords_.end(); }
+
+ void debug(std::ostream& os = std::cerr) const
+ { os << "DFacet, normal=" << normal_ << ", mark=" << mark() << std::endl;
+ for(unsigned i=0; i<number_of_facet_cycles(); ++i) {
+ os << " facet cycle ";
+ // put all vertices in facet cycle into contour:
+ Coord_const_iterator cit;
+ for(cit = facet_cycle_begin(i); cit != facet_cycle_end(i); ++cit)
+ os << *cit;
+ os << std::endl;
+ }
+ }
+
+ }; // DFacet
+
+
+// ----------------------------------------------------------------------------
+// OGL Drawable Polyhedron:
+// ----------------------------------------------------------------------------
+
+ inline void CGAL_GLU_TESS_CALLBACK beginCallback(GLenum which)
+ { glBegin(which); }
+
+ inline void CGAL_GLU_TESS_CALLBACK endCallback(void)
+ { glEnd(); }
+
+ inline void CGAL_GLU_TESS_CALLBACK errorCallback(GLenum errorCode)
+ { const GLubyte *estring;
+ estring = gluErrorString(errorCode);
+ fprintf(stderr, "Tessellation Error: %s\n", estring);
+ std::exit (0);
+ }
+
+ inline void CGAL_GLU_TESS_CALLBACK vertexCallback(GLvoid* vertex,
+ GLvoid* user)
+ { GLdouble* pc(static_cast<GLdouble*>(vertex));
+ GLdouble* pu(static_cast<GLdouble*>(user));
+ // CGAL_NEF_TRACEN("vertexCallback coord "<<pc[0]<<","<<pc[1]<<","<<pc[2]);
+ // CGAL_NEF_TRACEN("vertexCallback normal "<<pu[0]<<","<<pu[1]<<","<<pu[2]);
+ glNormal3dv(pu);
+ glVertex3dv(pc);
+ }
+
+ inline void CGAL_GLU_TESS_CALLBACK combineCallback(GLdouble coords[3], GLvoid *d[4], GLfloat w[4], GLvoid **dataOut)
+ {
+ static std::list<GLdouble*> pcache;
+ if (dataOut) {
+ GLdouble *n = new GLdouble[3];
+ n[0] = coords[0];
+ n[1] = coords[1];
+ n[2] = coords[2];
+ pcache.push_back(n);
+ *dataOut = n;
+ } else {
+ for (std::list<GLdouble*>::const_iterator i = pcache.begin(); i != pcache.end(); i++)
+ delete[] *i;
+ pcache.clear();
+ }
+ }
+
+
+ enum { SNC_AXES};
+ enum { SNC_BOUNDARY, SNC_SKELETON };
+
+ class Polyhedron : public CGAL::OGL::OGL_base_object {
+ public:
+ std::list<DPoint> vertices_;
+ std::list<DSegment> edges_;
+ std::list<DFacet> halffacets_;
+
+ GLuint object_list_;
+ bool init_;
+
+ Bbox_3 bbox_;
+
+ int style;
+ std::vector<bool> switches;
+
+ typedef std::list<DPoint>::const_iterator Vertex_iterator;
+ typedef std::list<DSegment>::const_iterator Edge_iterator;
+ typedef std::list<DFacet>::const_iterator Halffacet_iterator;
+
+ enum RenderColor {
+ CGAL_NEF3_MARKED_VERTEX_COLOR,
+ CGAL_NEF3_MARKED_EDGE_COLOR,
+ CGAL_NEF3_MARKED_FACET_COLOR,
+ CGAL_NEF3_UNMARKED_VERTEX_COLOR,
+ CGAL_NEF3_UNMARKED_EDGE_COLOR,
+ CGAL_NEF3_UNMARKED_FACET_COLOR,
+ NUM_COLORS
+ };
+ static unsigned char colors[NUM_COLORS][3];
+ public:
+ Polyhedron() : bbox_(-1,-1,-1,1,1,1), switches(1) {
+ object_list_ = 0;
+ init_ = false;
+ style = SNC_BOUNDARY;
+ switches[SNC_AXES] = false;
+ }
+
+ ~Polyhedron()
+ { if (object_list_) glDeleteLists(object_list_, 4); }
+
+ void push_back(const Double_point& p, bool m) {
+ vertices_.push_back(DPoint(p,m));
+ }
+ void push_back(const Double_segment& s, bool m)
+ { edges_.push_back(DSegment(s,m)); }
+ void push_back(const DFacet& f)
+ { halffacets_.push_back(f); }
+
+ void toggle(int index) {
+ switches[index] = !switches[index];
+ }
+
+ void set_style(int index) {
+ style = index;
+ }
+
+ bool is_initialized() const { return init_; }
+
+ Bbox_3 bbox() const { return bbox_; }
+ Bbox_3& bbox() { return bbox_; }
+
+ void draw(Vertex_iterator v) const {
+ // CGAL_NEF_TRACEN("drawing vertex "<<*v);
+ unsigned char *c = v->mark() ? colors[CGAL_NEF3_UNMARKED_VERTEX_COLOR] : colors[CGAL_NEF3_MARKED_VERTEX_COLOR];
+ glPointSize(10);
+ glColor3ubv(c);
+ glBegin(GL_POINTS);
+ glVertex3d(v->x(),v->y(),v->z());
+#ifdef CGAL_NEF_EMPHASIZE_VERTEX
+ glColor3ub(255,0,0);
+ glVertex3d(CGAL_NEF_EMPHASIZE_VERTEX);
+#endif
+ glEnd();
+ }
+
+ void draw(Edge_iterator e) const {
+ // CGAL_NEF_TRACEN("drawing edge "<<*e);
+ Double_point p = e->source(), q = e->target();
+ unsigned char *c = e->mark() ? colors[CGAL_NEF3_UNMARKED_EDGE_COLOR] : colors[CGAL_NEF3_MARKED_EDGE_COLOR];
+ glLineWidth(5);
+ glColor3ubv(c);
+ glBegin(GL_LINE_STRIP);
+ glVertex3d(p.x(), p.y(), p.z());
+ glVertex3d(q.x(), q.y(), q.z());
+ glEnd();
+ }
+
+ void draw(Halffacet_iterator f) const {
+ // CGAL_NEF_TRACEN("drawing facet "<<(f->debug(),""));
+ GLUtesselator* tess_ = gluNewTess();
+ gluTessCallback(tess_, GLenum(GLU_TESS_VERTEX_DATA),
+ (GLvoid (CGAL_GLU_TESS_CALLBACK *)(CGAL_GLU_TESS_DOTS)) &vertexCallback);
+ gluTessCallback(tess_, GLenum(GLU_TESS_COMBINE),
+ (GLvoid (CGAL_GLU_TESS_CALLBACK *)(CGAL_GLU_TESS_DOTS)) &combineCallback);
+ gluTessCallback(tess_, GLenum(GLU_TESS_BEGIN),
+ (GLvoid (CGAL_GLU_TESS_CALLBACK *)(CGAL_GLU_TESS_DOTS)) &beginCallback);
+ gluTessCallback(tess_, GLenum(GLU_TESS_END),
+ (GLvoid (CGAL_GLU_TESS_CALLBACK *)(CGAL_GLU_TESS_DOTS)) &endCallback);
+ gluTessCallback(tess_, GLenum(GLU_TESS_ERROR),
+ (GLvoid (CGAL_GLU_TESS_CALLBACK *)(CGAL_GLU_TESS_DOTS)) &errorCallback);
+ gluTessProperty(tess_, GLenum(GLU_TESS_WINDING_RULE),
+ GLU_TESS_WINDING_POSITIVE);
+
+ DFacet::Coord_const_iterator cit;
+ unsigned char *c = f->mark() ? colors[CGAL_NEF3_UNMARKED_FACET_COLOR] : colors[CGAL_NEF3_MARKED_FACET_COLOR];
+ glColor3ubv(c);
+ gluTessBeginPolygon(tess_,f->normal());
+ // CGAL_NEF_TRACEN(" ");
+ // CGAL_NEF_TRACEN("Begin Polygon");
+ gluTessNormal(tess_,f->dx(),f->dy(),f->dz());
+ // forall facet cycles of f:
+ for(unsigned i = 0; i < f->number_of_facet_cycles(); ++i) {
+ gluTessBeginContour(tess_);
+ // CGAL_NEF_TRACEN(" Begin Contour");
+ // put all vertices in facet cycle into contour:
+ for(cit = f->facet_cycle_begin(i);
+ cit != f->facet_cycle_end(i); ++cit) {
+ gluTessVertex(tess_, *cit, *cit);
+ // CGAL_NEF_TRACEN(" add Vertex");
+ }
+ gluTessEndContour(tess_);
+ // CGAL_NEF_TRACEN(" End Contour");
+ }
+ gluTessEndPolygon(tess_);
+ // CGAL_NEF_TRACEN("End Polygon");
+ gluDeleteTess(tess_);
+ combineCallback(NULL, NULL, NULL, NULL);
+ }
+
+ void construct_axes() const
+ {
+ glLineWidth(2.0);
+ // red x-axis
+ glColor3f(1.0,0.0,0.0);
+ glBegin(GL_LINES);
+ glVertex3f(0.0,0.0,0.0);
+ glVertex3f(5000.0,0.0,0.0);
+ glEnd();
+ // green y-axis
+ glColor3f(0.0,1.0,0.0);
+ glBegin(GL_LINES);
+ glVertex3f(0.0,0.0,0.0);
+ glVertex3f(0.0,5000.0,0.0);
+ glEnd();
+ // blue z-axis and equator
+ glColor3f(0.0,0.0,1.0);
+ glBegin(GL_LINES);
+ glVertex3f(0.0,0.0,0.0);
+ glVertex3f(0.0,0.0,5000.0);
+ glEnd();
+ // six coordinate points in pink:
+ glPointSize(10);
+ glBegin(GL_POINTS);
+ glColor3f(1.0,0.0,0.0);
+ glVertex3d(5,0,0);
+ glColor3f(0.0,1.0,0.0);
+ glVertex3d(0,5,0);
+ glColor3f(0.0,0.0,1.0);
+ glVertex3d(0,0,5);
+ glEnd();
+ }
+
+
+ void fill_display_lists() {
+ glNewList(object_list_, GL_COMPILE);
+ Vertex_iterator v;
+ for(v=vertices_.begin();v!=vertices_.end();++v)
+ draw(v);
+ glEndList();
+
+ glNewList(object_list_+1, GL_COMPILE);
+ Edge_iterator e;
+ for(e=edges_.begin();e!=edges_.end();++e)
+ draw(e);
+ glEndList();
+
+ glNewList(object_list_+2, GL_COMPILE);
+ Halffacet_iterator f;
+ for(f=halffacets_.begin();f!=halffacets_.end();++f)
+ draw(f);
+ glEndList();
+
+ glNewList(object_list_+3, GL_COMPILE); // axes:
+ construct_axes();
+ glEndList();
+
+ }
+
+ void init() {
+ if (init_) return;
+ init_ = true;
+ switches[SNC_AXES] = false;
+ style = SNC_BOUNDARY;
+ object_list_ = glGenLists(4);
+ CGAL_assertion(object_list_);
+ fill_display_lists();
+ }
+
+
+ void draw() const
+ {
+ if (!is_initialized()) const_cast<Polyhedron&>(*this).init();
+ double l = (std::max)( (std::max)( bbox().xmax() - bbox().xmin(),
+ bbox().ymax() - bbox().ymin()),
+ bbox().zmax() - bbox().zmin());
+ if ( l < 1) // make sure that a single point doesn't screw up here
+ l = 1;
+ glScaled( 4.0/l, 4.0/l, 4.0/l);
+ glTranslated( -(bbox().xmax() + bbox().xmin()) / 2.0,
+ -(bbox().ymax() + bbox().ymin()) / 2.0,
+ -(bbox().zmax() + bbox().zmin()) / 2.0);
+ if (style == SNC_BOUNDARY) {
+ //glEnable(GL_LIGHTING);
+ glCallList(object_list_+2); // facets
+ //glDisable(GL_LIGHTING);
+ }
+ // move edges and vertices a bit towards the view-point,
+ // i.e., 1/100th of the unit vector in camera space
+ // double f = l / 4.0 / 100.0;
+ // glTranslated( z_vec[0] * f, z_vec[1] * f, z_vec[2] * f);
+ glCallList(object_list_+1); // edges
+ glCallList(object_list_); // vertices
+ if (switches[SNC_AXES]) glCallList(object_list_+3); // axis
+ }
+
+ void debug(std::ostream& os = std::cerr) const
+ {
+ os << "OGL::Polyhedron" << std::endl;
+ os << "Vertices:" << std::endl;
+ Vertex_iterator v;
+ for(v=vertices_.begin();v!=vertices_.end();++v)
+ os << " "<<*v<<", mark="<<v->mark()<<std::endl;
+ os << "Edges:" << std::endl;
+ Edge_iterator e;
+ for(e=edges_.begin();e!=edges_.end();++e)
+ os << " "<<*e<<", mark="<<e->mark()<<std::endl;
+ os << "Facets:" << std::endl;
+ Halffacet_iterator f;
+ for(f=halffacets_.begin();f!=halffacets_.end();++f)
+ f->debug(); os << std::endl;
+ os << std::endl;
+ }
+
+ }; // Polyhedron
+ unsigned char Polyhedron::colors[][3] = {
+ {0xb7, 0xe8, 0x5c},
+ {0xab, 0xd8, 0x56},
+ {0x9d, 0xcb, 0x51},
+ {0xff, 0xf6, 0x7c},
+ {0xff, 0xec, 0x5e},
+ {0xf9, 0xd7, 0x2c}
+ };
+
+ template<typename Nef_polyhedron>
+ class Nef3_Converter {
+ typedef typename Nef_polyhedron::SNC_structure SNC_structure;
+ typedef CGAL::SNC_decorator<SNC_structure> Base;
+ typedef CGAL::SNC_FM_decorator<SNC_structure> FM_decorator;
+
+ public:
+ typedef typename SNC_structure::Vertex_const_iterator Vertex_const_iterator;
+ typedef typename SNC_structure::Halfedge_const_iterator Halfedge_const_iterator;
+ typedef typename SNC_structure::Halffacet_const_iterator Halffacet_const_iterator;
+ typedef typename SNC_structure::Halffacet_cycle_const_iterator Halffacet_cycle_const_iterator;
+
+ typedef typename SNC_structure::Object_const_handle Object_const_handle;
+ typedef typename SNC_structure::SHalfedge_const_handle SHalfedge_const_handle;
+ typedef typename SNC_structure::SHalfloop_const_handle SHalfloop_const_handle;
+
+ typedef typename SNC_structure::Vertex_const_handle Vertex_const_handle;
+ typedef typename SNC_structure::Halfedge_const_handle Halfedge_const_handle;
+ typedef typename SNC_structure::Halffacet_const_handle Halffacet_const_handle;
+
+ typedef typename SNC_structure::Point_3 Point_3;
+ typedef typename SNC_structure::Vector_3 Vector_3;
+ typedef typename SNC_structure::Segment_3 Segment_3;
+ typedef typename SNC_structure::Plane_3 Plane_3;
+ typedef typename SNC_structure::Mark Mark;
+ typedef typename SNC_structure::SHalfedge_around_facet_const_circulator
+ SHalfedge_around_facet_const_circulator;
+
+ private:
+ static OGL::Double_point double_point(const Point_3& p)
+ { return OGL::Double_point(CGAL::to_double(p.x()),
+ CGAL::to_double(p.y()),
+ CGAL::to_double(p.z())); }
+
+ static OGL::Double_segment double_segment(const Segment_3& s)
+ { return OGL::Double_segment(double_point(s.source()),
+ double_point(s.target())); }
+
+ static void draw(Vertex_const_handle v, const Nef_polyhedron& ,
+ Polyhedron& P) {
+ Point_3 bp = v->point();
+ // CGAL_NEF_TRACEN("vertex " << bp);
+ P.push_back(double_point(bp), v->mark());
+ }
+
+ static void draw(Halfedge_const_handle e, const Nef_polyhedron& ,
+ Polyhedron& P) {
+ Vertex_const_handle s = e->source();
+ Vertex_const_handle t = e->twin()->source();
+ Segment_3 seg(s->point(),t->point());
+ // CGAL_NEF_TRACEN("edge " << seg);
+ P.push_back(double_segment(seg), e->mark());
+ }
+
+ static void draw(Halffacet_const_handle f, const Nef_polyhedron& ,
+ Polyhedron& P) {
+ OGL::DFacet g;
+ Halffacet_cycle_const_iterator fc; // all facet cycles:
+ CGAL_forall_facet_cycles_of(fc,f)
+ if ( fc.is_shalfedge() ) { // non-trivial facet cycle
+ g.new_facet_cycle();
+ SHalfedge_const_handle h = fc;
+ SHalfedge_around_facet_const_circulator hc(h), he(hc);
+ CGAL_For_all(hc,he){ // all vertex coordinates in facet cycle
+ Point_3 sp = hc->source()->source()->point();
+ // CGAL_NEF_TRACEN(" ");CGAL_NEF_TRACEN("facet" << sp);
+ g.push_back_vertex(double_point(sp));
+ }
+ }
+ Vector_3 v = f->plane().orthogonal_vector();
+ g.set_normal(CGAL::to_double(v.x()),
+ CGAL::to_double(v.y()),
+ CGAL::to_double(v.z()),
+ f->mark());
+ P.push_back(g);
+ }
+
+ // Returns the bounding box of the finite vertices of the polyhedron.
+ // Returns $[-1,+1]^3$ as bounding box if no finite vertex exists.
+
+ static Bbox_3 bounded_bbox(const Nef_polyhedron& N) {
+ bool first_vertex = true;
+ Bbox_3 bbox( -1.0, -1.0, -1.0, 1.0, 1.0, 1.0);
+ Vertex_const_iterator vi;
+ CGAL_forall_vertices(vi, N) {
+ Point_3 p = vi->point();
+ double x = CGAL::to_double(p.hx());
+ double y = CGAL::to_double(p.hy());
+ double z = CGAL::to_double(p.hz());
+ double w = CGAL::to_double(p.hw());
+ if (N.is_standard(vi)) {
+ if(first_vertex) {
+ bbox = Bbox_3(x/w, y/w, z/w, x/w, y/w, z/w);
+ first_vertex = false;
+ } else {
+ bbox = bbox + Bbox_3(x/w, y/w, z/w, x/w, y/w, z/w);
+ first_vertex = false;
+ }
+ }
+ }
+ return bbox;
+ }
+
+ static void set_R(Bbox_3& bbox, const Nef_polyhedron& N) {
+ if(N.is_standard_kernel()) return;
+ double size = abs(bbox.xmin());
+ if(size < bbox.xmax()) size = bbox.xmax();
+ if(size < bbox.ymin()) size = bbox.ymin();
+ if(size < bbox.ymax()) size = bbox.ymax();
+ if(size < bbox.zmin()) size = bbox.zmin();
+ if(size < bbox.zmax()) size = bbox.zmax();
+ N.set_size_of_infimaximal_box(size*50);
+ // CGAL_NEF_TRACEN("set infi box size to " << size);
+ Vertex_const_iterator vi;
+ CGAL_forall_vertices(vi, N)
+ if(N.is_standard(vi))
+ return;
+ bbox = Bbox_3(bbox.xmin()*10,bbox.ymin()*10,bbox.zmin()*10,
+ bbox.xmax()*10,bbox.ymax()*10,bbox.zmax()*10);
+ }
+ public:
+ static void setColor(Polyhedron::RenderColor color_index,
+ unsigned char r, unsigned char g, unsigned char b) {
+ assert(color_index < Polyhedron::NUM_COLORS);
+ Polyhedron::colors[color_index][0] = r;
+ Polyhedron::colors[color_index][1] = g;
+ Polyhedron::colors[color_index][2] = b;
+ }
+
+ static void convert_to_OGLPolyhedron(const Nef_polyhedron& N, Polyhedron* P) {
+ Bbox_3 bbox(bounded_bbox(N));
+ set_R(bbox,N);
+ P->bbox() = bbox;
+ Vertex_const_iterator v;
+ CGAL_forall_vertices(v,*N.sncp()) draw(v,N,*P);
+ Halfedge_const_iterator e;
+ CGAL_forall_edges(e,*N.sncp()) draw(e,N,*P);
+ Halffacet_const_iterator f;
+ CGAL_forall_facets(f,*N.sncp()) draw(f,N,*P);
+ }
+
+ }; // Nef3_Converter
+
+ } // namespace OGL
+
+} // namespace OpenSCAD
+
+#endif // CGAL_NEF_OPENGL_HELPER_H
diff --git a/src/EventFilter.h b/src/EventFilter.h
new file mode 100644
index 0000000..c3942b5
--- /dev/null
+++ b/src/EventFilter.h
@@ -0,0 +1,28 @@
+#ifndef FILTER_H_
+#define FILTER_H_
+
+#include <QObject>
+#include <QFileOpenEvent>
+#include "MainWindow.h"
+
+class EventFilter : public QObject
+{
+ Q_OBJECT;
+
+public:
+ EventFilter(QObject *parent) : QObject(parent) {}
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) {
+ // Handle Apple event for opening files
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent *foe = static_cast<QFileOpenEvent *>(event);
+ MainWindow::requestOpenFile(foe->file());
+ return true;
+ } else {
+ // standard event processing
+ return QObject::eventFilter(obj, event);
+ }
+ }
+};
+
+#endif
diff --git a/src/GLView.h b/src/GLView.h
new file mode 100644
index 0000000..dd896fc
--- /dev/null
+++ b/src/GLView.h
@@ -0,0 +1,81 @@
+#ifndef GLVIEW_H_
+#define GLVIEW_H_
+
+#ifdef ENABLE_OPENCSG
+// this must be included before the GL headers
+# include <GL/glew.h>
+#endif
+
+#include <QGLWidget>
+#include <QLabel>
+
+class GLView : public QGLWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(bool showAxes READ showAxes WRITE setShowAxes);
+ Q_PROPERTY(bool showCrosshairs READ showCrosshairs WRITE setShowCrosshairs);
+ Q_PROPERTY(bool orthoMode READ orthoMode WRITE setOrthoMode);
+
+public:
+ GLView(QWidget *parent = NULL);
+ void setRenderFunc(void (*func)(void*), void *userdata);
+#ifdef ENABLE_OPENCSG
+ bool hasOpenCSGSupport() { return this->opencsg_support; }
+#endif
+ // Properties
+ bool showAxes() const { return this->showaxes; }
+ void setShowAxes(bool enabled) { this->showaxes = enabled; }
+ bool showCrosshairs() const { return this->showcrosshairs; }
+ void setShowCrosshairs(bool enabled) { this->showcrosshairs = enabled; }
+ bool orthoMode() const { return this->orthomode; }
+ void setOrthoMode(bool enabled) { this->orthomode = enabled; }
+
+ QLabel *statusLabel;
+ double object_rot_x;
+ double object_rot_y;
+ double object_rot_z;
+ double object_trans_x;
+ double object_trans_y;
+ double object_trans_z;
+ GLint shaderinfo[11];
+
+private:
+ void (*renderfunc)(void*);
+ void *renderfunc_vp;
+
+ bool showaxes;
+ bool showcrosshairs;
+ bool orthomode;
+
+ double viewer_distance;
+
+ double w_h_ratio;
+
+#ifdef ENABLE_OPENCSG
+ bool opencsg_support;
+#endif
+
+ bool mouse_drag_active;
+ int last_mouse_x;
+ int last_mouse_y;
+
+ void keyPressEvent(QKeyEvent *event);
+ void wheelEvent(QWheelEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+ void mouseReleaseEvent(QMouseEvent *event);
+
+ void initializeGL();
+ void resizeGL(int w, int h);
+ void paintGL();
+
+#ifdef ENABLE_OPENCSG
+private slots:
+ void display_opengl20_warning();
+#endif
+
+signals:
+ void doAnimateUpdate();
+};
+
+#endif
diff --git a/src/MainWindow.h b/src/MainWindow.h
new file mode 100644
index 0000000..bb1bdc4
--- /dev/null
+++ b/src/MainWindow.h
@@ -0,0 +1,142 @@
+#ifndef MAINWINDOW_H_
+#define MAINWINDOW_H_
+
+#include <QMainWindow>
+#include "ui_MainWindow.h"
+#include "openscad.h"
+
+class MainWindow : public QMainWindow, public Ui::MainWindow
+{
+ Q_OBJECT
+
+public:
+ static QPointer<MainWindow> current_win;
+ static void requestOpenFile(const QString &filename);
+
+ QString fileName;
+ class Highlighter *highlighter;
+
+ class Preferences *prefs;
+
+ QTimer *animate_timer;
+ double tval, fps, fsteps;
+
+ Context root_ctx;
+ AbstractModule *root_module; // Result of parsing
+ ModuleInstantiation root_inst; // Top level instance
+ AbstractNode *absolute_root_node; // Result of tree evaluation
+ AbstractNode *root_node; // Root if the root modifier (!) is used
+
+ CSGTerm *root_raw_term; // Result of CSG term rendering
+ CSGTerm *root_norm_term; // Normalized CSG products
+ CSGChain *root_chain;
+#ifdef ENABLE_CGAL
+ CGAL_Nef_polyhedron *root_N;
+ bool recreate_cgal_ogl_p;
+ void *cgal_ogl_p;
+ PolySet *cgal_ogl_ps;
+#endif
+
+ QVector<CSGTerm*> highlight_terms;
+ CSGChain *highlights_chain;
+ QVector<CSGTerm*> background_terms;
+ CSGChain *background_chain;
+ QString last_compiled_doc;
+ bool enableOpenCSG;
+
+ static const int maxRecentFiles = 10;
+ QAction *actionRecentFile[maxRecentFiles];
+ QString examplesdir;
+
+ MainWindow(const char *filename = 0);
+ ~MainWindow();
+
+protected:
+ void closeEvent(QCloseEvent *event);
+
+private slots:
+ void updatedFps();
+ void updateTVal();
+ void setFileName(const QString &filename);
+ void setFont(const QString &family, uint size);
+
+private:
+ void openFile(const QString &filename);
+ void load();
+ AbstractNode *find_root_tag(AbstractNode *n);
+ void compile(bool procevents);
+ bool maybeSave();
+
+private slots:
+ void actionNew();
+ void actionOpen();
+ void actionOpenRecent();
+ void actionOpenExample();
+ void clearRecentFiles();
+ void updateRecentFileActions();
+ void actionSave();
+ void actionSaveAs();
+ void actionReload();
+
+private slots:
+ void editIndent();
+ void editUnindent();
+ void editComment();
+ void editUncomment();
+ void pasteViewportTranslation();
+ void pasteViewportRotation();
+ void hideEditor();
+ void preferences();
+
+private slots:
+ void actionReloadCompile();
+ void actionCompile();
+#ifdef ENABLE_CGAL
+ void actionRenderCGAL();
+#endif
+ void actionDisplayAST();
+ void actionDisplayCSGTree();
+ void actionDisplayCSGProducts();
+ void actionExportSTLorOFF(bool stl_mode);
+ void actionExportSTL();
+ void actionExportOFF();
+ void actionExportDXF();
+ void actionFlushCaches();
+
+public:
+ void viewModeActionsUncheck();
+
+public slots:
+#ifdef ENABLE_OPENCSG
+ void viewModeOpenCSG();
+#endif
+#ifdef ENABLE_CGAL
+ void viewModeCGALSurface();
+ void viewModeCGALGrid();
+#endif
+ void viewModeThrownTogether();
+ void viewModeShowEdges();
+ void viewModeShowAxes();
+ void viewModeShowCrosshairs();
+ void viewModeAnimate();
+ void viewAngleTop();
+ void viewAngleBottom();
+ void viewAngleLeft();
+ void viewAngleRight();
+ void viewAngleFront();
+ void viewAngleBack();
+ void viewAngleDiagonal();
+ void viewCenter();
+ void viewPerspective();
+ void viewOrthogonal();
+ void hideConsole();
+ void animateUpdateDocChanged();
+ void animateUpdate();
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dropEvent(QDropEvent *event);
+ void helpAbout();
+ void helpManual();
+ void quit();
+};
+
+#endif
diff --git a/src/Preferences.cc b/src/Preferences.cc
new file mode 100644
index 0000000..cfc2bdb
--- /dev/null
+++ b/src/Preferences.cc
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ *
+ * 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 "Preferences.h"
+
+#include <QFontDatabase>
+#include <QKeyEvent>
+#include <QSettings>
+
+Preferences *Preferences::instance = NULL;
+
+Preferences::Preferences(QWidget *parent) : QMainWindow(parent)
+{
+ setupUi(this);
+
+ // Setup default settings
+ this->defaultmap["3dview/colorscheme"] = this->colorSchemeChooser->currentItem()->text();
+ this->defaultmap["editor/fontfamily"] = this->fontChooser->currentText();
+ this->defaultmap["editor/fontsize"] = this->fontSize->currentText().toUInt();
+
+ // Toolbar
+ QActionGroup *group = new QActionGroup(this);
+ group->addAction(prefsAction3DView);
+ group->addAction(prefsActionEditor);
+ group->addAction(prefsActionAdvanced);
+ connect(group, SIGNAL(triggered(QAction*)), this, SLOT(actionTriggered(QAction*)));
+
+ prefsAction3DView->setChecked(true);
+ this->actionTriggered(this->prefsAction3DView);
+
+ // 3D View pane
+ this->colorschemes["Cornfield"][BACKGROUND_COLOR] = QColor(0xff, 0xff, 0xe5);
+ this->colorschemes["Cornfield"][OPENCSG_FACE_FRONT_COLOR] = QColor(0xf9, 0xd7, 0x2c);
+ this->colorschemes["Cornfield"][OPENCSG_FACE_BACK_COLOR] = QColor(0x9d, 0xcb, 0x51);
+ this->colorschemes["Cornfield"][CGAL_FACE_FRONT_COLOR] = QColor(0xf9, 0xd7, 0x2c);
+ this->colorschemes["Cornfield"][CGAL_FACE_BACK_COLOR] = QColor(0x9d, 0xcb, 0x51);
+ this->colorschemes["Cornfield"][CGAL_FACE_2D_COLOR] = QColor(0x00, 0xbf, 0x99);
+ this->colorschemes["Cornfield"][CGAL_EDGE_FRONT_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Cornfield"][CGAL_EDGE_BACK_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Cornfield"][CGAL_EDGE_2D_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Cornfield"][CROSSHAIR_COLOR] = QColor(0x80, 0x00, 0x00);
+
+ this->colorschemes["Metallic"][BACKGROUND_COLOR] = QColor(0xaa, 0xaa, 0xff);
+ this->colorschemes["Metallic"][OPENCSG_FACE_FRONT_COLOR] = QColor(0xdd, 0xdd, 0xff);
+ this->colorschemes["Metallic"][OPENCSG_FACE_BACK_COLOR] = QColor(0xdd, 0x22, 0xdd);
+ this->colorschemes["Metallic"][CGAL_FACE_FRONT_COLOR] = QColor(0xdd, 0xdd, 0xff);
+ this->colorschemes["Metallic"][CGAL_FACE_BACK_COLOR] = QColor(0xdd, 0x22, 0xdd);
+ this->colorschemes["Metallic"][CGAL_FACE_2D_COLOR] = QColor(0x00, 0xbf, 0x99);
+ this->colorschemes["Metallic"][CGAL_EDGE_FRONT_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Metallic"][CGAL_EDGE_BACK_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Metallic"][CGAL_EDGE_2D_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Metallic"][CROSSHAIR_COLOR] = QColor(0x80, 0x00, 0x00);
+
+ this->colorschemes["Sunset"][BACKGROUND_COLOR] = QColor(0xaa, 0x44, 0x44);
+ this->colorschemes["Sunset"][OPENCSG_FACE_FRONT_COLOR] = QColor(0xff, 0xaa, 0xaa);
+ this->colorschemes["Sunset"][OPENCSG_FACE_BACK_COLOR] = QColor(0x88, 0x22, 0x33);
+ this->colorschemes["Sunset"][CGAL_FACE_FRONT_COLOR] = QColor(0xff, 0xaa, 0xaa);
+ this->colorschemes["Sunset"][CGAL_FACE_BACK_COLOR] = QColor(0x88, 0x22, 0x33);
+ this->colorschemes["Sunset"][CGAL_FACE_2D_COLOR] = QColor(0x00, 0xbf, 0x99);
+ this->colorschemes["Sunset"][CGAL_EDGE_FRONT_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Sunset"][CGAL_EDGE_BACK_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Sunset"][CGAL_EDGE_2D_COLOR] = QColor(0xff, 0x00, 0x00);
+ this->colorschemes["Sunset"][CROSSHAIR_COLOR] = QColor(0x80, 0x00, 0x00);
+
+ // Editor pane
+ QFontDatabase db;
+ foreach(int size, db.standardSizes()) {
+ this->fontSize->addItem(QString::number(size));
+ }
+
+ connect(this->colorSchemeChooser, SIGNAL(itemSelectionChanged()),
+ this, SLOT(colorSchemeChanged()));
+ connect(this->fontChooser, SIGNAL(activated(const QString &)),
+ this, SLOT(fontFamilyChanged(const QString &)));
+ connect(this->fontSize, SIGNAL(editTextChanged(const QString &)),
+ this, SLOT(fontSizeChanged(const QString &)));
+
+ updateGUI();
+}
+
+Preferences::~Preferences()
+{
+ removeDefaultSettings();
+}
+
+void
+Preferences::actionTriggered(QAction *action)
+{
+ if (action == this->prefsAction3DView) {
+ this->stackedWidget->setCurrentWidget(this->page3DView);
+ }
+ else if (action == this->prefsActionEditor) {
+ this->stackedWidget->setCurrentWidget(this->pageEditor);
+ }
+ else if (action == this->prefsActionAdvanced) {
+ this->stackedWidget->setCurrentWidget(this->pageAdvanced);
+ }
+}
+
+void Preferences::colorSchemeChanged()
+{
+ QSettings settings;
+ settings.setValue("3dview/colorscheme", this->colorSchemeChooser->currentItem()->text());
+
+ emit requestRedraw();
+}
+
+const QColor &Preferences::color(RenderColor idx)
+{
+ return this->colorschemes[getValue("3dview/colorscheme").toString()][idx];
+}
+
+void Preferences::fontFamilyChanged(const QString &family)
+{
+ QSettings settings;
+ settings.setValue("editor/fontfamily", family);
+ emit fontChanged(family, getValue("editor/fontsize").toUInt());
+}
+
+void Preferences::fontSizeChanged(const QString &size)
+{
+ uint intsize = size.toUInt();
+ QSettings settings;
+ settings.setValue("editor/fontsize", intsize);
+ emit fontChanged(getValue("editor/fontfamily").toString(), intsize);
+}
+
+void Preferences::keyPressEvent(QKeyEvent *e)
+{
+#ifdef Q_WS_MAC
+ if (e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) {
+ close();
+ } else
+#endif
+ if ((e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_W) ||
+ e->key() == Qt::Key_Escape) {
+ close();
+ }
+}
+
+/*!
+ Removes settings that are the same as the default settings to avoid
+ overwriting future changes to default settings.
+ */
+void Preferences::removeDefaultSettings()
+{
+ QSettings settings;
+ for (QSettings::SettingsMap::const_iterator iter = this->defaultmap.begin();
+ iter != this->defaultmap.end();
+ iter++) {
+ if (settings.value(iter.key()) == iter.value()) {
+ settings.remove(iter.key());
+ }
+ }
+}
+
+QVariant Preferences::getValue(const QString &key) const
+{
+ QSettings settings;
+ return settings.value(key, this->defaultmap[key]);
+}
+
+void Preferences::updateGUI()
+{
+ QList<QListWidgetItem *> found =
+ this->colorSchemeChooser->findItems(getValue("3dview/colorscheme").toString(),
+ Qt::MatchExactly);
+ if (!found.isEmpty()) this->colorSchemeChooser->setCurrentItem(found.first());
+
+ QString fontfamily = getValue("editor/fontfamily").toString();
+ int fidx = this->fontChooser->findText(fontfamily);
+ if (fidx >= 0) {
+ this->fontChooser->setCurrentIndex(fidx);
+ }
+
+ QString fontsize = getValue("editor/fontsize").toString();
+ int sidx = this->fontSize->findText(fontsize);
+ if (sidx >= 0) {
+ this->fontSize->setCurrentIndex(sidx);
+ }
+ else {
+ this->fontSize->setEditText(fontsize);
+ }
+}
+
+void Preferences::apply() const
+{
+ emit fontChanged(getValue("editor/fontfamily").toString(), getValue("editor/fontsize").toUInt());
+ emit requestRedraw();
+}
+
diff --git a/src/Preferences.h b/src/Preferences.h
new file mode 100644
index 0000000..39599fd
--- /dev/null
+++ b/src/Preferences.h
@@ -0,0 +1,54 @@
+#ifndef PREFERENCES_H_
+#define PREFERENCES_H_
+
+#include <QMainWindow>
+#include <QSettings>
+#include "ui_Preferences.h"
+
+class Preferences : public QMainWindow, public Ui::Preferences
+{
+ Q_OBJECT;
+
+public:
+ ~Preferences();
+ static Preferences *inst() { if (!instance) instance = new Preferences(); return instance; }
+
+ enum RenderColor {
+ BACKGROUND_COLOR,
+ OPENCSG_FACE_FRONT_COLOR,
+ OPENCSG_FACE_BACK_COLOR,
+ CGAL_FACE_FRONT_COLOR,
+ CGAL_FACE_2D_COLOR,
+ CGAL_FACE_BACK_COLOR,
+ CGAL_EDGE_FRONT_COLOR,
+ CGAL_EDGE_BACK_COLOR,
+ CGAL_EDGE_2D_COLOR,
+ CROSSHAIR_COLOR
+ };
+ const QColor &color(RenderColor idx);
+ QVariant getValue(const QString &key) const;
+ void apply() const;
+
+public slots:
+ void actionTriggered(class QAction *);
+ void colorSchemeChanged();
+ void fontFamilyChanged(const QString &);
+ void fontSizeChanged(const QString &);
+
+signals:
+ void requestRedraw() const;
+ void fontChanged(const QString &family, uint size) const;
+
+private:
+ Preferences(QWidget *parent = NULL);
+ void keyPressEvent(QKeyEvent *e);
+ void updateGUI();
+ void removeDefaultSettings();
+
+ QSettings::SettingsMap defaultmap;
+ QHash<QString, QMap<RenderColor, QColor> > colorschemes;
+
+ static Preferences *instance;
+};
+
+#endif
diff --git a/src/context.cc b/src/context.cc
new file mode 100644
index 0000000..15a2ea6
--- /dev/null
+++ b/src/context.cc
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+#include "printutils.h"
+
+Context::Context(const Context *parent)
+{
+ this->parent = parent;
+ functions_p = NULL;
+ modules_p = NULL;
+ inst_p = NULL;
+ ctx_stack.append(this);
+}
+
+Context::~Context()
+{
+ ctx_stack.pop_back();
+}
+
+void Context::args(const QVector<QString> &argnames, const QVector<Expression*> &argexpr,
+ const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues)
+{
+ for (int i=0; i<argnames.size(); i++) {
+ set_variable(argnames[i], i < argexpr.size() && argexpr[i] ? argexpr[i]->evaluate(this->parent) : Value());
+ }
+
+ int posarg = 0;
+ for (int i=0; i<call_argnames.size(); i++) {
+ if (call_argnames[i].isEmpty()) {
+ if (posarg < argnames.size())
+ set_variable(argnames[posarg++], call_argvalues[i]);
+ } else {
+ set_variable(call_argnames[i], call_argvalues[i]);
+ }
+ }
+}
+
+QVector<const Context*> Context::ctx_stack;
+
+void Context::set_variable(QString name, Value value)
+{
+ if (name.startsWith("$"))
+ config_variables[name] = value;
+ else
+ variables[name] = value;
+}
+
+Value Context::lookup_variable(QString name, bool silent) const
+{
+ if (name.startsWith("$")) {
+ for (int i = ctx_stack.size()-1; i >= 0; i--) {
+ if (ctx_stack[i]->config_variables.contains(name))
+ return ctx_stack[i]->config_variables[name];
+ }
+ return Value();
+ }
+ if (variables.contains(name))
+ return variables[name];
+ if (parent)
+ return parent->lookup_variable(name, silent);
+ if (!silent)
+ PRINTA("WARNING: Ignoring unkown variable '%1'.", name);
+ return Value();
+}
+
+Value Context::evaluate_function(QString name, const QVector<QString> &argnames, const QVector<Value> &argvalues) const
+{
+ if (functions_p && functions_p->contains(name))
+ return functions_p->value(name)->evaluate(this, argnames, argvalues);
+ if (parent)
+ return parent->evaluate_function(name, argnames, argvalues);
+ PRINTA("WARNING: Ignoring unkown function '%1'.", name);
+ return Value();
+}
+
+AbstractNode *Context::evaluate_module(const ModuleInstantiation *inst) const
+{
+ if (modules_p && modules_p->contains(inst->modname))
+ return modules_p->value(inst->modname)->evaluate(this, inst);
+ if (parent)
+ return parent->evaluate_module(inst);
+ PRINTA("WARNING: Ignoring unkown module '%1'.", inst->modname);
+ return NULL;
+}
+
diff --git a/src/control.cc b/src/control.cc
new file mode 100644
index 0000000..19c5255
--- /dev/null
+++ b/src/control.cc
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+enum control_type_e {
+ CHILD,
+ ECHO,
+ ASSIGN,
+ FOR,
+ INT_FOR,
+ IF
+};
+
+class ControlModule : public AbstractModule
+{
+public:
+ control_type_e type;
+ ControlModule(control_type_e type) : type(type) { }
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+};
+
+void for_eval(AbstractNode *node, int l, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues, const QVector<ModuleInstantiation*> arg_children, const Context *arg_context)
+{
+ if (call_argnames.size() > l) {
+ QString it_name = call_argnames[l];
+ Value it_values = call_argvalues[l];
+ Context c(arg_context);
+ if (it_values.type == Value::RANGE) {
+ double range_begin = it_values.range_begin;
+ double range_end = it_values.range_end;
+ double range_step = it_values.range_step;
+ if (range_end < range_begin) {
+ double t = range_begin;
+ range_begin = range_end;
+ range_end = t;
+ }
+ if (range_step > 0 && (range_begin-range_end)/range_step < 10000) {
+ for (double i = range_begin; i <= range_end; i += range_step) {
+ c.set_variable(it_name, Value(i));
+ for_eval(node, l+1, call_argnames, call_argvalues, arg_children, &c);
+ }
+ }
+ }
+ else if (it_values.type == Value::VECTOR) {
+ for (int i = 0; i < it_values.vec.size(); i++) {
+ c.set_variable(it_name, *it_values.vec[i]);
+ for_eval(node, l+1, call_argnames, call_argvalues, arg_children, &c);
+ }
+ }
+ else {
+ for_eval(node, l+1, call_argnames, call_argvalues, arg_children, &c);
+ }
+ } else {
+ foreach (ModuleInstantiation *v, arg_children) {
+ AbstractNode *n = v->evaluate(arg_context);
+ if (n != NULL)
+ node->children.append(n);
+ }
+ }
+}
+
+AbstractNode *ControlModule::evaluate(const Context*, const ModuleInstantiation *inst) const
+{
+ if (type == CHILD)
+ {
+ int n = 0;
+ if (inst->argvalues.size() > 0) {
+ double v;
+ if (inst->argvalues[0].getnum(v))
+ n = v;
+ }
+ for (int i = Context::ctx_stack.size()-1; i >= 0; i--) {
+ const Context *c = Context::ctx_stack[i];
+ if (c->inst_p) {
+ if (n < c->inst_p->children.size())
+ return c->inst_p->children[n]->evaluate(c->inst_p->ctx);
+ return NULL;
+ }
+ c = c->parent;
+ }
+ return NULL;
+ }
+
+ AbstractNode *node;
+
+ if (type == INT_FOR)
+ node = new AbstractIntersectionNode(inst);
+ else
+ node = new AbstractNode(inst);
+
+ if (type == ECHO)
+ {
+ QString msg = QString("ECHO: ");
+ for (int i = 0; i < inst->argnames.size(); i++) {
+ if (i > 0)
+ msg += QString(", ");
+ if (!inst->argnames[i].isEmpty())
+ msg += inst->argnames[i] + QString(" = ");
+ msg += inst->argvalues[i].dump();
+ }
+ PRINT(msg);
+ }
+
+ if (type == ASSIGN)
+ {
+ Context c(inst->ctx);
+ for (int i = 0; i < inst->argnames.size(); i++) {
+ if (!inst->argnames[i].isEmpty())
+ c.set_variable(inst->argnames[i], inst->argvalues[i]);
+ }
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(&c);
+ if (n != NULL)
+ node->children.append(n);
+ }
+ }
+
+ if (type == FOR || type == INT_FOR)
+ {
+ for_eval(node, 0, inst->argnames, inst->argvalues, inst->children, inst->ctx);
+ }
+
+ if (type == IF)
+ {
+ if (inst->argvalues.size() > 0 && inst->argvalues[0].type == Value::BOOL && inst->argvalues[0].b)
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n != NULL)
+ node->children.append(n);
+ }
+ }
+
+ return node;
+}
+
+void register_builtin_control()
+{
+ builtin_modules["child"] = new ControlModule(CHILD);
+ builtin_modules["echo"] = new ControlModule(ECHO);
+ builtin_modules["assign"] = new ControlModule(ASSIGN);
+ builtin_modules["for"] = new ControlModule(FOR);
+ builtin_modules["intersection_for"] = new ControlModule(INT_FOR);
+ builtin_modules["if"] = new ControlModule(IF);
+}
+
diff --git a/src/csgops.cc b/src/csgops.cc
new file mode 100644
index 0000000..be29ede
--- /dev/null
+++ b/src/csgops.cc
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+enum csg_type_e {
+ CSG_TYPE_UNION,
+ CSG_TYPE_DIFFERENCE,
+ CSG_TYPE_INTERSECTION
+};
+
+class CsgModule : public AbstractModule
+{
+public:
+ csg_type_e type;
+ CsgModule(csg_type_e type) : type(type) { }
+ 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);
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n != NULL)
+ node->children.append(n);
+ }
+ return node;
+}
+
+#ifdef ENABLE_CGAL
+
+CGAL_Nef_polyhedron CsgNode::render_cgal_nef_polyhedron() 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;
+ 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;
+ }
+ }
+ }
+
+ 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 *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;
+}
+
+QString CsgNode::dump(QString indent) 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";
+ }
+ return dump_cache;
+}
+
+void register_builtin_csgops()
+{
+ builtin_modules["union"] = new CsgModule(CSG_TYPE_UNION);
+ builtin_modules["difference"] = new CsgModule(CSG_TYPE_DIFFERENCE);
+ builtin_modules["intersection"] = new CsgModule(CSG_TYPE_INTERSECTION);
+}
+
diff --git a/src/csgterm.cc b/src/csgterm.cc
new file mode 100644
index 0000000..ebad060
--- /dev/null
+++ b/src/csgterm.cc
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+
+CSGTerm::CSGTerm(PolySet *polyset, double m[20], QString label)
+{
+ this->type = TYPE_PRIMITIVE;
+ this->polyset = polyset;
+ this->label = label;
+ this->left = NULL;
+ this->right = NULL;
+ for (int i = 0; i < 20; i++)
+ this->m[i] = m[i];
+ refcounter = 1;
+}
+
+CSGTerm::CSGTerm(type_e type, CSGTerm *left, CSGTerm *right)
+{
+ this->type = type;
+ this->polyset = NULL;
+ this->left = left;
+ this->right = right;
+ refcounter = 1;
+}
+
+CSGTerm *CSGTerm::normalize()
+{
+ // This function implements the CSG normalization
+ // Reference: Florian Kirsch, Juergen Doeller,
+ // OpenCSG: A Library for Image-Based CSG Rendering,
+ // University of Potsdam, Hasso-Plattner-Institute, Germany
+ // http://www.opencsg.org/data/csg_freenix2005_paper.pdf
+
+ if (type == TYPE_PRIMITIVE)
+ return link();
+
+ CSGTerm *t1, *t2, *x, *y;
+
+ x = left->normalize();
+ y = right->normalize();
+
+ if (x != left || y != right) {
+ t1 = new CSGTerm(type, x, y);
+ } else {
+ t1 = link();
+ x->unlink();
+ y->unlink();
+ }
+
+ while (1) {
+ t2 = t1->normalize_tail();
+ t1->unlink();
+ if (t1 == t2)
+ break;
+ t1 = t2;
+ }
+
+ return t1;
+}
+
+CSGTerm *CSGTerm::normalize_tail()
+{
+ CSGTerm *x, *y, *z;
+
+ // Part A: The 'x . (y . z)' expressions
+
+ x = left;
+ y = right->left;
+ z = right->right;
+
+ // 1. x - (y + z) -> (x - y) - z
+ if (type == TYPE_DIFFERENCE && right->type == TYPE_UNION)
+ return new CSGTerm(TYPE_DIFFERENCE, new CSGTerm(TYPE_DIFFERENCE, x->link(), y->link()), z->link());
+
+ // 2. x * (y + z) -> (x * y) + (x * z)
+ if (type == TYPE_INTERSECTION && right->type == TYPE_UNION)
+ return new CSGTerm(TYPE_UNION, new CSGTerm(TYPE_INTERSECTION, x->link(), y->link()), new CSGTerm(TYPE_INTERSECTION, x->link(), z->link()));
+
+ // 3. x - (y * z) -> (x - y) + (x - z)
+ if (type == TYPE_DIFFERENCE && right->type == TYPE_INTERSECTION)
+ return new CSGTerm(TYPE_UNION, new CSGTerm(TYPE_DIFFERENCE, x->link(), y->link()), new CSGTerm(TYPE_DIFFERENCE, x->link(), z->link()));
+
+ // 4. x * (y * z) -> (x * y) * z
+ if (type == TYPE_INTERSECTION && right->type == TYPE_INTERSECTION)
+ return new CSGTerm(TYPE_INTERSECTION, new CSGTerm(TYPE_INTERSECTION, x->link(), y->link()), z->link());
+
+ // 5. x - (y - z) -> (x - y) + (x * z)
+ if (type == TYPE_DIFFERENCE && right->type == TYPE_DIFFERENCE)
+ return new CSGTerm(TYPE_UNION, new CSGTerm(TYPE_DIFFERENCE, x->link(), y->link()), new CSGTerm(TYPE_INTERSECTION, x->link(), z->link()));
+
+ // 6. x * (y - z) -> (x * y) - z
+ if (type == TYPE_INTERSECTION && right->type == TYPE_DIFFERENCE)
+ return new CSGTerm(TYPE_DIFFERENCE, new CSGTerm(TYPE_INTERSECTION, x->link(), y->link()), z->link());
+
+ // Part B: The '(x . y) . z' expressions
+
+ x = left->left;
+ y = left->right;
+ z = right;
+
+ // 7. (x - y) * z -> (x * z) - y
+ if (left->type == TYPE_DIFFERENCE && type == TYPE_INTERSECTION)
+ return new CSGTerm(TYPE_DIFFERENCE, new CSGTerm(TYPE_INTERSECTION, x->link(), z->link()), y->link());
+
+ // 8. (x + y) - z -> (x - z) + (y - z)
+ if (left->type == TYPE_UNION && type == TYPE_DIFFERENCE)
+ return new CSGTerm(TYPE_UNION, new CSGTerm(TYPE_DIFFERENCE, x->link(), z->link()), new CSGTerm(TYPE_DIFFERENCE, y->link(), z->link()));
+
+ // 9. (x + y) * z -> (x * z) + (y * z)
+ if (left->type == TYPE_UNION && type == TYPE_INTERSECTION)
+ return new CSGTerm(TYPE_UNION, new CSGTerm(TYPE_INTERSECTION, x->link(), z->link()), new CSGTerm(TYPE_INTERSECTION, y->link(), z->link()));
+
+ return link();
+}
+
+CSGTerm *CSGTerm::link()
+{
+ refcounter++;
+ return this;
+}
+
+void CSGTerm::unlink()
+{
+ if (--refcounter <= 0) {
+ if (polyset)
+ polyset->unlink();
+ if (left)
+ left->unlink();
+ if (right)
+ right->unlink();
+ delete this;
+ }
+}
+
+QString CSGTerm::dump()
+{
+ if (type == TYPE_UNION)
+ return QString("(%1 + %2)").arg(left->dump(), right->dump());
+ if (type == TYPE_INTERSECTION)
+ return QString("(%1 * %2)").arg(left->dump(), right->dump());
+ if (type == TYPE_DIFFERENCE)
+ return QString("(%1 - %2)").arg(left->dump(), right->dump());
+ return label;
+}
+
+CSGChain::CSGChain()
+{
+}
+
+void CSGChain::add(PolySet *polyset, double *m, CSGTerm::type_e type, QString label)
+{
+ polysets.append(polyset);
+ matrices.append(m);
+ types.append(type);
+ labels.append(label);
+}
+
+void CSGChain::import(CSGTerm *term, CSGTerm::type_e type)
+{
+ if (term->type == CSGTerm::TYPE_PRIMITIVE) {
+ add(term->polyset, term->m, type, term->label);
+ } else {
+ import(term->left, type);
+ import(term->right, term->type);
+ }
+}
+
+QString CSGChain::dump()
+{
+ QString text;
+ for (int i = 0; i < types.size(); i++)
+ {
+ if (types[i] == CSGTerm::TYPE_UNION) {
+ if (i != 0)
+ text += "\n";
+ text += "+";
+ }
+ if (types[i] == CSGTerm::TYPE_DIFFERENCE)
+ text += " -";
+ if (types[i] == CSGTerm::TYPE_INTERSECTION)
+ text += " *";
+ text += labels[i];
+ }
+ text += "\n";
+ return text;
+}
+
diff --git a/src/dxfdata.cc b/src/dxfdata.cc
new file mode 100644
index 0000000..864ee7b
--- /dev/null
+++ b/src/dxfdata.cc
@@ -0,0 +1,514 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+#include "printutils.h"
+
+#include <QFile>
+#include <QTextStream>
+
+struct Line {
+ typedef DxfData::Point Point;
+ Point *p[2];
+ bool disabled;
+ Line(Point *p1, Point *p2) { p[0] = p1; p[1] = p2; disabled = false; }
+ Line() { p[0] = NULL; p[1] = NULL; disabled = false; }
+};
+
+DxfData::DxfData()
+{
+}
+
+/*!
+ Reads a layer from the given file, or all layers if layename.isNull()
+ */
+DxfData::DxfData(double fn, double fs, double fa, QString filename, QString layername, double xorigin, double yorigin, double scale)
+{
+ handle_dep(filename); // Register ourselves as a dependency
+
+ QFile f(filename);
+ if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ PRINTF("WARNING: Can't open DXF file `%s'.", filename.toUtf8().data());
+ return;
+ }
+ QTextStream stream(&f);
+
+ Grid2d< QVector<int> > grid(GRID_COARSE);
+ QList<Line> lines; // Global lines
+ QHash< QString, QList<Line> > blockdata; // Lines in blocks
+
+ bool in_entities_section = false;
+ bool in_blocks_section = false;
+ QString current_block;
+
+#define ADD_LINE(_x1, _y1, _x2, _y2) do { \
+ double _p1x = _x1, _p1y = _y1, _p2x = _x2, _p2y = _y2; \
+ if (!in_entities_section && !in_blocks_section) \
+ break; \
+ if (in_entities_section && \
+ !(layername.isNull() || layername == layer)) \
+ break; \
+ grid.align(_p1x, _p1y); \
+ grid.align(_p2x, _p2y); \
+ grid.data(_p1x, _p1y).append(lines.count()); \
+ grid.data(_p2x, _p2y).append(lines.count()); \
+ if (in_entities_section) \
+ lines.append( \
+ Line(addPoint(_p1x, _p1y), addPoint(_p2x, _p2y))); \
+ if (in_blocks_section && !current_block.isNull()) \
+ blockdata[current_block].append( \
+ Line(addPoint(_p1x, _p1y), addPoint(_p2x, _p2y))); \
+ } while (0)
+
+ QString mode, layer, name, iddata;
+ int dimtype = 0;
+ double coords[7][2]; // Used by DIMENSION entities
+ QVector<double> xverts;
+ QVector<double> yverts;
+ double radius = 0;
+ double arc_start_angle = 0, arc_stop_angle = 0;
+ double ellipse_start_angle = 0, ellipse_stop_angle = 0;
+
+ for (int i = 0; i < 7; i++)
+ for (int j = 0; j < 2; j++)
+ coords[i][j] = 0;
+
+ QHash<QString, int> unsupported_entities_list;
+
+
+ //
+ // Parse DXF file. Will populate this->points, this->dims, lines and blockdata
+ //
+ while (!stream.atEnd())
+ {
+ QString id_str = stream.readLine();
+ QString data = stream.readLine();
+
+ bool status;
+ int id = id_str.toInt(&status);
+
+ if (!status) {
+ PRINTA("WARNING: Illegal ID `%1' in `%3'.", id_str, filename);
+ break;
+ }
+
+ if (id >= 10 && id <= 16) {
+ if (in_blocks_section)
+ coords[id-10][0] = data.toDouble();
+ else if (id == 11 || id == 12 || id == 16)
+ coords[id-10][0] = data.toDouble() * scale;
+ else
+ coords[id-10][0] = (data.toDouble() - xorigin) * scale;
+ }
+
+ if (id >= 20 && id <= 26) {
+ if (in_blocks_section)
+ coords[id-20][1] = data.toDouble();
+ else if (id == 21 || id == 22 || id == 26)
+ coords[id-20][1] = data.toDouble() * scale;
+ else
+ coords[id-20][1] = (data.toDouble() - yorigin) * scale;
+ }
+
+ switch (id)
+ {
+ case 0:
+ if (mode == "SECTION") {
+ in_entities_section = iddata == "ENTITIES";
+ in_blocks_section = iddata == "BLOCKS";
+ }
+ else if (mode == "LINE") {
+ ADD_LINE(xverts[0], yverts[0], xverts[1], yverts[1]);
+ }
+ else if (mode == "LWPOLYLINE") {
+ assert(xverts.size() == yverts.size());
+ // polyline flag is stored in 'dimtype'
+ int numverts = xverts.size();
+ for (int i=1;i<numverts;i++) {
+ ADD_LINE(xverts[i-1], yverts[i-1], xverts[i%numverts], yverts[i%numverts]);
+ }
+ if (dimtype & 0x01) { // closed polyline
+ ADD_LINE(xverts[numverts-1], yverts[numverts-1], xverts[0], yverts[0]);
+ }
+ }
+ else if (mode == "CIRCLE") {
+ int n = get_fragments_from_r(radius, fn, fs, fa);
+ Point center(xverts[0], yverts[0]);
+ for (int i = 0; i < n; i++) {
+ double a1 = (2*M_PI*i)/n;
+ double a2 = (2*M_PI*(i+1))/n;
+ ADD_LINE(cos(a1)*radius + center.x, sin(a1)*radius + center.y,
+ cos(a2)*radius + center.x, sin(a2)*radius + center.y);
+ }
+ }
+ else if (mode == "ARC") {
+ Point center(xverts[0], yverts[0]);
+ int n = get_fragments_from_r(radius, fn, fs, fa);
+ while (arc_start_angle > arc_stop_angle)
+ arc_stop_angle += 360.0;
+ n = (int)ceil(n * (arc_stop_angle-arc_start_angle) / 360);
+ for (int i = 0; i < n; i++) {
+ double a1 = ((arc_stop_angle-arc_start_angle)*i)/n;
+ double a2 = ((arc_stop_angle-arc_start_angle)*(i+1))/n;
+ a1 = (arc_start_angle + a1) * M_PI / 180.0;
+ a2 = (arc_start_angle + a2) * M_PI / 180.0;
+ ADD_LINE(cos(a1)*radius + center.x, sin(a1)*radius + center.y,
+ cos(a2)*radius + center.x, sin(a2)*radius + center.y);
+ }
+ }
+ else if (mode == "ELLIPSE") {
+ // Commented code is meant as documentation of vector math
+ while (ellipse_start_angle > ellipse_stop_angle) ellipse_stop_angle += 2 * M_PI;
+// Vector2d center(xverts[0], yverts[0]);
+ Point center(xverts[0], yverts[0]);
+// Vector2d ce(xverts[1], yverts[1]);
+ Point ce(xverts[1], yverts[1]);
+// double r_major = ce.length();
+ double r_major = sqrt(ce.x*ce.x + ce.y*ce.y);
+// double rot_angle = ce.angle();
+ double rot_angle;
+ {
+// double dot = ce.dot(Vector2d(1.0, 0.0));
+ double dot = ce.x;
+ double cosval = dot / r_major;
+ if (cosval > 1.0) cosval = 1.0;
+ if (cosval < -1.0) cosval = -1.0;
+ rot_angle = acos(cosval);
+ if (ce.y < 0.0) rot_angle = 2 * M_PI - rot_angle;
+ }
+
+ // the ratio stored in 'radius; due to the parser code not checking entity type
+ double r_minor = r_major * radius;
+ double sweep_angle = ellipse_stop_angle-ellipse_start_angle;
+ int n = get_fragments_from_r(r_major, fn, fs, fa);
+ n = (int)ceil(n * sweep_angle / (2 * M_PI));
+// Vector2d p1;
+ Point p1;
+ for (int i=0;i<=n;i++) {
+ double a = (ellipse_start_angle + sweep_angle*i/n);
+// Vector2d p2(cos(a)*r_major, sin(a)*r_minor);
+ Point p2(cos(a)*r_major, sin(a)*r_minor);
+// p2.rotate(rot_angle);
+ Point p2_rot(cos(rot_angle)*p2.x - sin(rot_angle)*p2.y,
+ sin(rot_angle)*p2.x + cos(rot_angle)*p2.y);
+// p2 += center;
+ p2_rot.x += center.x;
+ p2_rot.y += center.y;
+ if (i > 0) {
+// ADD_LINE(p1[0], p1[1], p2[0], p2[1]);
+ ADD_LINE(p1.x, p1.y, p2_rot.x, p2_rot.y);
+ }
+// p1 = p2;
+ p1.x = p2_rot.x;
+ p1.y = p2_rot.y;
+ }
+ }
+ else if (mode == "INSERT") {
+ // scale is stored in ellipse_start|stop_angle, rotation in arc_start_angle;
+ // due to the parser code not checking entity type
+ int n = blockdata[iddata].size();
+ for (int i = 0; i < n; i++) {
+ double a = arc_start_angle * M_PI / 180.0;
+ double lx1 = blockdata[iddata][i].p[0]->x * ellipse_start_angle;
+ double ly1 = blockdata[iddata][i].p[0]->y * ellipse_stop_angle;
+ double lx2 = blockdata[iddata][i].p[1]->x * ellipse_start_angle;
+ double ly2 = blockdata[iddata][i].p[1]->y * ellipse_stop_angle;
+ double px1 = (cos(a)*lx1 - sin(a)*ly1) * scale + xverts[0];
+ double py1 = (sin(a)*lx1 + cos(a)*ly1) * scale + yverts[0];
+ double px2 = (cos(a)*lx2 - sin(a)*ly2) * scale + xverts[0];
+ double py2 = (sin(a)*lx2 + cos(a)*ly2) * scale + yverts[0];
+ ADD_LINE(px1, py1, px2, py2);
+ }
+ }
+ else if (mode == "DIMENSION" &&
+ (layername.isNull() || layername == layer)) {
+ this->dims.append(Dim());
+ this->dims.last().type = dimtype;
+ for (int i = 0; i < 7; i++)
+ for (int j = 0; j < 2; j++)
+ this->dims.last().coords[i][j] = coords[i][j];
+ this->dims.last().angle = arc_start_angle;
+ this->dims.last().length = radius;
+ this->dims.last().name = name;
+ }
+ else if (mode == "BLOCK") {
+ current_block = iddata;
+ }
+ else if (mode == "ENDBLK") {
+ current_block = QString();
+ }
+ else if (mode == "ENDSEC") {
+ }
+ else if (in_blocks_section || (in_entities_section &&
+ (layername.isNull() || layername == layer))) {
+ unsupported_entities_list[mode]++;
+ }
+ mode = data;
+ layer = QString();
+ name = QString();
+ iddata = QString();
+ dimtype = 0;
+ for (int i = 0; i < 7; i++)
+ for (int j = 0; j < 2; j++)
+ coords[i][j] = 0;
+ xverts.clear();
+ yverts.clear();
+ radius = arc_start_angle = arc_stop_angle = 0;
+ ellipse_start_angle = ellipse_stop_angle = 0;
+ if (mode == "INSERT") {
+ ellipse_start_angle = ellipse_stop_angle = 1.0; // scale
+ }
+ break;
+ case 1:
+ name = data;
+ break;
+ case 2:
+ iddata = data;
+ break;
+ case 8:
+ layer = data;
+ break;
+ case 10:
+ if (in_blocks_section)
+ xverts.append((data.toDouble()));
+ else
+ xverts.append((data.toDouble() - xorigin) * scale);
+ break;
+ case 11:
+ if (in_blocks_section)
+ xverts.append((data.toDouble()));
+ else
+ xverts.append((data.toDouble() - xorigin) * scale);
+ break;
+ case 20:
+ if (in_blocks_section)
+ yverts.append((data.toDouble()));
+ else
+ yverts.append((data.toDouble() - yorigin) * scale);
+ break;
+ case 21:
+ if (in_blocks_section)
+ yverts.append((data.toDouble()));
+ else
+ yverts.append((data.toDouble() - yorigin) * scale);
+ break;
+ case 40:
+ // CIRCLE, ARC: radius
+ // ELLIPSE: minor to major ratio
+ // DIMENSION (radial, diameter): Leader length
+ radius = data.toDouble();
+ if (!in_blocks_section) radius *= scale;
+ break;
+ case 41:
+ // ELLIPSE: start_angle
+ // INSERT: X scale
+ ellipse_start_angle = data.toDouble();
+ break;
+ case 50:
+ // ARC: start_angle
+ // INSERT: rot angle
+ // DIMENSION: linear and rotated: angle
+ arc_start_angle = data.toDouble();
+ break;
+ case 42:
+ // ELLIPSE: stop_angle
+ // INSERT: Y scale
+ ellipse_stop_angle = data.toDouble();
+ break;
+ case 51: // ARC
+ arc_stop_angle = data.toDouble();
+ break;
+ case 70:
+ // LWPOLYLINE: polyline flag
+ // DIMENSION: dimension type
+ dimtype = data.toInt();
+ break;
+ }
+ }
+
+ QHashIterator<QString, int> i(unsupported_entities_list);
+ while (i.hasNext()) {
+ i.next();
+ if (layername.isNull()) {
+ PRINTA("WARNING: Unsupported DXF Entity `%1' (%2x) in `%3'.",
+ i.key(), QString::number(i.value()), filename);
+ } else {
+ PRINTA("WARNING: Unsupported DXF Entity `%1' (%2x) in layer `%3' of `%4'.",
+ i.key(), QString::number(i.value()), layername, filename);
+ }
+ }
+
+ // Extract paths from parsed data
+
+ QHash<int, int> enabled_lines;
+ for (int i = 0; i < lines.count(); i++) {
+ enabled_lines[i] = i;
+ }
+
+ // extract all open paths
+ while (enabled_lines.count() > 0)
+ {
+ int current_line, current_point;
+
+ foreach (int i, enabled_lines) {
+ for (int j = 0; j < 2; j++) {
+ QVector<int> *lv = &grid.data(lines[i].p[j]->x, lines[i].p[j]->y);
+ for (int ki = 0; ki < lv->count(); ki++) {
+ int k = lv->at(ki);
+ if (k == i || lines[k].disabled)
+ continue;
+ goto next_open_path_j;
+ }
+ current_line = i;
+ current_point = j;
+ goto create_open_path;
+ next_open_path_j:;
+ }
+ }
+
+ break;
+
+ create_open_path:
+ this->paths.append(Path());
+ Path *this_path = &this->paths.last();
+
+ this_path->points.append(lines[current_line].p[current_point]);
+ while (1) {
+ this_path->points.append(lines[current_line].p[!current_point]);
+ Point *ref_point = lines[current_line].p[!current_point];
+ lines[current_line].disabled = true;
+ enabled_lines.remove(current_line);
+ QVector<int> *lv = &grid.data(ref_point->x, ref_point->y);
+ for (int ki = 0; ki < lv->count(); ki++) {
+ int k = lv->at(ki);
+ if (lines[k].disabled)
+ continue;
+ if (grid.eq(ref_point->x, ref_point->y, lines[k].p[0]->x, lines[k].p[0]->y)) {
+ current_line = k;
+ current_point = 0;
+ goto found_next_line_in_open_path;
+ }
+ if (grid.eq(ref_point->x, ref_point->y, lines[k].p[1]->x, lines[k].p[1]->y)) {
+ current_line = k;
+ current_point = 1;
+ goto found_next_line_in_open_path;
+ }
+ }
+ break;
+ found_next_line_in_open_path:;
+ }
+ }
+
+ // extract all closed paths
+ while (enabled_lines.count() > 0)
+ {
+ int current_line = enabled_lines.begin().value(), current_point = 0;
+
+ this->paths.append(Path());
+ Path *this_path = &this->paths.last();
+ this_path->is_closed = true;
+
+ this_path->points.append(lines[current_line].p[current_point]);
+ while (1) {
+ this_path->points.append(lines[current_line].p[!current_point]);
+ Point *ref_point = lines[current_line].p[!current_point];
+ lines[current_line].disabled = true;
+ enabled_lines.remove(current_line);
+ QVector<int> *lv = &grid.data(ref_point->x, ref_point->y);
+ for (int ki = 0; ki < lv->count(); ki++) {
+ int k = lv->at(ki);
+ if (lines[k].disabled)
+ continue;
+ if (grid.eq(ref_point->x, ref_point->y, lines[k].p[0]->x, lines[k].p[0]->y)) {
+ current_line = k;
+ current_point = 0;
+ goto found_next_line_in_closed_path;
+ }
+ if (grid.eq(ref_point->x, ref_point->y, lines[k].p[1]->x, lines[k].p[1]->y)) {
+ current_line = k;
+ current_point = 1;
+ goto found_next_line_in_closed_path;
+ }
+ }
+ break;
+ found_next_line_in_closed_path:;
+ }
+ }
+
+ fixup_path_direction();
+
+#if 0
+ printf("----- DXF Data -----\n");
+ for (int i = 0; i < this->paths.count(); i++) {
+ printf("Path %d (%s):\n", i, this->paths[i].is_closed ? "closed" : "open");
+ for (int j = 0; j < this->paths[i].points.count(); j++)
+ printf(" %f %f\n", this->paths[i].points[j]->x, this->paths[i].points[j]->y);
+ }
+ printf("--------------------\n");
+ fflush(stdout);
+#endif
+}
+
+/*!
+ Ensures that all paths have the same vertex ordering.
+ FIXME: CW or CCW?
+*/
+void DxfData::fixup_path_direction()
+{
+ for (int i = 0; i < this->paths.count(); i++) {
+ if (!this->paths[i].is_closed)
+ break;
+ this->paths[i].is_inner = true;
+ double min_x = this->paths[i].points[0]->x;
+ int min_x_point = 0;
+ for (int j = 1; j < this->paths[i].points.count(); j++) {
+ if (this->paths[i].points[j]->x < min_x) {
+ min_x = this->paths[i].points[j]->x;
+ min_x_point = j;
+ }
+ }
+ // rotate points if the path is in non-standard rotation
+ int b = min_x_point;
+ int a = b == 0 ? this->paths[i].points.count() - 2 : b - 1;
+ int c = b == this->paths[i].points.count() - 1 ? 1 : b + 1;
+ double ax = this->paths[i].points[a]->x - this->paths[i].points[b]->x;
+ double ay = this->paths[i].points[a]->y - this->paths[i].points[b]->y;
+ double cx = this->paths[i].points[c]->x - this->paths[i].points[b]->x;
+ double cy = this->paths[i].points[c]->y - this->paths[i].points[b]->y;
+#if 0
+ printf("Rotate check:\n");
+ printf(" a/b/c indices = %d %d %d\n", a, b, c);
+ printf(" b->a vector = %f %f (%f)\n", ax, ay, atan2(ax, ay));
+ printf(" b->c vector = %f %f (%f)\n", cx, cy, atan2(cx, cy));
+#endif
+ // FIXME: atan2() usually takes y,x. This variant probably makes the path clockwise..
+ if (atan2(ax, ay) < atan2(cx, cy)) {
+ for (int j = 0; j < this->paths[i].points.count()/2; j++)
+ this->paths[i].points.swap(j, this->paths[i].points.count()-1-j);
+ }
+ }
+}
+
+DxfData::Point *DxfData::addPoint(double x, double y)
+{
+ this->points.append(Point(x, y));
+ return &this->points.last();
+}
+
diff --git a/src/dxfdim.cc b/src/dxfdim.cc
new file mode 100644
index 0000000..359606a
--- /dev/null
+++ b/src/dxfdim.cc
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+#include "printutils.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+QHash<QString,Value> dxf_dim_cache;
+QHash<QString,Value> dxf_cross_cache;
+
+Value builtin_dxf_dim(const QVector<QString> &argnames, const QVector<Value> &args)
+{
+ QString filename;
+ QString layername;
+ QString name;
+ double xorigin = 0;
+ double yorigin = 0;
+ double scale = 1;
+
+ for (int i = 0; i < argnames.count() && i < args.count(); i++) {
+ if (argnames[i] == "file")
+ filename = args[i].text;
+ if (argnames[i] == "layer")
+ layername = 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;
+ }
+
+ struct stat st;
+ memset(&st, 0, sizeof(struct stat));
+ stat(filename.toAscii().data(), &st);
+
+ QString key = filename + "|" + layername + "|" + name + "|" + QString::number(xorigin) + "|" + QString::number(yorigin) +
+ "|" + QString::number(scale) + "|" + QString::number(st.st_mtime) + "|" + QString::number(st.st_size);
+
+ if (dxf_dim_cache.contains(key))
+ return dxf_dim_cache[key];
+
+ DxfData dxf(36, 0, 0, filename, layername, xorigin, yorigin, scale);
+
+ for (int i = 0; i < dxf.dims.count(); i++)
+ {
+ if (!name.isNull() && dxf.dims[i].name != name)
+ continue;
+
+ DxfData::Dim *d = &dxf.dims[i];
+ int type = d->type & 7;
+
+ if (type == 0) {
+ // Rotated, horizontal or vertical
+ double x = d->coords[4][0] - d->coords[3][0];
+ double y = d->coords[4][1] - d->coords[3][1];
+ double angle = d->angle;
+ double distance_projected_on_line = fabs(x * cos(angle*M_PI/180) + y * sin(angle*M_PI/180));
+ return dxf_dim_cache[key] = Value(distance_projected_on_line);
+ }
+ else if (type == 1) {
+ // Aligned
+ double x = d->coords[4][0] - d->coords[3][0];
+ double y = d->coords[4][1] - d->coords[3][1];
+ return dxf_dim_cache[key] = Value(sqrt(x*x + y*y));
+ }
+ else if (type == 2) {
+ // Angular
+ double a1 = atan2(d->coords[0][0] - d->coords[5][0], d->coords[0][1] - d->coords[5][1]);
+ double a2 = atan2(d->coords[4][0] - d->coords[3][0], d->coords[4][1] - d->coords[3][1]);
+ return dxf_dim_cache[key] = Value(fabs(a1 - a2) * 180 / M_PI);
+ }
+ else if (type == 3 || type == 4) {
+ // Diameter or Radius
+ double x = d->coords[5][0] - d->coords[0][0];
+ double y = d->coords[5][1] - d->coords[0][1];
+ return dxf_dim_cache[key] = Value(sqrt(x*x + y*y));
+ }
+ else if (type == 5) {
+ // Angular 3 Point
+ }
+ else if (type == 6) {
+ // Ordinate
+ return dxf_dim_cache[key] = Value((d->type & 64) ? d->coords[3][0] : d->coords[3][1]);
+ }
+
+ PRINTA("WARNING: Dimension `%1' in `%2', layer `%3' has unsupported type!", name, filename, layername);
+ return Value();
+ }
+
+ PRINTA("WARNING: Can't find dimension `%1' in `%2', layer `%3'!", name, filename, layername);
+
+ return Value();
+}
+
+Value builtin_dxf_cross(const QVector<QString> &argnames, const QVector<Value> &args)
+{
+ QString filename;
+ QString layername;
+ double xorigin = 0;
+ double yorigin = 0;
+ double scale = 1;
+
+ for (int i = 0; i < argnames.count() && i < args.count(); i++) {
+ if (argnames[i] == "file")
+ filename = args[i].text;
+ if (argnames[i] == "layer")
+ layername = args[i].text;
+ if (argnames[i] == "origin")
+ args[i].getv2(xorigin, yorigin);
+ if (argnames[i] == "scale")
+ args[i].getnum(scale);
+ }
+
+ struct stat st;
+ memset(&st, 0, sizeof(struct stat));
+ stat(filename.toAscii().data(), &st);
+
+ QString key = filename + "|" + layername + "|" + QString::number(xorigin) + "|" + QString::number(yorigin) +
+ "|" + QString::number(scale) + "|" + QString::number(st.st_mtime) + "|" + QString::number(st.st_size);
+
+ if (dxf_cross_cache.contains(key))
+ return dxf_cross_cache[key];
+
+ DxfData dxf(36, 0, 0, filename, layername, xorigin, yorigin, scale);
+
+ double coords[4][2];
+
+ for (int i = 0, j = 0; i < dxf.paths.count(); i++) {
+ if (dxf.paths[i].points.count() != 2)
+ continue;
+ coords[j][0] = dxf.paths[i].points[0]->x;
+ coords[j++][1] = dxf.paths[i].points[0]->y;
+ coords[j][0] = dxf.paths[i].points[1]->x;
+ coords[j++][1] = dxf.paths[i].points[1]->y;
+
+ if (j == 4) {
+ double x1 = coords[0][0], y1 = coords[0][1];
+ double x2 = coords[1][0], y2 = coords[1][1];
+ double x3 = coords[2][0], y3 = coords[2][1];
+ double x4 = coords[3][0], y4 = coords[3][1];
+ double dem = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
+ if (dem == 0)
+ break;
+ double ua = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3)) / dem;
+ // double ub = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / dem;
+ double x = x1 + ua*(x2 - x1);
+ double y = y1 + ua*(y2 - y1);
+ Value ret;
+ ret.type = Value::VECTOR;
+ ret.vec.append(new Value(x));
+ ret.vec.append(new Value(y));
+ return dxf_cross_cache[key] = ret;
+ }
+ }
+
+ PRINTA("WARNING: Can't find cross in `%1', layer `%2'!", filename, layername);
+
+ return Value();
+}
+
+void initialize_builtin_dxf_dim()
+{
+ builtin_functions["dxf_dim"] = new BuiltinFunction(&builtin_dxf_dim);
+ builtin_functions["dxf_cross"] = new BuiltinFunction(&builtin_dxf_cross);
+}
+
diff --git a/src/dxflinextrude.cc b/src/dxflinextrude.cc
new file mode 100644
index 0000000..f7a1d24
--- /dev/null
+++ b/src/dxflinextrude.cc
@@ -0,0 +1,359 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <QApplication>
+#include <QTime>
+
+class DxfLinearExtrudeModule : public AbstractModule
+{
+public:
+ DxfLinearExtrudeModule() { }
+ 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);
+
+ QVector<QString> argnames = QVector<QString>() << "file" << "layer" << "height" << "origin" << "scale" << "center" << "twist" << "slices";
+ QVector<Expression*> argexpr;
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ node->fn = c.lookup_variable("$fn").num;
+ node->fs = c.lookup_variable("$fs").num;
+ node->fa = c.lookup_variable("$fa").num;
+
+ Value file = c.lookup_variable("file");
+ Value layer = c.lookup_variable("layer", true);
+ Value height = c.lookup_variable("height", true);
+ Value convexity = c.lookup_variable("convexity", true);
+ Value origin = c.lookup_variable("origin", true);
+ Value scale = c.lookup_variable("scale", true);
+ Value center = c.lookup_variable("center", true);
+ Value twist = c.lookup_variable("twist", true);
+ Value slices = c.lookup_variable("slices", true);
+
+ node->filename = file.text;
+ node->layername = layer.text;
+ node->height = height.num;
+ node->convexity = (int)convexity.num;
+ origin.getv2(node->origin_x, node->origin_y);
+ node->scale = scale.num;
+
+ if (center.type == Value::BOOL)
+ node->center = center.b;
+
+ if (node->height <= 0)
+ node->height = 100;
+
+ if (node->convexity <= 0)
+ node->convexity = 1;
+
+ if (node->scale <= 0)
+ node->scale = 1;
+
+ if (twist.type == Value::NUMBER) {
+ node->twist = twist.num;
+ if (slices.type == Value::NUMBER) {
+ node->slices = (int)slices.num;
+ } else {
+ node->slices = (int)fmax(2, fabs(get_fragments_from_r(node->height,
+ node->fn, node->fs, node->fa) * node->twist / 360));
+ }
+ node->has_twist = true;
+ }
+
+ if (node->filename.isEmpty()) {
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n)
+ node->children.append(n);
+ }
+ }
+
+ return node;
+}
+
+void register_builtin_dxf_linear_extrude()
+{
+ builtin_modules["dxf_linear_extrude"] = new DxfLinearExtrudeModule();
+ 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);
+ }
+ }
+ }
+}
+
+static void report_func(const class AbstractNode*, void *vp, int mark)
+{
+ QProgressDialog *pd = (QProgressDialog*)vp;
+ int v = (int)((mark*100.0) / progress_report_count);
+ pd->setValue(v < 100 ? v : 99);
+ QString label;
+ label.sprintf("Rendering Polygon Mesh using CGAL (%d/%d)", mark, progress_report_count);
+ pd->setLabelText(label);
+ QApplication::processEvents();
+}
+
+PolySet *DxfLinearExtrudeNode::render_polyset(render_mode_e rm) 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();
+ DxfData *dxf;
+
+ if (filename.isEmpty())
+ {
+ QTime t;
+ QProgressDialog *pd = NULL;
+
+ if (rm == RENDER_OPENCSG)
+ {
+ PRINT_NOCACHE("Processing uncached linear_extrude outline...");
+ QApplication::processEvents();
+
+ t.start();
+ pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", QString(), 0, 100);
+ pd->setValue(0);
+ pd->setAutoClose(false);
+ pd->show();
+ QApplication::processEvents();
+
+ progress_report_prep((AbstractNode*)this, report_func, pd);
+ }
+
+ // Before extruding, union all (2D) children nodes
+ // to a single DxfData, then tessealte 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);
+
+ if (rm == RENDER_OPENCSG) {
+ progress_report_fin();
+ int s = t.elapsed() / 1000;
+ PRINTF_NOCACHE("..rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
+ delete pd;
+ }
+ } 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_cache.insert(key, new PolySet::ps_cache_entry(ps->link()));
+ print_messages_pop();
+ delete dxf;
+
+ return ps;
+}
+
+QString DxfLinearExtrudeNode::dump(QString indent) const
+{
+ if (dump_cache.isEmpty()) {
+ QString text;
+ struct stat st;
+ memset(&st, 0, sizeof(struct stat));
+ stat(filename.toAscii().data(), &st);
+ 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)st.st_mtime, (int)st.st_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, fs, fa);
+ 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;
+ }
+ return dump_cache;
+}
+
diff --git a/src/dxfrotextrude.cc b/src/dxfrotextrude.cc
new file mode 100644
index 0000000..427bcb0
--- /dev/null
+++ b/src/dxfrotextrude.cc
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <QTime>
+#include <QApplication>
+
+class DxfRotateExtrudeModule : public AbstractModule
+{
+public:
+ DxfRotateExtrudeModule() { }
+ 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);
+
+ QVector<QString> argnames = QVector<QString>() << "file" << "layer" << "origin" << "scale";
+ QVector<Expression*> argexpr;
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ node->fn = c.lookup_variable("$fn").num;
+ node->fs = c.lookup_variable("$fs").num;
+ node->fa = c.lookup_variable("$fa").num;
+
+ Value file = c.lookup_variable("file");
+ Value layer = c.lookup_variable("layer", true);
+ Value convexity = c.lookup_variable("convexity", true);
+ Value origin = c.lookup_variable("origin", true);
+ Value scale = c.lookup_variable("scale", true);
+
+ node->filename = file.text;
+ node->layername = layer.text;
+ node->convexity = (int)convexity.num;
+ origin.getv2(node->origin_x, node->origin_y);
+ node->scale = scale.num;
+
+ if (node->convexity <= 0)
+ node->convexity = 1;
+
+ if (node->scale <= 0)
+ node->scale = 1;
+
+ if (node->filename.isEmpty()) {
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n)
+ node->children.append(n);
+ }
+ }
+
+ return node;
+}
+
+void register_builtin_dxf_rotate_extrude()
+{
+ builtin_modules["dxf_rotate_extrude"] = new DxfRotateExtrudeModule();
+ builtin_modules["rotate_extrude"] = new DxfRotateExtrudeModule();
+}
+
+static void report_func(const class AbstractNode*, void *vp, int mark)
+{
+ QProgressDialog *pd = (QProgressDialog*)vp;
+ int v = (int)((mark*100.0) / progress_report_count);
+ pd->setValue(v < 100 ? v : 99);
+ QString label;
+ label.sprintf("Rendering Polygon Mesh using CGAL (%d/%d)", mark, progress_report_count);
+ pd->setLabelText(label);
+ QApplication::processEvents();
+}
+
+PolySet *DxfRotateExtrudeNode::render_polyset(render_mode_e rm) 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();
+ DxfData *dxf;
+
+ if (filename.isEmpty())
+ {
+ QTime t;
+ QProgressDialog *pd;
+
+ if (rm == RENDER_OPENCSG)
+ {
+ PRINT_NOCACHE("Processing uncached rotate_extrude outline...");
+ QApplication::processEvents();
+
+ t.start();
+ pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", QString(), 0, 100);
+ pd->setValue(0);
+ pd->setAutoClose(false);
+ pd->show();
+ QApplication::processEvents();
+
+ progress_report_prep((AbstractNode*)this, report_func, pd);
+ }
+
+ 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);
+
+ if (rm == RENDER_OPENCSG) {
+ progress_report_fin();
+ int s = t.elapsed() / 1000;
+ PRINTF_NOCACHE("..rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
+ delete pd;
+ }
+ } 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[fragments][dxf->paths[i].points.count()][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]);
+ }
+ }
+ }
+ }
+
+ PolySet::ps_cache.insert(key, new PolySet::ps_cache_entry(ps->link()));
+ print_messages_pop();
+ delete dxf;
+
+ return ps;
+}
+
+QString DxfRotateExtrudeNode::dump(QString indent) const
+{
+ if (dump_cache.isEmpty()) {
+ QString text;
+ struct stat st;
+ memset(&st, 0, sizeof(struct stat));
+ stat(filename.toAscii().data(), &st);
+ 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)st.st_mtime, (int)st.st_size,
+ layername.toAscii().data(), origin_x, origin_y, scale, convexity,
+ fn, fs, fa);
+ 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;
+}
+
diff --git a/src/dxftess-cgal.cc b/src/dxftess-cgal.cc
new file mode 100644
index 0000000..fa879b6
--- /dev/null
+++ b/src/dxftess-cgal.cc
@@ -0,0 +1,103 @@
+#include "openscad.h"
+#include "printutils.h"
+
+#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
+#include <CGAL/Constrained_Delaunay_triangulation_2.h>
+#include <CGAL/Delaunay_mesher_2.h>
+#include <CGAL/Delaunay_mesher_no_edge_refinement_2.h>
+#include <CGAL/Delaunay_mesh_face_base_2.h>
+#include <CGAL/Delaunay_mesh_criteria_2.h>
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Triangulation_vertex_base_2<K> Vb;
+typedef CGAL::Delaunay_mesh_face_base_2<K> Fb;
+typedef CGAL::Triangulation_data_structure_2<Vb, Fb> Tds;
+typedef CGAL::Constrained_Delaunay_triangulation_2<K, Tds> CDT;
+//typedef CGAL::Delaunay_mesh_criteria_2<CDT> Criteria;
+
+typedef CDT::Vertex_handle Vertex_handle;
+typedef CDT::Point CDTPoint;
+
+#include <CGAL/Mesh_2/Face_badness.h>
+
+template <class T> class DummyCriteria {
+public:
+ typedef double Quality;
+ class Is_bad {
+ public:
+ CGAL::Mesh_2::Face_badness operator()(const Quality) const {
+ return CGAL::Mesh_2::NOT_BAD;
+ }
+ CGAL::Mesh_2::Face_badness operator()(const typename T::Face_handle&, Quality&q) const {
+ q = 1;
+ return CGAL::Mesh_2::NOT_BAD;
+ }
+ };
+ Is_bad is_bad_object() const { return Is_bad(); }
+};
+
+void dxf_tesselate(PolySet *ps, DxfData *dxf, double rot, bool up, bool do_triangle_splitting, double h)
+{
+ CDT cdt;
+
+ // <pathidx,pointidx>
+ Grid3d< QPair<int,int> > point_to_path(GRID_FINE);
+
+ for (int i = 0; i < dxf->paths.count(); i++) {
+ if (!dxf->paths[i].is_closed)
+ continue;
+ Vertex_handle first, prev;
+ for (int j = 1; j < dxf->paths[i].points.count(); j++) {
+ double x = dxf->paths[i].points[j]->x;
+ double y = dxf->paths[i].points[j]->y;
+ point_to_path.data(x, y, h) = QPair<int,int>(i, j);
+ Vertex_handle vh = cdt.insert(CDTPoint(x, y));
+ if (j == 1) {
+ first = vh;
+ }
+ else {
+ cdt.insert_constraint(prev, vh);
+ }
+ prev = vh;
+ }
+ cdt.insert_constraint(prev, first);
+ }
+
+ std::list<CDTPoint> list_of_seeds;
+// FIXME: Give hints about holes here
+// list_of_seeds.push_back(CDTPoint(-1, -1));
+// list_of_seeds.push_back(CDTPoint(20, 50));
+ CGAL::refine_Delaunay_mesh_2_without_edge_refinement(cdt, list_of_seeds.begin(), list_of_seeds.end(),
+ DummyCriteria<CDT>());
+ CDT::Finite_faces_iterator iter = cdt.finite_faces_begin();
+ for( ; iter != cdt.finite_faces_end(); ++iter) {
+ if (!iter->is_in_domain()) continue;
+ ps->append_poly();
+
+ int path[3], point[3];
+ for (int i=0;i<3;i++) {
+ int idx = up ? i : (2-i);
+ double px = iter->vertex(idx)->point()[0];
+ double py = iter->vertex(idx)->point()[1];
+ ps->append_vertex(px * cos(rot*M_PI/180) + py * sin(rot*M_PI/180),
+ px * -sin(rot*M_PI/180) + py * cos(rot*M_PI/180),
+ h);
+ path[i] = point_to_path.data(px, py, h).first;
+ point[i] = point_to_path.data(px, py, h).second;
+ }
+
+ if (path[0] == path[1] && point[0] == 1 && point[1] == 2)
+ dxf->paths[path[0]].is_inner = up;
+ if (path[0] == path[1] && point[0] == 2 && point[1] == 1)
+ dxf->paths[path[0]].is_inner = !up;
+ if (path[1] == path[2] && point[1] == 1 && point[2] == 2)
+ dxf->paths[path[1]].is_inner = up;
+ if (path[1] == path[2] && point[1] == 2 && point[2] == 1)
+ dxf->paths[path[1]].is_inner = !up;
+
+ if (path[2] == path[0] && point[2] == 1 && point[0] == 2)
+ dxf->paths[path[2]].is_inner = up;
+ if (path[2] == path[0] && point[2] == 2 && point[0] == 1)
+ dxf->paths[path[2]].is_inner = !up;
+ }
+}
diff --git a/src/dxftess-glu.cc b/src/dxftess-glu.cc
new file mode 100644
index 0000000..9e5f530
--- /dev/null
+++ b/src/dxftess-glu.cc
@@ -0,0 +1,376 @@
+#ifdef WIN32
+# define STDCALL __stdcall
+#else
+# define STDCALL
+#endif
+
+#undef DEBUG_TRIANGLE_SPLITTING
+
+struct tess_vdata {
+ GLdouble v[3];
+};
+
+struct tess_triangle {
+ GLdouble *p[3];
+ tess_triangle() { p[0] = NULL; p[1] = NULL; p[2] = NULL; }
+ tess_triangle(double *p1, double *p2, double *p3) { p[0] = p1; p[1] = p2; p[2] = p3; }
+};
+
+static GLenum tess_type;
+static int tess_count;
+static QVector<tess_triangle> tess_tri;
+static GLdouble *tess_p1, *tess_p2;
+
+static void STDCALL tess_vertex(void *vertex_data)
+{
+ GLdouble *p = (double*)vertex_data;
+#if 0
+ printf(" %d: %f %f %f\n", tess_count, p[0], p[1], p[2]);
+#endif
+ if (tess_type == GL_TRIANGLE_FAN) {
+ if (tess_count == 0) {
+ tess_p1 = p;
+ }
+ if (tess_count == 1) {
+ tess_p2 = p;
+ }
+ if (tess_count > 1) {
+ tess_tri.append(tess_triangle(tess_p1, tess_p2, p));
+ tess_p2 = p;
+ }
+ }
+ if (tess_type == GL_TRIANGLE_STRIP) {
+ if (tess_count == 0) {
+ tess_p1 = p;
+ }
+ if (tess_count == 1) {
+ tess_p2 = p;
+ }
+ if (tess_count > 1) {
+ if (tess_count % 2 == 1) {
+ tess_tri.append(tess_triangle(tess_p2, tess_p1, p));
+ } else {
+ tess_tri.append(tess_triangle(tess_p1, tess_p2, p));
+ }
+ tess_p1 = tess_p2;
+ tess_p2 = p;
+ }
+ }
+ if (tess_type == GL_TRIANGLES) {
+ if (tess_count == 0) {
+ tess_p1 = p;
+ }
+ if (tess_count == 1) {
+ tess_p2 = p;
+ }
+ if (tess_count == 2) {
+ tess_tri.append(tess_triangle(tess_p1, tess_p2, p));
+ tess_count = -1;
+ }
+ }
+ tess_count++;
+}
+
+static void STDCALL tess_begin(GLenum type)
+{
+#if 0
+ if (type == GL_TRIANGLE_FAN) {
+ printf("GL_TRIANGLE_FAN:\n");
+ }
+ if (type == GL_TRIANGLE_STRIP) {
+ printf("GL_TRIANGLE_STRIP:\n");
+ }
+ if (type == GL_TRIANGLES) {
+ printf("GL_TRIANGLES:\n");
+ }
+#endif
+ tess_count = 0;
+ tess_type = type;
+}
+
+static void STDCALL tess_end(void)
+{
+ /* nothing to be done here */
+}
+
+static void STDCALL tess_error(GLenum errno)
+{
+ fprintf(stderr, "GLU tesselation error %s", gluErrorString(errno));
+ PRINTF("GLU tesselation error %s", gluErrorString(errno));
+}
+
+static void STDCALL tess_begin_data()
+{
+ PRINTF("GLU tesselation BEGIN_DATA\n");
+}
+
+static void STDCALL tess_edge_flag(GLboolean flag)
+{
+// PRINTF("GLU tesselation EDGE_FLAG\n");
+}
+
+static void STDCALL tess_edge_flag_data(GLboolean flag, void *polygon_data)
+{
+ PRINTF("GLU tesselation EDGE_FLAG_DATA\n");
+}
+static void STDCALL tess_vertex_data(void *vertex_data, void *polygon_data)
+{
+ PRINTF("GLU tesselation VERTEX_DATA\n");
+}
+static void STDCALL tess_end_data(void *polygon_data)
+{
+ PRINTF("GLU tesselation END_DATA\n");
+}
+static void STDCALL tess_combine(GLdouble coords[3], void *vertex_data[4],
+ GLfloat weight[4], void **outData )
+{
+ PRINTF("GLU tesselation COMBINE\n");
+}
+static void STDCALL tess_combine_data(GLdouble coords[3], void *vertex_data[4],
+ GLfloat weight[4], void **outData,
+ void *polygon_data)
+{
+ PRINTF("GLU tesselation COMBINE_DATA\n");
+}
+static void STDCALL tess_error_data(GLenum errno, void *polygon_data )
+{
+ PRINTF("GLU tesselation ERROR_DATA\n");
+}
+
+static bool point_on_line(double *p1, double *p2, double *p3)
+{
+ if (fabs(p1[0] - p2[0]) < 0.00001 && fabs(p1[1] - p2[1]) < 0.00001)
+ return false;
+
+ if (fabs(p3[0] - p2[0]) < 0.00001 && fabs(p3[1] - p2[1]) < 0.00001)
+ return false;
+
+ double v1[2] = { p2[0] - p1[0], p2[1] - p1[1] };
+ double v2[2] = { p3[0] - p1[0], p3[1] - p1[1] };
+
+ if (sqrt(v1[0]*v1[0] + v1[1]*v1[1]) > sqrt(v2[0]*v2[0] + v2[1]*v2[1]))
+ return false;
+
+ if (fabs(v1[0]) > fabs(v1[1])) {
+ // y = x * dy/dx
+ if (v2[0] == 0 || ((v1[0] > 0) != (v2[0] > 0)))
+ return false;
+ double v1_dy_dx = v1[1] / v1[0];
+ double v2_dy_dx = v2[1] / v2[0];
+ if (fabs(v1_dy_dx - v2_dy_dx) > 1e-15)
+ return false;
+ } else {
+ // x = y * dx/dy
+ if (v2[1] == 0 || ((v1[1] > 0) != (v2[1] > 0)))
+ return false;
+ double v1_dy_dx = v1[0] / v1[1];
+ double v2_dy_dx = v2[0] / v2[1];
+ if (fabs(v1_dy_dx - v2_dy_dx) > 1e-15)
+ return false;
+ }
+
+#if 0
+ printf("Point on line: %f/%f %f/%f %f/%f\n", p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);
+#endif
+ return true;
+}
+
+/*!
+ up: true if the polygon is facing in the normal direction (i.e. normal = [0,0,1])
+ rot: CLOCKWISE rotation around positive Z axis
+ */
+
+void dxf_tesselate(PolySet *ps, DxfData *dxf, double rot, bool up, bool do_triangle_splitting, double h)
+{
+ GLUtesselator *tobj = gluNewTess();
+
+ gluTessCallback(tobj, GLU_TESS_VERTEX, (void(STDCALL *)())&tess_vertex);
+ gluTessCallback(tobj, GLU_TESS_BEGIN, (void(STDCALL *)())&tess_begin);
+ gluTessCallback(tobj, GLU_TESS_END, (void(STDCALL *)())&tess_end);
+ gluTessCallback(tobj, GLU_TESS_ERROR, (void(STDCALL *)())&tess_error);
+
+ gluTessCallback(tobj, GLU_TESS_EDGE_FLAG, (void(STDCALL *)())&tess_edge_flag);
+// gluTessCallback(tobj, GLU_TESS_COMBINE, (void(STDCALL *)())&tess_combine);
+
+/* gluTessCallback(tobj, GLU_TESS_BEGIN_DATA, (void(STDCALL *)())&tess_begin_data); */
+/* gluTessCallback(tobj, GLU_TESS_EDGE_FLAG_DATA, (void(STDCALL *)())&tess_edge_flag_data); */
+/* gluTessCallback(tobj, GLU_TESS_VERTEX_DATA, (void(STDCALL *)())&tess_vertex_data); */
+/* gluTessCallback(tobj, GLU_TESS_END_DATA, (void(STDCALL *)())&tess_end_data); */
+/* gluTessCallback(tobj, GLU_TESS_COMBINE_DATA, (void(STDCALL *)())&tess_combine_data); */
+/* gluTessCallback(tobj, GLU_TESS_ERROR_DATA, (void(STDCALL *)())&tess_error_data); */
+
+
+ tess_tri.clear();
+ QList<tess_vdata> vl;
+
+ gluTessBeginPolygon(tobj, NULL);
+
+ gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
+ if (up) {
+ gluTessNormal(tobj, 0, 0, -1);
+ } else {
+ gluTessNormal(tobj, 0, 0, +1);
+ }
+
+ Grid3d< QPair<int,int> > point_to_path(GRID_COARSE);
+
+ for (int i = 0; i < dxf->paths.count(); i++) {
+ if (!dxf->paths[i].is_closed)
+ continue;
+ gluTessBeginContour(tobj);
+ for (int j = 1; j < dxf->paths[i].points.count(); j++) {
+ point_to_path.data(dxf->paths[i].points[j]->x,
+ dxf->paths[i].points[j]->y,
+ h) = QPair<int,int>(i, j);
+ vl.append(tess_vdata());
+ vl.last().v[0] = dxf->paths[i].points[j]->x;
+ vl.last().v[1] = dxf->paths[i].points[j]->y;
+ vl.last().v[2] = h;
+ gluTessVertex(tobj, vl.last().v, vl.last().v);
+ }
+ gluTessEndContour(tobj);
+ }
+
+ gluTessEndPolygon(tobj);
+ gluDeleteTess(tobj);
+
+#if 0
+ for (int i = 0; i < tess_tri.count(); i++) {
+ printf("~~~\n");
+ printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]);
+ printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]);
+ printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]);
+ }
+#endif
+
+ // GLU tessing sometimes generates degenerated triangles. We must find and remove
+ // them so we can use the triangle array with CGAL..
+ for (int i = 0; i < tess_tri.count(); i++) {
+ if (point_on_line(tess_tri[i].p[0], tess_tri[i].p[1], tess_tri[i].p[2]) ||
+ point_on_line(tess_tri[i].p[1], tess_tri[i].p[2], tess_tri[i].p[0]) ||
+ point_on_line(tess_tri[i].p[2], tess_tri[i].p[0], tess_tri[i].p[1])) {
+ // printf("DEBUG: Removed triangle\n");
+ tess_tri.remove(i--);
+ }
+ }
+
+ // GLU tessing creates T-junctions. This is ok for GL displaying but creates
+ // invalid polyhedrons for CGAL. So we split this tirangles up again in order
+ // to create polyhedrons that are also accepted by CGAL..
+ // All triangle edges are sorted by their atan2 and only edges with a simmilar atan2
+ // value are compared. This speeds up this code block dramatically (compared to the
+ // n^2 compares that are neccessary in the trivial implementation).
+#if 1
+ if (do_triangle_splitting)
+ {
+ bool added_triangles = true;
+ typedef QPair<int,int> QPair_ii;
+ QHash<int, QPair_ii> tri_by_atan2;
+ for (int i = 0; i < tess_tri.count(); i++)
+ for (int j = 0; j < 3; j++) {
+ int ai = (int)round(atan2(fabs(tess_tri[i].p[(j+1)%3][0] - tess_tri[i].p[j][0]),
+ fabs(tess_tri[i].p[(j+1)%3][1] - tess_tri[i].p[j][1])) / 0.001);
+ tri_by_atan2.insertMulti(ai, QPair<int,int>(i, j));
+ }
+ while (added_triangles)
+ {
+ added_triangles = false;
+#ifdef DEBUG_TRIANGLE_SPLITTING
+ printf("*** Triangle splitting (%d) ***\n", tess_tri.count()+1);
+#endif
+ for (int i = 0; i < tess_tri.count(); i++)
+ for (int k = 0; k < 3; k++)
+ {
+ QHash<QPair_ii, QPair_ii> possible_neigh;
+ int ai = (int)floor(atan2(fabs(tess_tri[i].p[(k+1)%3][0] - tess_tri[i].p[k][0]),
+ fabs(tess_tri[i].p[(k+1)%3][1] - tess_tri[i].p[k][1])) / 0.001 - 0.5);
+ for (int j = 0; j < 2; j++) {
+ foreach (const QPair_ii &jl, tri_by_atan2.values(ai+j))
+ if (i != jl.first)
+ possible_neigh[jl] = jl;
+ }
+#ifdef DEBUG_TRIANGLE_SPLITTING
+ printf("%d/%d: %d\n", i, k, possible_neigh.count());
+#endif
+ foreach (const QPair_ii &jl, possible_neigh) {
+ int j = jl.first;
+ for (int l = jl.second; l != (jl.second + 2) % 3; l = (l + 1) % 3)
+ if (point_on_line(tess_tri[i].p[k], tess_tri[j].p[l], tess_tri[i].p[(k+1)%3])) {
+#ifdef DEBUG_TRIANGLE_SPLITTING
+ printf("%% %f %f %f %f %f %f [%d %d]\n",
+ tess_tri[i].p[k][0], tess_tri[i].p[k][1],
+ tess_tri[j].p[l][0], tess_tri[j].p[l][1],
+ tess_tri[i].p[(k+1)%3][0], tess_tri[i].p[(k+1)%3][1],
+ i, j);
+#endif
+ tess_tri.append(tess_triangle(tess_tri[j].p[l],
+ tess_tri[i].p[(k+1)%3], tess_tri[i].p[(k+2)%3]));
+ for (int m = 0; m < 2; m++) {
+ int ai = (int)round(atan2(fabs(tess_tri.last().p[(m+1)%3][0] - tess_tri.last().p[m][0]),
+ fabs(tess_tri.last().p[(m+1)%3][1] - tess_tri.last().p[m][1])) / 0.001 );
+ tri_by_atan2.insertMulti(ai, QPair<int,int>(tess_tri.count()-1, m));
+ }
+ tess_tri[i].p[(k+1)%3] = tess_tri[j].p[l];
+ for (int m = 0; m < 2; m++) {
+ int ai = (int)round(atan2(fabs(tess_tri[i].p[(m+1)%3][0] - tess_tri[i].p[m][0]),
+ fabs(tess_tri[i].p[(m+1)%3][1] - tess_tri[i].p[m][1])) / 0.001 );
+ tri_by_atan2.insertMulti(ai, QPair<int,int>(i, m));
+ }
+ added_triangles = true;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ for (int i = 0; i < tess_tri.count(); i++)
+ {
+#if 0
+ printf("---\n");
+ printf(" %f %f %f\n", tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]);
+ printf(" %f %f %f\n", tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]);
+ printf(" %f %f %f\n", tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]);
+#endif
+ double x, y;
+ ps->append_poly();
+
+ x = tess_tri[i].p[0][0] * cos(rot*M_PI/180) + tess_tri[i].p[0][1] * sin(rot*M_PI/180);
+ y = tess_tri[i].p[0][0] * -sin(rot*M_PI/180) + tess_tri[i].p[0][1] * cos(rot*M_PI/180);
+ ps->insert_vertex(x, y, tess_tri[i].p[0][2]);
+
+ x = tess_tri[i].p[1][0] * cos(rot*M_PI/180) + tess_tri[i].p[1][1] * sin(rot*M_PI/180);
+ y = tess_tri[i].p[1][0] * -sin(rot*M_PI/180) + tess_tri[i].p[1][1] * cos(rot*M_PI/180);
+ ps->insert_vertex(x, y, tess_tri[i].p[1][2]);
+
+ x = tess_tri[i].p[2][0] * cos(rot*M_PI/180) + tess_tri[i].p[2][1] * sin(rot*M_PI/180);
+ y = tess_tri[i].p[2][0] * -sin(rot*M_PI/180) + tess_tri[i].p[2][1] * cos(rot*M_PI/180);
+ ps->insert_vertex(x, y, tess_tri[i].p[2][2]);
+
+ int i0 = point_to_path.data(tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]).first;
+ int j0 = point_to_path.data(tess_tri[i].p[0][0], tess_tri[i].p[0][1], tess_tri[i].p[0][2]).second;
+
+ int i1 = point_to_path.data(tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]).first;
+ int j1 = point_to_path.data(tess_tri[i].p[1][0], tess_tri[i].p[1][1], tess_tri[i].p[1][2]).second;
+
+ int i2 = point_to_path.data(tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]).first;
+ int j2 = point_to_path.data(tess_tri[i].p[2][0], tess_tri[i].p[2][1], tess_tri[i].p[2][2]).second;
+
+ if (i0 == i1 && j0 == 1 && j1 == 2)
+ dxf->paths[i0].is_inner = !up;
+ if (i0 == i1 && j0 == 2 && j1 == 1)
+ dxf->paths[i0].is_inner = up;
+
+ if (i1 == i2 && j1 == 1 && j2 == 2)
+ dxf->paths[i1].is_inner = !up;
+ if (i1 == i2 && j1 == 2 && j2 == 1)
+ dxf->paths[i1].is_inner = up;
+
+ if (i2 == i0 && j2 == 1 && j0 == 2)
+ dxf->paths[i2].is_inner = !up;
+ if (i2 == i0 && j2 == 2 && j0 == 1)
+ dxf->paths[i2].is_inner = up;
+ }
+
+ tess_tri.clear();
+}
diff --git a/src/dxftess.cc b/src/dxftess.cc
new file mode 100644
index 0000000..c0d4c0c
--- /dev/null
+++ b/src/dxftess.cc
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#ifdef CGAL_TESSELATE
+#include "dxftess-cgal.cc"
+#else
+#include "dxftess-glu.cc"
+#endif // CGAL_TESSELATE
+
+/*!
+ Converts all paths in the given DxfData to PolySet::borders polygons
+ without tesselating. Vertex ordering of the resulting polygons
+ will follow the paths' is_inner flag.
+*/
+void dxf_border_to_ps(PolySet *ps, DxfData *dxf)
+{
+ for (int i = 0; i < dxf->paths.count(); i++) {
+ const DxfData::Path &pt = dxf->paths[i];
+ if (!pt.is_closed)
+ continue;
+ ps->borders.append(PolySet::Polygon());
+ for (int j = 1; j < pt.points.count(); j++) {
+ double x = pt.points[j]->x, y = pt.points[j]->y, z = 0.0;
+ ps->grid.align(x, y, z);
+ if (pt.is_inner) {
+ ps->borders.last().append(PolySet::Point(x, y, z));
+ } else {
+ ps->borders.last().insert(0, PolySet::Point(x, y, z));
+ }
+ }
+ }
+}
diff --git a/src/export.cc b/src/export.cc
new file mode 100644
index 0000000..cdfbef6
--- /dev/null
+++ b/src/export.cc
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+#include "MainWindow.h"
+
+#include <QApplication>
+
+#ifdef ENABLE_CGAL
+
+void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, QProgressDialog *pd)
+{
+ CGAL_Polyhedron P;
+ root_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;
+
+ FILE *f = fopen(filename.toUtf8().data(), "w");
+ if (!f) {
+ PRINTA("Can't open STL file \"%1\" for STL export: %2",
+ filename, QString(strerror(errno)));
+ MainWindow::current_win = NULL;
+ return;
+ }
+ fprintf(f, "solid\n");
+
+ int facet_count = 0;
+ for (FCI fi = P.facets_begin(); fi != P.facets_end(); ++fi) {
+ HFCC hc = fi->facet_begin();
+ HFCC hc_end = hc;
+ Vertex v1, v2, v3;
+ v1 = *VCI((hc++)->vertex());
+ v3 = *VCI((hc++)->vertex());
+ do {
+ v2 = v3;
+ v3 = *VCI((hc++)->vertex());
+ double x1 = CGAL::to_double(v1.point().x());
+ double y1 = CGAL::to_double(v1.point().y());
+ double z1 = CGAL::to_double(v1.point().z());
+ double x2 = CGAL::to_double(v2.point().x());
+ double y2 = CGAL::to_double(v2.point().y());
+ double z2 = CGAL::to_double(v2.point().z());
+ double x3 = CGAL::to_double(v3.point().x());
+ double y3 = CGAL::to_double(v3.point().y());
+ double z3 = CGAL::to_double(v3.point().z());
+ QString vs1, vs2, vs3;
+ vs1.sprintf("%f %f %f", x1, y1, z1);
+ vs2.sprintf("%f %f %f", x2, y2, z2);
+ vs3.sprintf("%f %f %f", x3, y3, z3);
+ if (vs1 != vs2 && vs1 != vs3 && vs2 != vs3) {
+
+ double nx = (y1-y2)*(z1-z3) - (z1-z2)*(y1-y3);
+ double ny = (z1-z2)*(x1-x3) - (x1-x2)*(z1-z3);
+ double nz = (x1-x2)*(y1-y3) - (y1-y2)*(x1-x3);
+ double n_scale = 1 / sqrt(nx*nx + ny*ny + nz*nz);
+ fprintf(f, " facet normal %f %f %f\n",
+ nx * n_scale, ny * n_scale, nz * n_scale);
+ 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");
+ }
+ } while (hc != hc_end);
+ if (pd) {
+ pd->setValue(facet_count++);
+ QApplication::processEvents();
+ }
+ }
+
+ fprintf(f, "endsolid\n");
+ fclose(f);
+}
+
+void export_off(CGAL_Nef_polyhedron*, QString, QProgressDialog*)
+{
+ PRINTF("WARNING: OFF import is not implemented yet.");
+}
+
+void export_dxf(CGAL_Nef_polyhedron *root_N, QString filename, 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)));
+ MainWindow::current_win = NULL;
+ return;
+ }
+
+ fprintf(f, " 0\n");
+ fprintf(f, "SECTION\n");
+ fprintf(f, " 2\n");
+ fprintf(f, "ENTITIES\n");
+
+ DxfData dd(*root_N);
+ for (int i=0; i<dd.paths.size(); i++)
+ {
+ for (int j=1; j<dd.paths[i].points.size(); j++) {
+ DxfData::Point *p1 = dd.paths[i].points[j-1];
+ DxfData::Point *p2 = dd.paths[i].points[j];
+ double x1 = p1->x;
+ double y1 = p1->y;
+ double x2 = p2->x;
+ double y2 = p2->y;
+ fprintf(f, " 0\n");
+ fprintf(f, "LINE\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);
+ }
+ }
+
+ fprintf(f, " 0\n");
+ fprintf(f, "ENDSEC\n");
+ fprintf(f, " 0\n");
+ fprintf(f, "EOF\n");
+
+ fclose(f);
+}
+
+#endif
+
diff --git a/src/expr.cc b/src/expr.cc
new file mode 100644
index 0000000..e20e977
--- /dev/null
+++ b/src/expr.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.
+ *
+ * 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 "openscad.h"
+
+Expression::Expression()
+{
+ const_value = NULL;
+}
+
+Expression::~Expression()
+{
+ for (int i=0; i < children.size(); i++)
+ delete children[i];
+ if (const_value)
+ delete const_value;
+}
+
+Value Expression::evaluate(const Context *context) const
+{
+ if (type == "!")
+ return ! children[0]->evaluate(context);
+ if (type == "&&")
+ return children[0]->evaluate(context) && children[1]->evaluate(context);
+ if (type == "||")
+ return children[0]->evaluate(context) || children[1]->evaluate(context);
+ if (type == "*")
+ return children[0]->evaluate(context) * children[1]->evaluate(context);
+ if (type == "/")
+ return children[0]->evaluate(context) / children[1]->evaluate(context);
+ if (type == "%")
+ return children[0]->evaluate(context) % children[1]->evaluate(context);
+ if (type == "+")
+ return children[0]->evaluate(context) + children[1]->evaluate(context);
+ if (type == "-")
+ return children[0]->evaluate(context) - children[1]->evaluate(context);
+ if (type == "<")
+ return children[0]->evaluate(context) < children[1]->evaluate(context);
+ if (type == "<=")
+ return children[0]->evaluate(context) <= children[1]->evaluate(context);
+ if (type == "==")
+ return children[0]->evaluate(context) == children[1]->evaluate(context);
+ if (type == "!=")
+ return children[0]->evaluate(context) != children[1]->evaluate(context);
+ if (type == ">=")
+ return children[0]->evaluate(context) >= children[1]->evaluate(context);
+ if (type == ">")
+ return children[0]->evaluate(context) > children[1]->evaluate(context);
+ if (type == "?:") {
+ Value v = children[0]->evaluate(context);
+ if (v.type == Value::BOOL)
+ return children[v.b ? 1 : 2]->evaluate(context);
+ return Value();
+ }
+ if (type == "[]") {
+ Value v1 = children[0]->evaluate(context);
+ Value v2 = children[1]->evaluate(context);
+ if (v1.type == Value::VECTOR && v2.type == Value::NUMBER) {
+ int i = (int)(v2.num);
+ if (i < v1.vec.size())
+ return *v1.vec[i];
+ }
+ return Value();
+ }
+ if (type == "I")
+ return children[0]->evaluate(context).inv();
+ if (type == "C")
+ return *const_value;
+ if (type == "R") {
+ Value v1 = children[0]->evaluate(context);
+ Value v2 = children[1]->evaluate(context);
+ Value v3 = children[2]->evaluate(context);
+ if (v1.type == Value::NUMBER && v2.type == Value::NUMBER && v3.type == Value::NUMBER) {
+ Value r = Value();
+ r.type = Value::RANGE;
+ r.range_begin = v1.num;
+ r.range_step = v2.num;
+ r.range_end = v3.num;
+ return r;
+ }
+ return Value();
+ }
+ if (type == "V") {
+ Value v;
+ v.type = Value::VECTOR;
+ for (int i = 0; i < children.size(); i++)
+ v.vec.append(new Value(children[i]->evaluate(context)));
+ return v;
+ }
+ if (type == "L")
+ return context->lookup_variable(var_name);
+ if (type == "N")
+ {
+ Value v = children[0]->evaluate(context);
+
+ if (v.type == Value::VECTOR && var_name == QString("x"))
+ return *v.vec[0];
+ if (v.type == Value::VECTOR && var_name == QString("y"))
+ return *v.vec[1];
+ if (v.type == Value::VECTOR && var_name == QString("z"))
+ return *v.vec[2];
+
+ if (v.type == Value::RANGE && var_name == QString("begin"))
+ return Value(v.range_begin);
+ if (v.type == Value::RANGE && var_name == QString("step"))
+ return Value(v.range_step);
+ if (v.type == Value::RANGE && var_name == QString("end"))
+ return Value(v.range_end);
+
+ return Value();
+ }
+ if (type == "F") {
+ QVector<Value> argvalues;
+ for (int i=0; i < children.size(); i++)
+ argvalues.append(children[i]->evaluate(context));
+ return context->evaluate_function(call_funcname, call_argnames, argvalues);
+ }
+ abort();
+}
+
+QString Expression::dump() 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("[");
+ for (int i=0; i < children.size(); i++) {
+ if (i > 0)
+ text += QString(", ");
+ text += children[i]->dump();
+ }
+ return text + QString("]");
+ }
+ 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("(");
+ 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();
+ }
+ return text + QString(")");
+ }
+ abort();
+}
+
diff --git a/src/func.cc b/src/func.cc
new file mode 100644
index 0000000..1d70d98
--- /dev/null
+++ b/src/func.cc
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+
+AbstractFunction::~AbstractFunction()
+{
+}
+
+Value AbstractFunction::evaluate(const Context*, const QVector<QString>&, const QVector<Value>&) const
+{
+ return Value();
+}
+
+QString AbstractFunction::dump(QString indent, QString name) const
+{
+ return QString("%1abstract function %2();\n").arg(indent, name);
+}
+
+Function::~Function()
+{
+ for (int i=0; i < argexpr.size(); i++)
+ delete argexpr[i];
+ delete expr;
+}
+
+Value Function::evaluate(const Context *ctx, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues) const
+{
+ Context c(ctx);
+ c.args(argnames, argexpr, call_argnames, call_argvalues);
+ if (expr)
+ return expr->evaluate(&c);
+ return Value();
+}
+
+QString Function::dump(QString indent, QString name) const
+{
+ QString text = QString("%1function %2(").arg(indent, name);
+ for (int i=0; i < argnames.size(); i++) {
+ if (i > 0)
+ text += QString(", ");
+ text += argnames[i];
+ if (argexpr[i])
+ text += QString(" = ") + argexpr[i]->dump();
+ }
+ text += QString(") = %1;\n").arg(expr->dump());
+ return text;
+}
+
+QHash<QString, AbstractFunction*> builtin_functions;
+
+BuiltinFunction::~BuiltinFunction()
+{
+}
+
+Value BuiltinFunction::evaluate(const Context*, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues) const
+{
+ return eval_func(call_argnames, call_argvalues);
+}
+
+QString BuiltinFunction::dump(QString indent, QString name) const
+{
+ return QString("%1builtin function %2();\n").arg(indent, name);
+}
+
+static double deg2rad(double x)
+{
+ while (x < 0.0)
+ x += 360.0;
+ while (x >= 360.0)
+ x -= 360.0;
+ x = x * M_PI * 2.0 / 360.0;
+ return x;
+}
+
+static double rad2deg(double x)
+{
+ x = x * 360.0 / (M_PI * 2.0);
+ while (x < 0.0)
+ x += 360.0;
+ while (x >= 360.0)
+ x -= 360.0;
+ return x;
+}
+
+Value builtin_min(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() >= 1 && args[0].type == Value::NUMBER) {
+ double val = args[0].num;
+ for (int i = 1; i < args.size(); i++)
+ if (args[1].type == Value::NUMBER)
+ val = fmin(val, args[i].num);
+ return Value(val);
+ }
+ return Value();
+}
+
+Value builtin_max(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() >= 1 && args[0].type == Value::NUMBER) {
+ double val = args[0].num;
+ for (int i = 1; i < args.size(); i++)
+ if (args[1].type == Value::NUMBER)
+ val = fmax(val, args[i].num);
+ return Value(val);
+ }
+ return Value();
+}
+
+Value builtin_sin(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(sin(deg2rad(args[0].num)));
+ return Value();
+}
+
+Value builtin_cos(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(cos(deg2rad(args[0].num)));
+ return Value();
+}
+
+Value builtin_asin(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(rad2deg(asin(args[0].num)));
+ return Value();
+}
+
+Value builtin_acos(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(rad2deg(acos(args[0].num)));
+ return Value();
+}
+
+Value builtin_tan(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(tan(deg2rad(args[0].num)));
+ return Value();
+}
+
+Value builtin_atan(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 1 && args[0].type == Value::NUMBER)
+ return Value(rad2deg(atan(args[0].num)));
+ return Value();
+}
+
+Value builtin_atan2(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 2 && args[0].type == Value::NUMBER && args[1].type == Value::NUMBER)
+ return Value(rad2deg(atan2(args[0].num, args[1].num)));
+ return Value();
+}
+
+Value builtin_pow(const QVector<QString>&, const QVector<Value> &args)
+{
+ if (args.size() == 2 && args[0].type == Value::NUMBER && args[1].type == Value::NUMBER)
+ return Value(pow(args[0].num, args[1].num));
+ return Value();
+}
+
+Value builtin_str(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();
+ }
+ return Value(str);
+}
+
+Value builtin_lookup(const QVector<QString>&, const QVector<Value> &args)
+{
+ double p, low_p, low_v, high_p, high_v;
+ if (args.size() < 2 || !args[0].getnum(p) || args[1].vec.size() < 2 || args[1].vec[0]->vec.size() < 2)
+ return Value();
+ if (!args[1].vec[0]->getv2(low_p, low_v) || !args[1].vec[0]->getv2(high_p, high_v))
+ return Value();
+ for (int i = 1; i < args[1].vec.size(); i++) {
+ double this_p, this_v;
+ if (args[1].vec[i]->getv2(this_p, this_v)) {
+ if (this_p <= p && (this_p > low_p || low_p > p)) {
+ low_p = this_p;
+ low_v = this_v;
+ }
+ if (this_p >= p && (this_p < high_p || high_p < p)) {
+ high_p = this_p;
+ high_v = this_v;
+ }
+ }
+ }
+ if (p <= low_p)
+ return Value(low_v);
+ if (p >= high_p)
+ return Value(high_v);
+ double f = (p-low_p) / (high_p-low_p);
+ return Value(high_v * f + low_v * (1-f));
+}
+
+void initialize_builtin_functions()
+{
+ builtin_functions["min"] = new BuiltinFunction(&builtin_min);
+ builtin_functions["max"] = new BuiltinFunction(&builtin_max);
+ builtin_functions["sin"] = new BuiltinFunction(&builtin_sin);
+ builtin_functions["cos"] = new BuiltinFunction(&builtin_cos);
+ builtin_functions["asin"] = new BuiltinFunction(&builtin_asin);
+ builtin_functions["acos"] = new BuiltinFunction(&builtin_acos);
+ builtin_functions["tan"] = new BuiltinFunction(&builtin_tan);
+ builtin_functions["atan"] = new BuiltinFunction(&builtin_atan);
+ builtin_functions["atan2"] = new BuiltinFunction(&builtin_atan2);
+ builtin_functions["pow"] = new BuiltinFunction(&builtin_pow);
+ builtin_functions["str"] = new BuiltinFunction(&builtin_str);
+ builtin_functions["lookup"] = new BuiltinFunction(&builtin_lookup);
+ initialize_builtin_dxf_dim();
+}
+
+void destroy_builtin_functions()
+{
+ foreach (AbstractFunction *v, builtin_functions)
+ delete v;
+ builtin_functions.clear();
+}
+
diff --git a/src/glview.cc b/src/glview.cc
new file mode 100644
index 0000000..1068318
--- /dev/null
+++ b/src/glview.cc
@@ -0,0 +1,491 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+#include "GLView.h"
+#include "Preferences.h"
+
+#include <QApplication>
+#include <QWheelEvent>
+#include <QMouseEvent>
+#include <QMessageBox>
+#include <QTimer>
+
+#define FAR_FAR_AWAY 100000.0
+
+GLView::GLView(QWidget *parent) : QGLWidget(parent)
+{
+ 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;
+
+ mouse_drag_active = false;
+ last_mouse_x = 0;
+ last_mouse_y = 0;
+
+ orthomode = false;
+ showaxes = false;
+ showcrosshairs = false;
+
+ renderfunc = NULL;
+ renderfunc_vp = NULL;
+
+ for (int i = 0; i < 10; i++)
+ shaderinfo[i] = 0;
+
+ statusLabel = NULL;
+
+ setMouseTracking(true);
+#ifdef ENABLE_OPENCSG
+ opencsg_support = true;
+#endif
+}
+
+void GLView::setRenderFunc(void (*func)(void*), void *userdata)
+{
+ this->renderfunc = func;
+ this->renderfunc_vp = userdata;
+}
+
+void GLView::initializeGL()
+{
+ glEnable(GL_DEPTH_TEST);
+ glDepthRange(-FAR_FAR_AWAY, +FAR_FAR_AWAY);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+#ifdef ENABLE_OPENCSG
+ GLenum err = glewInit();
+ if (GLEW_OK != err) {
+ fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
+ }
+ 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);
+ }
+ }
+ } else {
+ opencsg_support = false;
+ QTimer::singleShot(0, this, SLOT(display_opengl20_warning()));
+ }
+#endif /* ENABLE_OPENCSG */
+}
+
+#ifdef ENABLE_OPENCSG
+void GLView::display_opengl20_warning()
+{
+ QMessageBox::warning(NULL, "GLEW: GL_VERSION_2_0 is not supported!",
+ "Warning: No support for OpenGL 2.0 found! OpenCSG View has been disabled.\n\n"
+ "It is highly recommended to use OpenSCAD on a system with OpenGL 2.0 "
+ "support. Please check if OpenGL 2.0 drivers are available for your "
+ "graphics hardware.");
+}
+#endif
+
+void GLView::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);
+}
+
+void GLView::paintGL()
+{
+ const QColor &col = Preferences::inst()->color(Preferences::BACKGROUND_COLOR);
+ glClearColor(col.redF(), col.greenF(), col.blueF(), 0.0);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ if (orthomode)
+ glOrtho(-w_h_ratio*viewer_distance/10, +w_h_ratio*viewer_distance/10,
+ -(1/w_h_ratio)*viewer_distance/10, +(1/w_h_ratio)*viewer_distance/10,
+ -FAR_FAR_AWAY, +FAR_FAR_AWAY);
+ else
+ glFrustum(-w_h_ratio, +w_h_ratio, -(1/w_h_ratio), +(1/w_h_ratio), +10.0, +FAR_FAR_AWAY);
+ gluLookAt(0.0, -viewer_distance, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ 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);
+
+ 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);
+
+ if (showcrosshairs)
+ {
+ glLineWidth(3);
+ const QColor &col = Preferences::inst()->color(Preferences::CROSSHAIR_COLOR);
+ glColor3f(col.redF(), col.greenF(), col.blueF());
+ glBegin(GL_LINES);
+ for (double xf = -1; xf <= +1; xf += 2)
+ for (double yf = -1; yf <= +1; yf += 2) {
+ double vd = viewer_distance/20;
+ glVertex3d(-xf*vd, -yf*vd, -vd);
+ glVertex3d(+xf*vd, +yf*vd, +vd);
+ }
+ glEnd();
+ }
+
+ glTranslated(object_trans_x, object_trans_y, object_trans_z);
+
+ // Large gray axis cross inline with the model
+ if (showaxes)
+ {
+ glLineWidth(1);
+ glColor3d(0.5, 0.5, 0.5);
+ glBegin(GL_LINES);
+ glVertex3d(-viewer_distance/10, 0, 0);
+ glVertex3d(+viewer_distance/10, 0, 0);
+ glVertex3d(0, -viewer_distance/10, 0);
+ glVertex3d(0, +viewer_distance/10, 0);
+ glVertex3d(0, 0, -viewer_distance/10);
+ glVertex3d(0, 0, +viewer_distance/10);
+ glEnd();
+ }
+
+ glDepthFunc(GL_LESS);
+ glCullFace(GL_BACK);
+ glDisable(GL_CULL_FACE);
+
+ glLineWidth(2);
+ glColor3d(1.0, 0.0, 0.0);
+
+ if (renderfunc)
+ renderfunc(renderfunc_vp);
+
+ // Small axis cross in the lower left corner
+ // FIXME: Adjust color to keep contrast with background
+ if (showaxes)
+ {
+ glDepthFunc(GL_ALWAYS);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glTranslated(-0.8, -0.8, 0);
+ glOrtho(-w_h_ratio*1000/10, +w_h_ratio*1000/10,
+ -(1/w_h_ratio)*1000/10, +(1/w_h_ratio)*1000/10,
+ -FAR_FAR_AWAY, +FAR_FAR_AWAY);
+ gluLookAt(0.0, -1000, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ 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);
+
+ glLineWidth(1);
+ glBegin(GL_LINES);
+ glColor3d(1.0, 0.0, 0.0);
+ glVertex3d(0, 0, 0); glVertex3d(10, 0, 0);
+ glColor3d(0.0, 1.0, 0.0);
+ glVertex3d(0, 0, 0); glVertex3d(0, 10, 0);
+ glColor3d(0.0, 0.0, 1.0);
+ glVertex3d(0, 0, 0); glVertex3d(0, 0, 10);
+ glEnd();
+
+ GLdouble mat_model[16];
+ glGetDoublev(GL_MODELVIEW_MATRIX, mat_model);
+
+ GLdouble mat_proj[16];
+ glGetDoublev(GL_PROJECTION_MATRIX, mat_proj);
+
+ GLint viewport[4];
+ glGetIntegerv(GL_VIEWPORT, viewport);
+
+ GLdouble xlabel_x, xlabel_y, xlabel_z;
+ gluProject(12, 0, 0, mat_model, mat_proj, viewport, &xlabel_x, &xlabel_y, &xlabel_z);
+ xlabel_x = round(xlabel_x); xlabel_y = round(xlabel_y);
+
+ GLdouble ylabel_x, ylabel_y, ylabel_z;
+ gluProject(0, 12, 0, mat_model, mat_proj, viewport, &ylabel_x, &ylabel_y, &ylabel_z);
+ ylabel_x = round(ylabel_x); ylabel_y = round(ylabel_y);
+
+ GLdouble zlabel_x, zlabel_y, zlabel_z;
+ gluProject(0, 0, 12, mat_model, mat_proj, viewport, &zlabel_x, &zlabel_y, &zlabel_z);
+ zlabel_x = round(zlabel_x); zlabel_y = round(zlabel_y);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glTranslated(-1, -1, 0);
+ glScaled(2.0/viewport[2], 2.0/viewport[3], 1);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ // FIXME: Adjust color to keep contrast with background
+ glColor3d(0.0, 0.0, 0.0);
+ glBegin(GL_LINES);
+ // X Label
+ glVertex3d(xlabel_x-3, xlabel_y-3, 0); glVertex3d(xlabel_x+3, xlabel_y+3, 0);
+ glVertex3d(xlabel_x-3, xlabel_y+3, 0); glVertex3d(xlabel_x+3, xlabel_y-3, 0);
+ // Y Label
+ glVertex3d(ylabel_x-3, ylabel_y-3, 0); glVertex3d(ylabel_x+3, ylabel_y+3, 0);
+ glVertex3d(ylabel_x-3, ylabel_y+3, 0); glVertex3d(ylabel_x, ylabel_y, 0);
+ // Z Label
+ glVertex3d(zlabel_x-3, zlabel_y-3, 0); glVertex3d(zlabel_x+3, zlabel_y-3, 0);
+ glVertex3d(zlabel_x-3, zlabel_y+3, 0); glVertex3d(zlabel_x+3, zlabel_y+3, 0);
+ glVertex3d(zlabel_x-3, zlabel_y-3, 0); glVertex3d(zlabel_x+3, zlabel_y+3, 0);
+ glEnd();
+ }
+
+ if (statusLabel) {
+ QString msg;
+ msg.sprintf("Viewport: translate = [ %.2f %.2f %.2f ], rotate = [ %.2f %.2f %.2f ], distance = %.2f",
+ -object_trans_x, -object_trans_y, -object_trans_z,
+ fmodf(360 - object_rot_x + 90, 360), fmodf(360 - object_rot_y, 360), fmodf(360 - object_rot_z, 360), viewer_distance);
+ statusLabel->setText(msg);
+ }
+}
+
+void GLView::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Plus) {
+ viewer_distance *= 0.9;
+ updateGL();
+ return;
+ }
+ if (event->key() == Qt::Key_Minus) {
+ viewer_distance /= 0.9;
+ updateGL();
+ return;
+ }
+}
+
+void GLView::wheelEvent(QWheelEvent *event)
+{
+ viewer_distance *= pow(0.9, event->delta() / 120.0);
+ updateGL();
+}
+
+void GLView::mousePressEvent(QMouseEvent *event)
+{
+ mouse_drag_active = true;
+ last_mouse_x = event->globalX();
+ last_mouse_y = event->globalY();
+ grabMouse();
+ setFocus();
+}
+
+static void mat_id(double *trg)
+{
+ for (int i = 0; i < 16; i++)
+ trg[i] = i%5 == 0;
+}
+
+static void mat_mul(double *trg, const double *m1, const double *m2)
+{
+ double m[16];
+ for (int x = 0; x < 4; x++)
+ for (int y = 0; y < 4; y++)
+ {
+ m[x+y*4] = 0;
+ for (int i = 0; i < 4; i++)
+ m[x+y*4] += m1[i+y*4] * m2[x+i*4];
+ }
+ for (int i = 0; i < 16; i++)
+ trg[i] = m[i];
+}
+
+static void mat_rot(double *trg, double angle, double x, double y, double z)
+{
+ double s = sin(M_PI*angle/180), c = cos(M_PI*angle/180);
+ double cc = 1 - c;
+ double m[16] = {
+ x*x*cc+c, x*y*cc-z*s, x*z*cc+y*s, 0,
+ y*x*cc+z*s, y*y*cc+c, y*z*cc-x*s, 0,
+ x*z*cc-y*s, y*z*cc+x*s, z*z*cc+c, 0,
+ 0, 0, 0, 1
+ };
+ for (int i = 0; i < 16; i++)
+ trg[i] = m[i];
+}
+
+void GLView::mouseMoveEvent(QMouseEvent *event)
+{
+ int this_mouse_x = event->globalX();
+ int this_mouse_y = event->globalY();
+ if (mouse_drag_active) {
+ if ((event->buttons() & Qt::LeftButton) != 0) {
+ object_rot_x += (this_mouse_y-last_mouse_y) * 0.7;
+ if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0)
+ object_rot_y += (this_mouse_x-last_mouse_x) * 0.7;
+ else
+ object_rot_z += (this_mouse_x-last_mouse_x) * 0.7;
+ while (object_rot_x < 0)
+ object_rot_x += 360;
+ while (object_rot_x >= 360)
+ object_rot_x -= 360;
+ while (object_rot_y < 0)
+ object_rot_y += 360;
+ while (object_rot_y >= 360)
+ object_rot_y -= 360;
+ while (object_rot_z < 0)
+ object_rot_z += 360;
+ while (object_rot_z >= 360)
+ object_rot_z -= 360;
+ } else {
+ double mx = +(this_mouse_x-last_mouse_x) * viewer_distance/1000;
+ double my = -(this_mouse_y-last_mouse_y) * viewer_distance/1000;
+ double rx[16], ry[16], rz[16], tm[16];
+ mat_rot(rx, -object_rot_x, 1.0, 0.0, 0.0);
+ mat_rot(ry, -object_rot_y, 0.0, 1.0, 0.0);
+ mat_rot(rz, -object_rot_z, 0.0, 0.0, 1.0);
+ mat_id(tm);
+ mat_mul(tm, rx, tm);
+ mat_mul(tm, ry, tm);
+ mat_mul(tm, rz, tm);
+ double vec[16] = {
+ 0, 0, 0, mx,
+ 0, 0, 0, 0,
+ 0, 0, 0, my,
+ 0, 0, 0, 1
+ };
+ if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) {
+ vec[3] = 0;
+ vec[7] = my;
+ vec[11] = 0;
+ }
+ mat_mul(tm, tm, vec);
+ object_trans_x += tm[3];
+ object_trans_y += tm[7];
+ object_trans_z += tm[11];
+ }
+ updateGL();
+ emit doAnimateUpdate();
+ }
+ last_mouse_x = this_mouse_x;
+ last_mouse_y = this_mouse_y;
+}
+
+void GLView::mouseReleaseEvent(QMouseEvent*)
+{
+ mouse_drag_active = false;
+ releaseMouse();
+}
+
diff --git a/src/highlighter.cc b/src/highlighter.cc
new file mode 100644
index 0000000..5e15867
--- /dev/null
+++ b/src/highlighter.cc
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+
+Highlighter::Highlighter(QTextDocument *parent)
+ : QSyntaxHighlighter(parent)
+{
+}
+
+void Highlighter::highlightBlock(const QString &text)
+{
+ int n = previousBlockState();
+ if (n < 0)
+ n = 0;
+ int k = n + text.size() + 1;
+ setCurrentBlockState(k);
+ if (parser_error_pos >= n && parser_error_pos < k) {
+ QTextCharFormat style;
+ style.setBackground(Qt::red);
+ setFormat(0, text.size(), style);
+#if 0
+ style.setBackground(Qt::black);
+ style.setForeground(Qt::white);
+ setFormat(parser_error_pos - n, 1, style);
+#endif
+ }
+}
+
diff --git a/src/import.cc b/src/import.cc
new file mode 100644
index 0000000..c9b1a66
--- /dev/null
+++ b/src/import.cc
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#include <QFile>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+enum import_type_e {
+ TYPE_STL,
+ TYPE_OFF,
+ TYPE_DXF
+};
+
+class ImportModule : public AbstractModule
+{
+public:
+ import_type_e type;
+ ImportModule(import_type_e type) : type(type) { }
+ 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) {
+ argnames = QVector<QString>() << "file" << "layer" << "convexity" << "origin" << "scale";
+ } else {
+ argnames = QVector<QString>() << "file" << "convexity";
+ }
+ QVector<Expression*> argexpr;
+
+ // Map old argnames to new argnames for compatibility
+ QVector<QString> inst_argnames = inst->argnames;
+ for (int i=0; i<inst_argnames.size(); i++) {
+ if (inst_argnames[i] == "filename")
+ inst_argnames[i] = "file";
+ if (inst_argnames[i] == "layername")
+ inst_argnames[i] = "layer";
+ }
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst_argnames, inst->argvalues);
+
+ node->fn = c.lookup_variable("$fn").num;
+ node->fs = c.lookup_variable("$fs").num;
+ node->fa = c.lookup_variable("$fa").num;
+
+ node->filename = c.lookup_variable("file").text;
+ node->layername = c.lookup_variable("layer", true).text;
+ node->convexity = c.lookup_variable("convexity", true).num;
+
+ if (node->convexity <= 0)
+ node->convexity = 1;
+
+ Value origin = c.lookup_variable("origin", true);
+ node->origin_x = node->origin_y = 0;
+ origin.getv2(node->origin_x, node->origin_y);
+
+ node->scale = c.lookup_variable("scale", true).num;
+
+ if (node->scale <= 0)
+ node->scale = 1;
+
+ return node;
+}
+
+void register_builtin_import()
+{
+ builtin_modules["import_stl"] = new ImportModule(TYPE_STL);
+ builtin_modules["import_off"] = new ImportModule(TYPE_OFF);
+ builtin_modules["import_dxf"] = new ImportModule(TYPE_DXF);
+}
+
+PolySet *ImportNode::render_polyset(render_mode_e) const
+{
+ PolySet *p = new PolySet();
+ p->convexity = convexity;
+
+ if (type == TYPE_STL)
+ {
+ handle_dep(filename);
+ QFile f(filename);
+ if (!f.open(QIODevice::ReadOnly)) {
+ PRINTF("WARNING: Can't open import file `%s'.", filename.toAscii().data());
+ return p;
+ }
+
+ QByteArray data = f.read(5);
+ if (data.size() == 5 && QString(data) == QString("solid"))
+ {
+ int i = 0;
+ double vdata[3][3];
+ QRegExp splitre = QRegExp("\\s*(vertex)?\\s+");
+ f.readLine();
+ while (!f.atEnd())
+ {
+ QString line = QString(f.readLine()).remove("\n").remove("\r");
+ if (line.contains("solid") || line.contains("facet") || line.contains("endloop"))
+ continue;
+ if (line.contains("outer loop")) {
+ i = 0;
+ continue;
+ }
+ if (line.contains("vertex")) {
+ QStringList tokens = line.split(splitre);
+ bool ok[3] = { false, false, false };
+ if (tokens.size() == 4) {
+ vdata[i][0] = tokens[1].toDouble(&ok[0]);
+ vdata[i][1] = tokens[2].toDouble(&ok[1]);
+ vdata[i][2] = tokens[3].toDouble(&ok[2]);
+ }
+ if (!ok[0] || !ok[1] || !ok[2]) {
+ PRINTF("WARNING: Can't parse vertex line `%s'.", line.toAscii().data());
+ i = 10;
+ } else if (++i == 3) {
+ p->append_poly();
+ p->append_vertex(vdata[0][0], vdata[0][1], vdata[0][2]);
+ p->append_vertex(vdata[1][0], vdata[1][1], vdata[1][2]);
+ p->append_vertex(vdata[2][0], vdata[2][1], vdata[2][2]);
+ }
+ }
+ }
+ }
+ else
+ {
+ f.read(80-5+4);
+ while (1) {
+ struct {
+ float i, j, k;
+ float x1, y1, z1;
+ float x2, y2, z2;
+ float x3, y3, z3;
+ unsigned short acount;
+ } __attribute__ ((packed)) data;
+ if (f.read((char*)&data, sizeof(data)) != sizeof(data))
+ break;
+ p->append_poly();
+ p->append_vertex(data.x1, data.y1, data.z1);
+ p->append_vertex(data.x2, data.y2, data.z2);
+ p->append_vertex(data.x3, data.y3, data.z3);
+ }
+ }
+ }
+
+ if (type == TYPE_OFF)
+ {
+ PRINTF("WARNING: OFF import is not implemented yet.");
+ }
+
+ if (type == TYPE_DXF)
+ {
+ DxfData dd(fn, fs, fa, filename, layername, origin_x, origin_y, scale);
+ p->is2d = true;
+ dxf_tesselate(p, &dd, 0, true, false, 0);
+ dxf_border_to_ps(p, &dd);
+ }
+
+ return p;
+}
+
+QString ImportNode::dump(QString indent) 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, fs, fa);
+ ((AbstractNode*)this)->dump_cache = indent + QString("n%1: ").arg(idx) + text;
+ }
+ return dump_cache;
+}
+
diff --git a/src/mainwin.cc b/src/mainwin.cc
new file mode 100644
index 0000000..7abb4ed
--- /dev/null
+++ b/src/mainwin.cc
@@ -0,0 +1,1800 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "MainWindow.h"
+#include "Preferences.h"
+#include "printutils.h"
+
+#include <QMenu>
+#include <QTime>
+#include <QMenuBar>
+#include <QSplitter>
+#include <QFileDialog>
+#include <QApplication>
+#include <QProgressDialog>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QLabel>
+#include <QFileInfo>
+#include <QTextStream>
+#include <QStatusBar>
+#include <QDropEvent>
+#include <QMimeData>
+#include <QUrl>
+#include <QTimer>
+#include <QMessageBox>
+#include <QDesktopServices>
+#include <QSettings>
+
+//for chdir
+#include <unistd.h>
+
+#ifdef ENABLE_CGAL
+
+#if 1
+#include "CGAL_renderer.h"
+using OpenSCAD::OGL::Polyhedron;
+using OpenSCAD::OGL::SNC_BOUNDARY;
+using OpenSCAD::OGL::SNC_SKELETON;
+using OpenSCAD::OGL::Nef3_Converter;
+#else
+// a little hackish: we need access to default-private members of
+// CGAL::OGL::Nef3_Converter so we can implement our own draw function
+// that does not scale the model. so we define 'class' to 'struct'
+// for this header..
+//
+// theoretically there could be two problems:
+// 1.) defining language keyword with the pre processor is illegal afair
+// 2.) the compiler could use a different memory layout or name mangling for structs
+//
+// both does not seam to be the case with todays compilers...
+//
+#define class struct
+#include <CGAL/Nef_3/OGL_helper.h>
+#undef class
+using CGAL::OGL::Polyhedron;
+using CGAL::OGL::SNC_BOUNDARY;
+using CGAL::OGL::SNC_SKELETON;
+using CGAL::OGL::Nef3_Converter;
+#endif
+#endif // ENABLE_CGAL
+
+#define QUOTE(x__) # x__
+#define QUOTED(x__) QUOTE(x__)
+
+static char helptitle[] =
+ "OpenSCAD "
+ QUOTED(OPENSCAD_VERSION)
+ " (www.openscad.org)\n";
+static char copyrighttext[] =
+ "Copyright (C) 2009 Clifford Wolf <clifford@clifford.at>\n"
+ "\n"
+ "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.";
+
+QPointer<MainWindow> MainWindow::current_win = NULL;
+
+MainWindow::MainWindow(const char *filename)
+{
+ setupUi(this);
+
+ 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);
+
+ root_module = NULL;
+ absolute_root_node = NULL;
+ root_raw_term = NULL;
+ root_norm_term = NULL;
+ root_chain = NULL;
+#ifdef ENABLE_CGAL
+ this->root_N = NULL;
+ this->recreate_cgal_ogl_p = false;
+ cgal_ogl_p = NULL;
+ cgal_ogl_ps = NULL;
+#endif
+
+ highlights_chain = NULL;
+ background_chain = NULL;
+ root_node = NULL;
+ enableOpenCSG = false;
+
+ tval = 0;
+ fps = 0;
+ fsteps = 1;
+
+ highlighter = NULL;
+
+ editor->setWordWrapMode(QTextOption::WrapAnywhere); // Not designable
+ setFont("", 0); // Init default font
+
+ screen->statusLabel = new QLabel(this);
+ statusBar()->addWidget(screen->statusLabel);
+
+ animate_timer = new QTimer(this);
+ connect(animate_timer, SIGNAL(timeout()), this, SLOT(updateTVal()));
+
+ connect(e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile()));
+ connect(e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps()));
+
+ animate_panel->hide();
+
+ // File menu
+ connect(this->fileActionNew, SIGNAL(triggered()), this, SLOT(actionNew()));
+ connect(this->fileActionOpen, SIGNAL(triggered()), this, SLOT(actionOpen()));
+ connect(this->fileActionSave, SIGNAL(triggered()), this, SLOT(actionSave()));
+ connect(this->fileActionSaveAs, SIGNAL(triggered()), this, SLOT(actionSaveAs()));
+ connect(this->fileActionReload, SIGNAL(triggered()), this, SLOT(actionReload()));
+ connect(this->fileActionQuit, SIGNAL(triggered()), this, SLOT(quit()));
+#ifndef __APPLE__
+ this->fileActionSave->setShortcut(QKeySequence(Qt::Key_F2));
+ this->fileActionReload->setShortcut(QKeySequence(Qt::Key_F3));
+#endif
+ // Open Recent
+ for (int i = 0;i<maxRecentFiles; i++) {
+ this->actionRecentFile[i] = new QAction(this);
+ this->actionRecentFile[i]->setVisible(false);
+ this->menuOpenRecent->addAction(this->actionRecentFile[i]);
+ connect(this->actionRecentFile[i], SIGNAL(triggered()),
+ this, SLOT(actionOpenRecent()));
+ }
+ this->menuOpenRecent->addSeparator();
+ this->menuOpenRecent->addAction(this->fileActionClearRecent);
+ connect(this->fileActionClearRecent, SIGNAL(triggered()),
+ this, SLOT(clearRecentFiles()));
+
+ QDir examplesdir(QApplication::instance()->applicationDirPath());
+#ifdef Q_WS_MAC
+ examplesdir.cd("../Resources"); // Examples can be bundled
+ if (!examplesdir.exists("examples")) examplesdir.cd("../../..");
+#endif
+ if (examplesdir.cd("examples")) {
+ this->examplesdir = examplesdir.path();
+
+ QStringList examples = examplesdir.entryList(QStringList("*.scad"),
+ QDir::Files | QDir::Readable, QDir::Name);
+ foreach (const QString &ex, examples) {
+ this->menuExamples->addAction(ex, this, SLOT(actionOpenExample()));
+ }
+ }
+
+ // Edit menu
+ connect(this->editActionUndo, SIGNAL(triggered()), editor, SLOT(undo()));
+ connect(this->editActionRedo, SIGNAL(triggered()), editor, SLOT(redo()));
+ connect(this->editActionCut, SIGNAL(triggered()), editor, SLOT(cut()));
+ connect(this->editActionCopy, SIGNAL(triggered()), editor, SLOT(copy()));
+ connect(this->editActionPaste, SIGNAL(triggered()), editor, SLOT(paste()));
+ connect(this->editActionIndent, SIGNAL(triggered()), this, SLOT(editIndent()));
+ connect(this->editActionUnindent, SIGNAL(triggered()), this, SLOT(editUnindent()));
+ connect(this->editActionComment, SIGNAL(triggered()), this, SLOT(editComment()));
+ connect(this->editActionUncomment, SIGNAL(triggered()), this, SLOT(editUncomment()));
+ connect(this->editActionPasteVPT, SIGNAL(triggered()), this, SLOT(pasteViewportTranslation()));
+ connect(this->editActionPasteVPR, SIGNAL(triggered()), this, SLOT(pasteViewportRotation()));
+ connect(this->editActionZoomIn, SIGNAL(triggered()), editor, SLOT(zoomIn()));
+ connect(this->editActionZoomOut, SIGNAL(triggered()), editor, SLOT(zoomOut()));
+ connect(this->editActionHide, SIGNAL(triggered()), this, SLOT(hideEditor()));
+ connect(this->editActionPreferences, SIGNAL(triggered()), this, SLOT(preferences()));
+
+ // Design menu
+ connect(this->designActionReloadAndCompile, SIGNAL(triggered()), this, SLOT(actionReloadCompile()));
+ connect(this->designActionCompile, SIGNAL(triggered()), this, SLOT(actionCompile()));
+#ifdef ENABLE_CGAL
+ connect(this->designActionCompileAndRender, SIGNAL(triggered()), this, SLOT(actionRenderCGAL()));
+#else
+ this->designActionCompileAndRender->setVisible(false);
+#endif
+ connect(this->designActionDisplayAST, SIGNAL(triggered()), this, SLOT(actionDisplayAST()));
+ connect(this->designActionDisplayCSGTree, SIGNAL(triggered()), this, SLOT(actionDisplayCSGTree()));
+ connect(this->designActionDisplayCSGProducts, SIGNAL(triggered()), this, SLOT(actionDisplayCSGProducts()));
+ connect(this->designActionExportSTL, SIGNAL(triggered()), this, SLOT(actionExportSTL()));
+ connect(this->designActionExportOFF, SIGNAL(triggered()), this, SLOT(actionExportOFF()));
+ connect(this->designActionExportDXF, SIGNAL(triggered()), this, SLOT(actionExportDXF()));
+ connect(this->designActionFlushCaches, SIGNAL(triggered()), this, SLOT(actionFlushCaches()));
+
+ // View menu
+#ifndef ENABLE_OPENCSG
+ this->viewActionOpenCSG->setVisible(false);
+#else
+ connect(this->viewActionOpenCSG, SIGNAL(triggered()), this, SLOT(viewModeOpenCSG()));
+ if (!screen->hasOpenCSGSupport()) {
+ this->viewActionOpenCSG->setEnabled(false);
+ }
+#endif
+
+#ifdef ENABLE_CGAL
+ connect(this->viewActionCGALSurfaces, SIGNAL(triggered()), this, SLOT(viewModeCGALSurface()));
+ connect(this->viewActionCGALGrid, SIGNAL(triggered()), this, SLOT(viewModeCGALGrid()));
+#else
+ this->viewActionCGALSurfaces->setVisible(false);
+ this->viewActionCGALGrid->setVisible(false);
+#endif
+ connect(this->viewActionThrownTogether, SIGNAL(triggered()), this, SLOT(viewModeThrownTogether()));
+ connect(this->viewActionShowEdges, SIGNAL(triggered()), this, SLOT(viewModeShowEdges()));
+ connect(this->viewActionShowAxes, SIGNAL(triggered()), this, SLOT(viewModeShowAxes()));
+ connect(this->viewActionShowCrosshairs, SIGNAL(triggered()), this, SLOT(viewModeShowCrosshairs()));
+ connect(this->viewActionAnimate, SIGNAL(triggered()), this, SLOT(viewModeAnimate()));
+ connect(this->viewActionTop, SIGNAL(triggered()), this, SLOT(viewAngleTop()));
+ connect(this->viewActionBottom, SIGNAL(triggered()), this, SLOT(viewAngleBottom()));
+ connect(this->viewActionLeft, SIGNAL(triggered()), this, SLOT(viewAngleLeft()));
+ connect(this->viewActionRight, SIGNAL(triggered()), this, SLOT(viewAngleRight()));
+ connect(this->viewActionFront, SIGNAL(triggered()), this, SLOT(viewAngleFront()));
+ connect(this->viewActionBack, SIGNAL(triggered()), this, SLOT(viewAngleBack()));
+ connect(this->viewActionDiagonal, SIGNAL(triggered()), this, SLOT(viewAngleDiagonal()));
+ connect(this->viewActionCenter, SIGNAL(triggered()), this, SLOT(viewCenter()));
+ connect(this->viewActionPerspective, SIGNAL(triggered()), this, SLOT(viewPerspective()));
+ connect(this->viewActionOrthogonal, SIGNAL(triggered()), this, SLOT(viewOrthogonal()));
+ connect(this->viewActionHide, SIGNAL(triggered()), this, SLOT(hideConsole()));
+
+// #ifdef ENABLE_CGAL
+// viewActionCGALSurface = menu->addAction("CGAL Surfaces", this, SLOT(viewModeCGALSurface()), QKeySequence(Qt::Key_F10));
+// viewActionCGALGrid = menu->addAction("CGAL Grid Only", this, SLOT(viewModeCGALGrid()), QKeySequence(Qt::Key_F11));
+// #endif
+
+ // Help menu
+ connect(this->helpActionAbout, SIGNAL(triggered()), this, SLOT(helpAbout()));
+ connect(this->helpActionManual, SIGNAL(triggered()), this, SLOT(helpManual()));
+
+
+ console->setReadOnly(true);
+ current_win = this;
+
+ PRINT(helptitle);
+ PRINT(copyrighttext);
+ PRINT("");
+
+ if (filename) {
+ openFile(filename);
+ } else {
+ setFileName("");
+ }
+
+ connect(editor->document(), SIGNAL(contentsChanged()), this, SLOT(animateUpdateDocChanged()));
+ connect(editor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool)));
+ connect(editor->document(), SIGNAL(modificationChanged(bool)), fileActionSave, SLOT(setEnabled(bool)));
+ connect(screen, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate()));
+
+ connect(Preferences::inst(), SIGNAL(requestRedraw()), this->screen, SLOT(updateGL()));
+ connect(Preferences::inst(), SIGNAL(fontChanged(const QString&,uint)),
+ this, SLOT(setFont(const QString&,uint)));
+ Preferences::inst()->apply();
+
+
+ // display this window and check for OpenGL 2.0 (OpenCSG) support
+ viewModeThrownTogether();
+ show();
+
+ // make sure it looks nice..
+ resize(800, 600);
+ splitter1->setSizes(QList<int>() << 400 << 400);
+ splitter2->setSizes(QList<int>() << 400 << 200);
+
+#ifdef ENABLE_OPENCSG
+ viewModeOpenCSG();
+#else
+ viewModeThrownTogether();
+#endif
+ viewPerspective();
+
+ setAcceptDrops(true);
+ current_win = NULL;
+}
+
+MainWindow::~MainWindow()
+{
+ if (root_module)
+ delete root_module;
+ if (root_node)
+ delete root_node;
+#ifdef ENABLE_CGAL
+ if (this->root_N)
+ delete this->root_N;
+ if (cgal_ogl_p) {
+ Polyhedron *p = (Polyhedron*)cgal_ogl_p;
+ delete p;
+ }
+ if (cgal_ogl_ps)
+ cgal_ogl_ps->unlink();
+#endif
+}
+
+/*!
+ Requests to open a file from an external event, e.g. by double-clicking a filename.
+ */
+#ifdef ENABLE_MDI
+void MainWindow::requestOpenFile(const QString &filename)
+{
+ new MainWindow(filename.toUtf8());
+}
+#else
+void MainWindow::requestOpenFile(const QString &)
+{
+}
+#endif
+
+void
+MainWindow::openFile(const QString &new_filename)
+{
+#ifdef ENABLE_MDI
+ if (!editor->toPlainText().isEmpty()) {
+ new MainWindow(new_filename.toUtf8());
+ current_win = NULL;
+ return;
+ }
+#endif
+ setFileName(new_filename);
+
+ load();
+}
+
+void
+MainWindow::setFileName(const QString &filename)
+{
+ if (filename.isEmpty()) {
+ this->fileName.clear();
+ setWindowTitle("OpenSCAD - New Document[*]");
+ }
+ else {
+ QFileInfo fileinfo(filename);
+ QString infoFileName = fileinfo.canonicalFilePath();
+ setWindowTitle("OpenSCAD - " + fileinfo.fileName() + "[*]");
+
+ // Check that the canonical file path exists - only update recent files
+ // if it does. Should prevent empty list items on initial open etc.
+ if (!infoFileName.isEmpty()) {
+ this->fileName = infoFileName;
+ QSettings settings; // already set up properly via main.cpp
+ QStringList files = settings.value("recentFileList").toStringList();
+ files.removeAll(this->fileName);
+ files.prepend(this->fileName);
+ while (files.size() > maxRecentFiles)
+ files.removeLast();
+ settings.setValue("recentFileList", files);
+ } else {
+ this->fileName = fileinfo.fileName();
+ }
+
+ QDir::setCurrent(fileinfo.dir().absolutePath());
+ }
+
+ foreach(QWidget *widget, QApplication::topLevelWidgets()) {
+ MainWindow *mainWin = qobject_cast<MainWindow *>(widget);
+ if (mainWin) {
+ mainWin->updateRecentFileActions();
+ }
+ }
+}
+
+void MainWindow::updatedFps()
+{
+ bool fps_ok;
+ double fps = e_fps->text().toDouble(&fps_ok);
+ animate_timer->stop();
+ if (fps_ok && fps > 0) {
+ animate_timer->setSingleShot(false);
+ animate_timer->setInterval(int(1000 / e_fps->text().toDouble()));
+ animate_timer->start();
+ }
+}
+
+void MainWindow::updateTVal()
+{
+ bool fps_ok;
+ double fps = e_fps->text().toDouble(&fps_ok);
+ if (fps_ok) {
+ if (fps <= 0) {
+ actionCompile();
+ } else {
+ double s = e_fsteps->text().toDouble();
+ double t = e_tval->text().toDouble() + 1/s;
+ QString txt;
+ txt.sprintf("%.5f", t >= 1.0 ? 0.0 : t);
+ e_tval->setText(txt);
+ }
+ }
+}
+
+void MainWindow::load()
+{
+ current_win = this;
+ if (!this->fileName.isEmpty()) {
+ QFile file(this->fileName);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ PRINTA("Failed to open file: %1 (%2)", this->fileName, file.errorString());
+ }
+ else {
+ QString text = QTextStream(&file).readAll();
+ PRINTA("Loaded design `%1'.", this->fileName);
+ editor->setPlainText(text);
+ }
+ }
+ current_win = this;
+}
+
+AbstractNode *MainWindow::find_root_tag(AbstractNode *n)
+{
+ foreach(AbstractNode *v, n->children) {
+ if (v->modinst->tag_root) return v;
+ if (AbstractNode *vroot = find_root_tag(v)) return vroot;
+ }
+ return NULL;
+}
+
+void MainWindow::compile(bool procevents)
+{
+ PRINT("Parsing design (AST generation)...");
+ if (procevents)
+ QApplication::processEvents();
+
+
+ // Remove previous CSG tree
+ if (root_module) {
+ delete root_module;
+ root_module = NULL;
+ }
+
+ if (absolute_root_node) {
+ delete absolute_root_node;
+ absolute_root_node = NULL;
+ }
+
+ if (root_raw_term) {
+ root_raw_term->unlink();
+ root_raw_term = NULL;
+ }
+
+ if (root_norm_term) {
+ root_norm_term->unlink();
+ root_norm_term = NULL;
+ }
+
+ if (root_chain) {
+ delete root_chain;
+ 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;
+ enableOpenCSG = false;
+
+ // Initialize special variables
+ 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);
+
+ 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)));
+ root_ctx.set_variable("$vpr", vpr);
+
+ // Parse
+ last_compiled_doc = editor->toPlainText();
+ root_module = parse((last_compiled_doc + "\n" + commandline_commands).toAscii().data(), false);
+
+ // Error highlighting
+ if (highlighter) {
+ delete highlighter;
+ highlighter = NULL;
+ }
+ if (parser_error_pos >= 0) {
+ highlighter = new Highlighter(editor->document());
+ }
+
+ if (!root_module) {
+ if (!animate_panel->isVisible()) {
+ QTextCursor cursor = editor->textCursor();
+ cursor.setPosition(parser_error_pos);
+ editor->setTextCursor(cursor);
+ }
+ goto fail;
+ }
+
+ // Evaluate CSG tree
+ PRINT("Compiling design (CSG Tree generation)...");
+ if (procevents)
+ QApplication::processEvents();
+
+ AbstractNode::idx_counter = 1;
+ root_inst = ModuleInstantiation();
+ absolute_root_node = root_module->evaluate(&root_ctx, &root_inst);
+
+ if (!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;
+ }
+ root_node->dump("");
+
+ PRINT("Compiling design (CSG Products generation)...");
+ 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
+ root_raw_term = root_node->render_csg_term(m, &highlight_terms, &background_terms);
+
+ if (!root_raw_term)
+ goto fail;
+
+ PRINT("Compiling design (CSG Products normalization)...");
+ if (procevents)
+ QApplication::processEvents();
+
+ root_norm_term = root_raw_term->link();
+
+ // CSG normalization
+ while (1) {
+ CSGTerm *n = root_norm_term->normalize();
+ root_norm_term->unlink();
+ if (root_norm_term == n)
+ break;
+ root_norm_term = n;
+ }
+
+ if (!root_norm_term)
+ goto fail;
+
+ root_chain = new CSGChain();
+ root_chain->import(root_norm_term);
+
+ if (root_chain->polysets.size() > 1000) {
+ PRINTF("WARNING: Normalized tree has %d elements!", root_chain->polysets.size());
+ PRINTF("WARNING: OpenCSG rendering has been disabled.");
+ } else {
+ enableOpenCSG = true;
+ }
+
+ if (highlight_terms.size() > 0)
+ {
+ PRINTF("Compiling highlights (%d CSG Trees)...", highlight_terms.size());
+ if (procevents)
+ QApplication::processEvents();
+
+ highlights_chain = new CSGChain();
+ for (int i = 0; i < highlight_terms.size(); i++) {
+ while (1) {
+ CSGTerm *n = highlight_terms[i]->normalize();
+ highlight_terms[i]->unlink();
+ if (highlight_terms[i] == n)
+ break;
+ highlight_terms[i] = n;
+ }
+ highlights_chain->import(highlight_terms[i]);
+ }
+ }
+
+ if (background_terms.size() > 0)
+ {
+ PRINTF("Compiling background (%d CSG Trees)...", background_terms.size());
+ if (procevents)
+ QApplication::processEvents();
+
+ background_chain = new CSGChain();
+ for (int i = 0; i < background_terms.size(); i++) {
+ while (1) {
+ CSGTerm *n = background_terms[i]->normalize();
+ background_terms[i]->unlink();
+ if (background_terms[i] == n)
+ break;
+ background_terms[i] = n;
+ }
+ background_chain->import(background_terms[i]);
+ }
+ }
+
+ if (1) {
+ PRINT("Compilation finished.");
+ if (procevents)
+ QApplication::processEvents();
+ } else {
+fail:
+ if (parser_error_pos < 0) {
+ PRINT("ERROR: Compilation failed! (no top level object found)");
+ } else {
+ int line = 1;
+ QByteArray pb = last_compiled_doc.toAscii();
+ char *p = pb.data();
+ for (int i = 0; i < parser_error_pos; i++) {
+ if (p[i] == '\n')
+ line++;
+ if (p[i] == 0) {
+ line = -1;
+ break;
+ }
+ }
+ PRINTF("ERROR: Compilation failed! (parser error in line %d)", line);
+ }
+ if (procevents)
+ QApplication::processEvents();
+ }
+}
+
+void MainWindow::actionNew()
+{
+#ifdef ENABLE_MDI
+ new MainWindow;
+#else
+ setFileName("");
+ editor->setPlainText("");
+#endif
+}
+
+void MainWindow::actionOpen()
+{
+ current_win = this;
+ QString new_filename = QFileDialog::getOpenFileName(this, "Open File", "", "OpenSCAD Designs (*.scad)");
+ if (!new_filename.isEmpty()) openFile(new_filename);
+ current_win = NULL;
+}
+
+void MainWindow::actionOpenRecent()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ if (action) {
+ openFile(action->data().toString());
+ }
+}
+
+void MainWindow::clearRecentFiles()
+{
+ QSettings settings; // already set up properly via main.cpp
+ QStringList files;
+ settings.setValue("recentFileList", files);
+
+ updateRecentFileActions();
+}
+
+void MainWindow::updateRecentFileActions()
+{
+ QSettings settings; // set up project and program properly in main.cpp
+ QStringList files = settings.value("recentFileList").toStringList();
+
+ int originalNumRecentFiles = files.size();
+
+ // Remove any duplicate or empty entries from the list
+#if (QT_VERSION >= QT_VERSION_CHECK(4, 5, 0))
+ files.removeDuplicates();
+#endif
+ files.removeAll(QString());
+ // Now remove any entries which do not exist
+ for(int i = files.size()-1; i >= 0; --i) {
+ QFileInfo fileInfo(files[i]);
+ if (!QFile(fileInfo.absoluteFilePath()).exists())
+ files.removeAt(i);
+ }
+
+ int numRecentFiles = qMin(files.size(),
+ static_cast<int>(maxRecentFiles));
+
+ for (int i = 0; i < numRecentFiles; ++i) {
+ this->actionRecentFile[i]->setText(QFileInfo(files[i]).fileName());
+ this->actionRecentFile[i]->setData(files[i]);
+ this->actionRecentFile[i]->setVisible(true);
+ }
+ for (int j = numRecentFiles; j < maxRecentFiles; ++j)
+ this->actionRecentFile[j]->setVisible(false);
+
+ // If we had to prune the list, then save the cleaned list
+ if (originalNumRecentFiles != numRecentFiles)
+ settings.setValue("recentFileList", files);
+}
+
+void MainWindow::actionOpenExample()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ if (action) {
+ openFile(this->examplesdir + QDir::separator() + action->text());
+ }
+}
+
+void MainWindow::actionSave()
+{
+ if (this->fileName.isEmpty()) {
+ actionSaveAs();
+ }
+ else {
+ current_win = this;
+ QFile file(this->fileName);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+ PRINTA("Failed to open file for writing: %1 (%2)", this->fileName, file.errorString());
+ }
+ else {
+ QTextStream(&file) << this->editor->toPlainText();
+ PRINTA("Saved design `%1'.", this->fileName);
+ this->editor->document()->setModified(false);
+ }
+ current_win = NULL;
+ }
+}
+
+void MainWindow::actionSaveAs()
+{
+ QString new_filename = QFileDialog::getSaveFileName(this, "Save File",
+ this->fileName.isEmpty()?"Untitled.scad":this->fileName,
+ "OpenSCAD Designs (*.scad)");
+ if (!new_filename.isEmpty()) {
+ if (QFileInfo(new_filename).suffix().isEmpty()) {
+ new_filename.append(".scad");
+
+ // Manual overwrite check since Qt doesn't do it, when using the
+ // defaultSuffix property
+ QFileInfo info(new_filename);
+ if (info.exists()) {
+ if (QMessageBox::warning(this, windowTitle(),
+ tr("%1 already exists.\nDo you want to replace it?").arg(info.fileName()),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ }
+ }
+ setFileName(new_filename);
+ actionSave();
+ }
+}
+
+void MainWindow::actionReload()
+{
+ load();
+}
+
+void MainWindow::editIndent()
+{
+ QTextCursor cursor = editor->textCursor();
+ int p1 = cursor.selectionStart();
+ QString txt = cursor.selectedText();
+
+ txt.replace(QString(QChar(8233)), QString(QChar(8233)) + QString("\t"));
+ if (txt.endsWith(QString(QChar(8233)) + QString("\t")))
+ txt.chop(1);
+ txt = QString("\t") + txt;
+
+ cursor.insertText(txt);
+ int p2 = cursor.position();
+ cursor.setPosition(p1, QTextCursor::MoveAnchor);
+ cursor.setPosition(p2, QTextCursor::KeepAnchor);
+ editor->setTextCursor(cursor);
+}
+
+void MainWindow::editUnindent()
+{
+ QTextCursor cursor = editor->textCursor();
+ int p1 = cursor.selectionStart();
+ QString txt = cursor.selectedText();
+
+ txt.replace(QString(QChar(8233)) + QString("\t"), QString(QChar(8233)));
+ if (txt.startsWith(QString("\t")))
+ txt.remove(0, 1);
+
+ cursor.insertText(txt);
+ int p2 = cursor.position();
+ cursor.setPosition(p1, QTextCursor::MoveAnchor);
+ cursor.setPosition(p2, QTextCursor::KeepAnchor);
+ editor->setTextCursor(cursor);
+}
+
+void MainWindow::editComment()
+{
+ QTextCursor cursor = editor->textCursor();
+ int p1 = cursor.selectionStart();
+ QString txt = cursor.selectedText();
+
+ txt.replace(QString(QChar(8233)), QString(QChar(8233)) + QString("//"));
+ if (txt.endsWith(QString(QChar(8233)) + QString("//")))
+ txt.chop(2);
+ txt = QString("//") + txt;
+
+ cursor.insertText(txt);
+ int p2 = cursor.position();
+ cursor.setPosition(p1, QTextCursor::MoveAnchor);
+ cursor.setPosition(p2, QTextCursor::KeepAnchor);
+ editor->setTextCursor(cursor);
+}
+
+void MainWindow::editUncomment()
+{
+ QTextCursor cursor = editor->textCursor();
+ int p1 = cursor.selectionStart();
+ QString txt = cursor.selectedText();
+
+ txt.replace(QString(QChar(8233)) + QString("//"), QString(QChar(8233)));
+ if (txt.startsWith(QString("//")))
+ txt.remove(0, 2);
+
+ cursor.insertText(txt);
+ int p2 = cursor.position();
+ cursor.setPosition(p1, QTextCursor::MoveAnchor);
+ cursor.setPosition(p2, QTextCursor::KeepAnchor);
+ editor->setTextCursor(cursor);
+}
+
+void MainWindow::hideEditor()
+{
+ if (editActionHide->isChecked()) {
+ editor->hide();
+ } else {
+ editor->show();
+ }
+}
+
+void MainWindow::pasteViewportTranslation()
+{
+ QTextCursor cursor = editor->textCursor();
+ QString txt;
+ txt.sprintf("[ %.2f, %.2f, %.2f ]", -screen->object_trans_x, -screen->object_trans_y, -screen->object_trans_z);
+ cursor.insertText(txt);
+}
+
+void MainWindow::pasteViewportRotation()
+{
+ QTextCursor cursor = editor->textCursor();
+ 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));
+ cursor.insertText(txt);
+}
+
+void MainWindow::actionReloadCompile()
+{
+ console->clear();
+
+ load();
+
+ current_win = this;
+ compile(true);
+
+#ifdef ENABLE_OPENCSG
+ if (!(viewActionOpenCSG->isVisible() && viewActionOpenCSG->isChecked()) &&
+ !viewActionThrownTogether->isChecked()) {
+ viewModeOpenCSG();
+ }
+ else
+#endif
+ {
+ screen->updateGL();
+ }
+ current_win = NULL;
+}
+
+void MainWindow::actionCompile()
+{
+ current_win = this;
+ console->clear();
+
+ compile(!viewActionAnimate->isChecked());
+
+ // Go to non-CGAL view mode
+ if (!viewActionOpenCSG->isChecked() && !viewActionThrownTogether->isChecked()) {
+ viewModeOpenCSG();
+ }
+ else {
+ screen->updateGL();
+ }
+
+ if (viewActionAnimate->isChecked() && e_dump->isChecked()) {
+ QImage img = screen->grabFrameBuffer();
+ QString filename;
+ double s = e_fsteps->text().toDouble();
+ double t = e_tval->text().toDouble();
+ filename.sprintf("frame%05d.png", int(round(s*t)));
+ img.save(filename, "PNG");
+ }
+
+ current_win = NULL;
+}
+
+#ifdef ENABLE_CGAL
+
+static void report_func(const class AbstractNode*, void *vp, int mark)
+{
+ QProgressDialog *pd = (QProgressDialog*)vp;
+ int v = (int)((mark*100.0) / progress_report_count);
+ pd->setValue(v < 100 ? v : 99);
+ QString label;
+ label.sprintf("Rendering Polygon Mesh using CGAL (%d/%d)", mark, progress_report_count);
+ pd->setLabelText(label);
+ QApplication::processEvents();
+}
+
+void MainWindow::actionRenderCGAL()
+{
+ current_win = this;
+ console->clear();
+
+ compile(true);
+
+ if (!root_module || !root_node)
+ return;
+
+ if (this->root_N) {
+ delete this->root_N;
+ this->root_N = NULL;
+ this->recreate_cgal_ogl_p = true;
+ }
+
+ PRINT("Rendering Polygon Mesh using CGAL...");
+ QApplication::processEvents();
+
+ QTime t;
+ t.start();
+
+ QProgressDialog *pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", QString(), 0, 100);
+ pd->setValue(0);
+ pd->setAutoClose(false);
+ pd->show();
+ QApplication::processEvents();
+
+ progress_report_prep(root_node, report_func, pd);
+ this->root_N = new CGAL_Nef_polyhedron(root_node->render_cgal_nef_polyhedron());
+ progress_report_fin();
+
+ 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) {
+ PRINTF(" Top level object is a 2D object:");
+ QApplication::processEvents();
+ PRINTF(" Empty: %6s", this->root_N->p2.is_empty() ? "yes" : "no");
+ QApplication::processEvents();
+ PRINTF(" Plane: %6s", this->root_N->p2.is_plane() ? "yes" : "no");
+ QApplication::processEvents();
+ PRINTF(" Vertices: %6d", (int)this->root_N->p2.explorer().number_of_vertices());
+ QApplication::processEvents();
+ PRINTF(" Halfedges: %6d", (int)this->root_N->p2.explorer().number_of_halfedges());
+ QApplication::processEvents();
+ PRINTF(" Edges: %6d", (int)this->root_N->p2.explorer().number_of_edges());
+ QApplication::processEvents();
+ PRINTF(" Faces: %6d", (int)this->root_N->p2.explorer().number_of_faces());
+ QApplication::processEvents();
+ PRINTF(" FaceCycles: %6d", (int)this->root_N->p2.explorer().number_of_face_cycles());
+ QApplication::processEvents();
+ PRINTF(" ConnComp: %6d", (int)this->root_N->p2.explorer().number_of_connected_components());
+ QApplication::processEvents();
+ }
+
+ if (this->root_N->dim == 3) {
+ PRINTF(" Top level object is a 3D object:");
+ PRINTF(" Simple: %6s", this->root_N->p3.is_simple() ? "yes" : "no");
+ QApplication::processEvents();
+ PRINTF(" Valid: %6s", this->root_N->p3.is_valid() ? "yes" : "no");
+ QApplication::processEvents();
+ PRINTF(" Vertices: %6d", (int)this->root_N->p3.number_of_vertices());
+ QApplication::processEvents();
+ PRINTF(" Halfedges: %6d", (int)this->root_N->p3.number_of_halfedges());
+ QApplication::processEvents();
+ PRINTF(" Edges: %6d", (int)this->root_N->p3.number_of_edges());
+ QApplication::processEvents();
+ PRINTF(" Halffacets: %6d", (int)this->root_N->p3.number_of_halffacets());
+ QApplication::processEvents();
+ PRINTF(" Facets: %6d", (int)this->root_N->p3.number_of_facets());
+ QApplication::processEvents();
+ PRINTF(" Volumes: %6d", (int)this->root_N->p3.number_of_volumes());
+ QApplication::processEvents();
+ }
+
+ int s = t.elapsed() / 1000;
+ PRINTF("Total rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
+
+ if (!viewActionCGALSurfaces->isChecked() && !viewActionCGALGrid->isChecked()) {
+ viewModeCGALSurface();
+ } else {
+ screen->updateGL();
+ }
+
+ PRINT("Rendering finished.");
+
+ delete pd;
+ current_win = NULL;
+}
+
+#endif /* ENABLE_CGAL */
+
+void MainWindow::actionDisplayAST()
+{
+ current_win = this;
+ QTextEdit *e = new QTextEdit(this);
+ e->setWindowFlags(Qt::Window);
+ e->setTabStopWidth(30);
+ e->setWindowTitle("AST Dump");
+ e->setReadOnly(true);
+ if (root_module) {
+ e->setPlainText(root_module->dump("", ""));
+ } else {
+ e->setPlainText("No AST to dump. Please try compiling first...");
+ }
+ e->show();
+ e->resize(600, 400);
+ current_win = NULL;
+}
+
+void MainWindow::actionDisplayCSGTree()
+{
+ current_win = this;
+ QTextEdit *e = new QTextEdit(this);
+ e->setWindowFlags(Qt::Window);
+ e->setTabStopWidth(30);
+ e->setWindowTitle("CSG Tree Dump");
+ e->setReadOnly(true);
+ if (root_node) {
+ e->setPlainText(root_node->dump(""));
+ } else {
+ e->setPlainText("No CSG to dump. Please try compiling first...");
+ }
+ e->show();
+ e->resize(600, 400);
+ current_win = NULL;
+}
+
+void MainWindow::actionDisplayCSGProducts()
+{
+ current_win = this;
+ QTextEdit *e = new QTextEdit(this);
+ e->setWindowFlags(Qt::Window);
+ e->setTabStopWidth(30);
+ e->setWindowTitle("CSG Products Dump");
+ e->setReadOnly(true);
+ e->setPlainText(QString("\nCSG before normalization:\n%1\n\n\nCSG after normalization:\n%2\n\n\nCSG rendering chain:\n%3\n\n\nHighlights CSG rendering chain:\n%4\n\n\nBackground CSG rendering chain:\n%5\n").arg(root_raw_term ? root_raw_term->dump() : "N/A", root_norm_term ? root_norm_term->dump() : "N/A", root_chain ? root_chain->dump() : "N/A", highlights_chain ? highlights_chain->dump() : "N/A", background_chain ? background_chain->dump() : "N/A"));
+ e->show();
+ e->resize(600, 400);
+ current_win = NULL;
+}
+
+#ifdef ENABLE_CGAL
+void MainWindow::actionExportSTLorOFF(bool stl_mode)
+#else
+void MainWindow::actionExportSTLorOFF(bool)
+#endif
+{
+#ifdef ENABLE_CGAL
+ current_win = this;
+
+ if (!this->root_N) {
+ PRINT("Nothing to export! Try building first (press F6).");
+ current_win = NULL;
+ return;
+ }
+
+ if (this->root_N->dim != 3) {
+ PRINT("Current top level object is not a 3D object.");
+ current_win = NULL;
+ return;
+ }
+
+ if (!this->root_N->p3.is_simple()) {
+ PRINT("Object isn't a valid 2-manifold! Modify your design..");
+ current_win = NULL;
+ return;
+ }
+
+ QString stl_filename = QFileDialog::getSaveFileName(this,
+ stl_mode ? "Export STL File" : "Export OFF File", "",
+ stl_mode ? "STL Files (*.stl)" : "OFF Files (*.off)");
+ if (stl_filename.isEmpty()) {
+ PRINTF("No filename specified. %s export aborted.", stl_mode ? "STL" : "OFF");
+ current_win = NULL;
+ return;
+ }
+
+ QProgressDialog *pd = new QProgressDialog(
+ stl_mode ? "Exporting object to STL file..." : "Exporting object to OFF file...",
+ QString(), 0, this->root_N->p3.number_of_facets() + 1);
+ pd->setValue(0);
+ pd->setAutoClose(false);
+ 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");
+
+ delete pd;
+
+ current_win = NULL;
+#endif /* ENABLE_CGAL */
+}
+
+void MainWindow::actionExportSTL()
+{
+ actionExportSTLorOFF(true);
+}
+
+void MainWindow::actionExportOFF()
+{
+ actionExportSTLorOFF(false);
+}
+
+void MainWindow::actionExportDXF()
+{
+#ifdef ENABLE_CGAL
+ current_win = this;
+
+ if (!this->root_N) {
+ PRINT("Nothing to export! Try building first (press F6).");
+ current_win = NULL;
+ return;
+ }
+
+ if (this->root_N->dim != 2) {
+ PRINT("Current top level object is not a 2D object.");
+ current_win = NULL;
+ return;
+ }
+
+ QString stl_filename = QFileDialog::getSaveFileName(this,
+ "Export DXF File", "", "DXF Files (*.dxf)");
+ if (stl_filename.isEmpty()) {
+ PRINTF("No filename specified. DXF export aborted.");
+ current_win = NULL;
+ return;
+ }
+
+ export_dxf(this->root_N, stl_filename, NULL);
+ PRINTF("DXF export finished.");
+
+ current_win = NULL;
+#endif /* ENABLE_CGAL */
+}
+
+void MainWindow::actionFlushCaches()
+{
+ PolySet::ps_cache.clear();
+ AbstractNode::cgal_nef_cache.clear();
+ dxf_dim_cache.clear();
+ dxf_cross_cache.clear();
+}
+
+void MainWindow::viewModeActionsUncheck()
+{
+ viewActionOpenCSG->setChecked(false);
+#ifdef ENABLE_CGAL
+ viewActionCGALSurfaces->setChecked(false);
+ viewActionCGALGrid->setChecked(false);
+#endif
+ viewActionThrownTogether->setChecked(false);
+}
+
+#ifdef ENABLE_OPENCSG
+
+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();
+ }
+};
+
+static 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);
+ }
+}
+
+static void renderGLThrownTogether(void *vp);
+
+static void renderGLviaOpenCSG(void *vp)
+{
+ MainWindow *m = (MainWindow*)vp;
+ if (!m->enableOpenCSG) {
+ renderGLThrownTogether(vp);
+ return;
+ }
+ static int glew_initialized = 0;
+ if (!glew_initialized) {
+ glew_initialized = 1;
+ glewInit();
+ }
+ if (m->root_chain) {
+ GLint *shaderinfo = m->screen->shaderinfo;
+ if (!shaderinfo[0])
+ shaderinfo = NULL;
+ renderCSGChainviaOpenCSG(m->root_chain, m->viewActionShowEdges->isChecked() ? shaderinfo : NULL, false, false);
+ if (m->background_chain) {
+ renderCSGChainviaOpenCSG(m->background_chain, m->viewActionShowEdges->isChecked() ? shaderinfo : NULL, false, true);
+ }
+ if (m->highlights_chain) {
+ renderCSGChainviaOpenCSG(m->highlights_chain, m->viewActionShowEdges->isChecked() ? shaderinfo : NULL, true, false);
+ }
+ }
+#ifdef ENABLE_MDI
+ OpenCSG::reset();
+#endif
+}
+
+/*!
+ Go to the OpenCSG view mode.
+ Falls back to thrown together mode if OpenCSG is not available
+*/
+void MainWindow::viewModeOpenCSG()
+{
+ if (screen->hasOpenCSGSupport()) {
+ viewModeActionsUncheck();
+ viewActionOpenCSG->setChecked(true);
+ screen->setRenderFunc(renderGLviaOpenCSG, this);
+ screen->updateGL();
+ } else {
+ viewModeThrownTogether();
+ }
+}
+
+#endif /* ENABLE_OPENCSG */
+
+#ifdef ENABLE_CGAL
+
+static void renderGLviaCGAL(void *vp)
+{
+ MainWindow *m = (MainWindow*)vp;
+ if (m->recreate_cgal_ogl_p) {
+ m->recreate_cgal_ogl_p = false;
+ Polyhedron *p = (Polyhedron*)m->cgal_ogl_p;
+ delete p;
+ m->cgal_ogl_p = NULL;
+ if (m->cgal_ogl_ps)
+ m->cgal_ogl_ps->unlink();
+ m->cgal_ogl_ps = NULL;
+ }
+ if (!m->root_N) return;
+ if (m->root_N->dim == 2)
+ {
+ if (m->cgal_ogl_ps == NULL) {
+ DxfData dd(*m->root_N);
+ m->cgal_ogl_ps = new PolySet();
+ m->cgal_ogl_ps->is2d = true;
+ dxf_tesselate(m->cgal_ogl_ps, &dd, 0, true, false, 0);
+ }
+
+ // Draw 2D polygons
+ glDisable(GL_LIGHTING);
+ const QColor &col = Preferences::inst()->color(Preferences::CGAL_FACE_2D_COLOR);
+ glColor3f(col.redF(), col.greenF(), col.blueF());
+
+ for (int i=0; i < m->cgal_ogl_ps->polygons.size(); i++) {
+ glBegin(GL_POLYGON);
+ for (int j=0; j < m->cgal_ogl_ps->polygons[i].size(); j++) {
+ PolySet::Point p = m->cgal_ogl_ps->polygons[i][j];
+ glVertex3d(p.x, p.y, -0.1);
+ }
+ glEnd();
+ }
+
+ typedef CGAL_Nef_polyhedron2::Explorer Explorer;
+ typedef Explorer::Face_const_iterator fci_t;
+ typedef Explorer::Halfedge_around_face_const_circulator heafcc_t;
+ typedef Explorer::Point Point;
+ Explorer E = m->root_N->p2.explorer();
+
+ // Draw 2D edges
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_LIGHTING);
+ glLineWidth(2);
+ const QColor &col2 = Preferences::inst()->color(Preferences::CGAL_EDGE_2D_COLOR);
+ glColor3f(col2.redF(), col2.greenF(), col2.blueF());
+
+ // Extract the boundary, including inner boundaries of the polygons
+ for (fci_t fit = E.faces_begin(), fend = E.faces_end(); fit != fend; ++fit)
+ {
+ bool fset = false;
+ double fx = 0.0, fy = 0.0;
+ heafcc_t fcirc(E.halfedge(fit)), fend(fcirc);
+ CGAL_For_all(fcirc, fend) {
+ if(E.is_standard(E.target(fcirc))) {
+ Point p = E.point(E.target(fcirc));
+ double x = to_double(p.x()), y = to_double(p.y());
+ if (!fset) {
+ glBegin(GL_LINE_STRIP);
+ fx = x, fy = y;
+ fset = true;
+ }
+ glVertex3d(x, y, -0.1);
+ }
+ }
+ if (fset) {
+ glVertex3d(fx, fy, -0.1);
+ glEnd();
+ }
+ }
+
+ glEnable(GL_DEPTH_TEST);
+ }
+ else if (m->root_N->dim == 3)
+ {
+ Polyhedron *p = (Polyhedron*)m->cgal_ogl_p;
+ if (!p) {
+ Nef3_Converter<CGAL_Nef_polyhedron3>::setColor(Polyhedron::CGAL_NEF3_MARKED_FACET_COLOR,
+ Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).red(),
+ Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).green(),
+ Preferences::inst()->color(Preferences::CGAL_FACE_BACK_COLOR).blue());
+ Nef3_Converter<CGAL_Nef_polyhedron3>::setColor(Polyhedron::CGAL_NEF3_UNMARKED_FACET_COLOR,
+ Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).red(),
+ Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).green(),
+ Preferences::inst()->color(Preferences::CGAL_FACE_FRONT_COLOR).blue());
+ m->cgal_ogl_p = p = new Polyhedron();
+ Nef3_Converter<CGAL_Nef_polyhedron3>::convert_to_OGLPolyhedron(m->root_N->p3, p);
+ p->init();
+ }
+ if (m->viewActionCGALSurfaces->isChecked())
+ p->set_style(SNC_BOUNDARY);
+ if (m->viewActionCGALGrid->isChecked())
+ p->set_style(SNC_SKELETON);
+#if 0
+ p->draw();
+#else
+ if (p->style == SNC_BOUNDARY) {
+ glCallList(p->object_list_+2);
+ if (m->viewActionShowEdges->isChecked()) {
+ glDisable(GL_LIGHTING);
+ glCallList(p->object_list_+1);
+ glCallList(p->object_list_);
+ }
+ } else {
+ glDisable(GL_LIGHTING);
+ glCallList(p->object_list_+1);
+ glCallList(p->object_list_);
+ }
+#endif
+ }
+}
+
+void MainWindow::viewModeCGALSurface()
+{
+ viewModeActionsUncheck();
+ viewActionCGALSurfaces->setChecked(true);
+ screen->setRenderFunc(renderGLviaCGAL, this);
+ screen->updateGL();
+}
+
+void MainWindow::viewModeCGALGrid()
+{
+ viewModeActionsUncheck();
+ viewActionCGALGrid->setChecked(true);
+ screen->setRenderFunc(renderGLviaCGAL, this);
+ screen->updateGL();
+}
+
+#endif /* ENABLE_CGAL */
+
+static void renderGLThrownTogetherChain(MainWindow *m, CSGChain *chain, bool highlight, bool background, bool fberror)
+{
+ glDepthFunc(GL_LEQUAL);
+ QHash<QPair<PolySet*,double*>,int> polySetVisitMark;
+ bool showEdges = m->viewActionShowEdges->isChecked();
+ for (int i = 0; i < chain->polysets.size(); i++) {
+ if (polySetVisitMark[QPair<PolySet*,double*>(chain->polysets[i], chain->matrices[i])]++ > 0)
+ continue;
+ double *m = chain->matrices[i];
+ glPushMatrix();
+ glMultMatrixd(m);
+ int csgmode = chain->types[i] == CSGTerm::TYPE_DIFFERENCE ? PolySet::CSGMODE_DIFFERENCE : PolySet::CSGMODE_NORMAL;
+ if (highlight) {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_HIGHLIGHT, PolySet::csgmode_e(csgmode + 20), m);
+ if (showEdges) {
+ glDisable(GL_LIGHTING);
+ chain->polysets[i]->render_edges(PolySet::COLORMODE_HIGHLIGHT, PolySet::csgmode_e(csgmode + 20));
+ glEnable(GL_LIGHTING);
+ }
+ } else if (background) {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_BACKGROUND, PolySet::csgmode_e(csgmode + 10), m);
+ if (showEdges) {
+ glDisable(GL_LIGHTING);
+ chain->polysets[i]->render_edges(PolySet::COLORMODE_BACKGROUND, PolySet::csgmode_e(csgmode + 10));
+ glEnable(GL_LIGHTING);
+ }
+ } else if (fberror) {
+ if (highlight) {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode + 20), m);
+ } else if (background) {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode + 10), m);
+ } else {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode), m);
+ }
+ } else if (m[16] >= 0 || m[17] >= 0 || m[18] >= 0 || m[19] >= 0) {
+ glColor4d(m[16], m[17], m[18], m[19]);
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode), m);
+ if (showEdges) {
+ glDisable(GL_LIGHTING);
+ glColor4d((m[16]+1)/2, (m[17]+1)/2, (m[18]+1)/2, 1.0);
+ chain->polysets[i]->render_edges(PolySet::COLORMODE_NONE, PolySet::csgmode_e(csgmode));
+ glEnable(GL_LIGHTING);
+ }
+ } else if (chain->types[i] == CSGTerm::TYPE_DIFFERENCE) {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_CUTOUT, PolySet::csgmode_e(csgmode), m);
+ if (showEdges) {
+ glDisable(GL_LIGHTING);
+ chain->polysets[i]->render_edges(PolySet::COLORMODE_CUTOUT, PolySet::csgmode_e(csgmode));
+ glEnable(GL_LIGHTING);
+ }
+ } else {
+ chain->polysets[i]->render_surface(PolySet::COLORMODE_MATERIAL, PolySet::csgmode_e(csgmode), m);
+ if (showEdges) {
+ glDisable(GL_LIGHTING);
+ chain->polysets[i]->render_edges(PolySet::COLORMODE_MATERIAL, PolySet::csgmode_e(csgmode));
+ glEnable(GL_LIGHTING);
+ }
+ }
+ glPopMatrix();
+ }
+}
+
+static void renderGLThrownTogether(void *vp)
+{
+ MainWindow *m = (MainWindow*)vp;
+ if (m->root_chain) {
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ renderGLThrownTogetherChain(m, m->root_chain, false, false, false);
+ glCullFace(GL_FRONT);
+ glColor3ub(255, 0, 255);
+ renderGLThrownTogetherChain(m, m->root_chain, false, false, true);
+ glDisable(GL_CULL_FACE);
+ }
+ if (m->background_chain)
+ renderGLThrownTogetherChain(m, m->background_chain, false, true, false);
+ if (m->highlights_chain)
+ renderGLThrownTogetherChain(m, m->highlights_chain, true, false, false);
+}
+
+void MainWindow::viewModeThrownTogether()
+{
+ viewModeActionsUncheck();
+ viewActionThrownTogether->setChecked(true);
+ screen->setRenderFunc(renderGLThrownTogether, this);
+ screen->updateGL();
+}
+
+void MainWindow::viewModeShowEdges()
+{
+ screen->updateGL();
+}
+
+void MainWindow::viewModeShowAxes()
+{
+ screen->setShowAxes(viewActionShowAxes->isChecked());
+ screen->updateGL();
+}
+
+void MainWindow::viewModeShowCrosshairs()
+{
+ screen->setShowCrosshairs(viewActionShowCrosshairs->isChecked());
+ screen->updateGL();
+}
+
+void MainWindow::viewModeAnimate()
+{
+ if (viewActionAnimate->isChecked()) {
+ animate_panel->show();
+ actionCompile();
+ updatedFps();
+ } else {
+ animate_panel->hide();
+ animate_timer->stop();
+ }
+}
+
+void MainWindow::animateUpdateDocChanged()
+{
+ QString current_doc = editor->toPlainText();
+ if (current_doc != last_compiled_doc)
+ animateUpdate();
+}
+
+void MainWindow::animateUpdate()
+{
+ if (animate_panel->isVisible()) {
+ bool fps_ok;
+ double fps = e_fps->text().toDouble(&fps_ok);
+ if (fps_ok && fps <= 0 && !animate_timer->isActive()) {
+ animate_timer->stop();
+ animate_timer->setSingleShot(true);
+ animate_timer->setInterval(50);
+ animate_timer->start();
+ }
+ }
+}
+
+void MainWindow::viewAngleTop()
+{
+ screen->object_rot_x = 90;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 0;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleBottom()
+{
+ screen->object_rot_x = 270;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 0;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleLeft()
+{
+ screen->object_rot_x = 0;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 90;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleRight()
+{
+ screen->object_rot_x = 0;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 270;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleFront()
+{
+ screen->object_rot_x = 0;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 0;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleBack()
+{
+ screen->object_rot_x = 0;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 180;
+ screen->updateGL();
+}
+
+void MainWindow::viewAngleDiagonal()
+{
+ screen->object_rot_x = 35;
+ screen->object_rot_y = 0;
+ screen->object_rot_z = 25;
+ screen->updateGL();
+}
+
+void MainWindow::viewCenter()
+{
+ screen->object_trans_x = 0;
+ screen->object_trans_y = 0;
+ screen->object_trans_z = 0;
+ screen->updateGL();
+}
+
+void MainWindow::viewPerspective()
+{
+ viewActionPerspective->setChecked(true);
+ viewActionOrthogonal->setChecked(false);
+ screen->setOrthoMode(false);
+ screen->updateGL();
+}
+
+void MainWindow::viewOrthogonal()
+{
+ viewActionPerspective->setChecked(false);
+ viewActionOrthogonal->setChecked(true);
+ screen->setOrthoMode(true);
+ screen->updateGL();
+}
+
+void MainWindow::hideConsole()
+{
+ if (viewActionHide->isChecked()) {
+ console->hide();
+ } else {
+ console->show();
+ }
+}
+
+void MainWindow::dragEnterEvent(QDragEnterEvent *event)
+{
+ if (event->mimeData()->hasUrls())
+ event->acceptProposedAction();
+}
+
+void MainWindow::dropEvent(QDropEvent *event)
+{
+ current_win = this;
+ const QList<QUrl> urls = event->mimeData()->urls();
+ for (int i = 0; i < urls.size(); i++) {
+ if (urls[i].scheme() != "file")
+ continue;
+ openFile(urls[i].path());
+ }
+ current_win = NULL;
+}
+
+void
+MainWindow::helpAbout()
+{
+ qApp->setWindowIcon(QApplication::windowIcon());
+ QMessageBox::information(this, "About OpenSCAD", QString(helptitle) + QString(copyrighttext));
+}
+
+void
+MainWindow::helpManual()
+{
+ QDesktopServices::openUrl(QUrl("http://en.wikibooks.org/wiki/OpenSCAD_User_Manual"));
+}
+
+/*!
+ FIXME: In SDI mode, this should be called also on New and Open
+ In MDI mode; also call on both reload functions?
+ */
+bool
+MainWindow::maybeSave()
+{
+ if (editor->document()->isModified()) {
+ QMessageBox::StandardButton ret;
+ ret = QMessageBox::warning(this, "Application",
+ "The document has been modified.\n"
+ "Do you want to save your changes?",
+ QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
+ if (ret == QMessageBox::Save) {
+ actionSave();
+ return true; // FIXME: Should return false on error
+ }
+ else if (ret == QMessageBox::Cancel) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+ if (maybeSave()) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void
+MainWindow::preferences()
+{
+ Preferences::inst()->show();
+ Preferences::inst()->activateWindow();
+ Preferences::inst()->raise();
+}
+
+void MainWindow::setFont(const QString &family, uint size)
+{
+ QFont font(editor->font());
+ if (!family.isEmpty()) font.setFamily(family);
+ if (size > 0) font.setPointSize(size);
+ font.setStyleHint(QFont::TypeWriter);
+ editor->setFont(font);
+}
+
+void MainWindow::quit()
+{
+ QCloseEvent ev;
+ QApplication::sendEvent(QApplication::instance(), &ev);
+ if (ev.isAccepted()) QApplication::instance()->quit();
+}
diff --git a/src/module.cc b/src/module.cc
new file mode 100644
index 0000000..a319e1e
--- /dev/null
+++ b/src/module.cc
@@ -0,0 +1,374 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+AbstractModule::~AbstractModule()
+{
+}
+
+AbstractNode *AbstractModule::evaluate(const Context*, const ModuleInstantiation *inst) const
+{
+ AbstractNode *node = new AbstractNode(inst);
+
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n)
+ node->children.append(n);
+ }
+
+ return node;
+}
+
+QString AbstractModule::dump(QString indent, QString name) const
+{
+ return QString("%1abstract module %2();\n").arg(indent, name);
+}
+
+ModuleInstantiation::~ModuleInstantiation()
+{
+ foreach (Expression *v, argexpr)
+ delete v;
+ foreach (ModuleInstantiation *v, children)
+ delete v;
+}
+
+QString ModuleInstantiation::dump(QString indent) const
+{
+ QString text = indent;
+ if (!label.isEmpty())
+ text += label + QString(": ");
+ text += modname + QString("(");
+ for (int i=0; i < argnames.size(); i++) {
+ if (i > 0)
+ text += QString(", ");
+ if (!argnames[i].isEmpty())
+ text += argnames[i] + QString(" = ");
+ text += argexpr[i]->dump();
+ }
+ if (children.size() == 0) {
+ text += QString(");\n");
+ } else if (children.size() == 1) {
+ text += QString(")\n");
+ text += children[0]->dump(indent + QString("\t"));
+ } else {
+ text += QString(") {\n");
+ for (int i = 0; i < children.size(); i++) {
+ text += children[i]->dump(indent + QString("\t"));
+ }
+ text += QString("%1}\n").arg(indent);
+ }
+ return text;
+}
+
+AbstractNode *ModuleInstantiation::evaluate(const Context *ctx) const
+{
+ AbstractNode *node = NULL;
+ if (this->ctx) {
+ PRINTA("WARNING: Ignoring recursive module instanciation of '%1'.", modname);
+ } else {
+ ModuleInstantiation *that = (ModuleInstantiation*)this;
+ that->argvalues.clear();
+ foreach (Expression *v, that->argexpr) {
+ that->argvalues.append(v->evaluate(ctx));
+ }
+ that->ctx = ctx;
+ node = ctx->evaluate_module(this);
+ that->ctx = NULL;
+ that->argvalues.clear();
+ }
+ return node;
+}
+
+Module::~Module()
+{
+ foreach (Expression *v, assignments_expr)
+ delete v;
+ foreach (AbstractFunction *v, functions)
+ delete v;
+ foreach (AbstractModule *v, modules)
+ delete v;
+ foreach (ModuleInstantiation *v, children)
+ delete v;
+}
+
+AbstractNode *Module::evaluate(const Context *ctx, const ModuleInstantiation *inst) const
+{
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ c.inst_p = inst;
+ c.set_variable("$children", Value(double(inst->children.size())));
+
+ c.functions_p = &functions;
+ c.modules_p = &modules;
+
+ for (int i = 0; i < assignments_var.size(); i++) {
+ c.set_variable(assignments_var[i], assignments_expr[i]->evaluate(&c));
+ }
+
+ AbstractNode *node = new AbstractNode(inst);
+ for (int i = 0; i < children.size(); i++) {
+ AbstractNode *n = children[i]->evaluate(&c);
+ if (n != NULL)
+ node->children.append(n);
+ }
+
+ return node;
+}
+
+QString Module::dump(QString indent, QString name) const
+{
+ QString text, tab;
+ if (!name.isEmpty()) {
+ text = QString("%1module %2(").arg(indent, name);
+ for (int i=0; i < argnames.size(); i++) {
+ if (i > 0)
+ text += QString(", ");
+ text += argnames[i];
+ if (argexpr[i])
+ text += QString(" = ") + argexpr[i]->dump();
+ }
+ text += QString(") {\n");
+ tab = "\t";
+ }
+ {
+ QHashIterator<QString, AbstractFunction*> i(functions);
+ while (i.hasNext()) {
+ i.next();
+ text += i.value()->dump(indent + tab, i.key());
+ }
+ }
+ {
+ QHashIterator<QString, AbstractModule*> i(modules);
+ while (i.hasNext()) {
+ i.next();
+ text += i.value()->dump(indent + tab, i.key());
+ }
+ }
+ 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());
+ }
+ for (int i = 0; i < children.size(); i++) {
+ text += children[i]->dump(indent + tab);
+ }
+ if (!name.isEmpty()) {
+ text += QString("%1}\n").arg(indent);
+ }
+ return text;
+}
+
+QHash<QString, AbstractModule*> builtin_modules;
+
+void initialize_builtin_modules()
+{
+ builtin_modules["group"] = new AbstractModule();
+
+ register_builtin_csgops();
+ register_builtin_transform();
+ register_builtin_primitives();
+ register_builtin_surface();
+ register_builtin_control();
+ register_builtin_render();
+ register_builtin_import();
+ register_builtin_dxf_linear_extrude();
+ register_builtin_dxf_rotate_extrude();
+}
+
+void destroy_builtin_modules()
+{
+ foreach (AbstractModule *v, builtin_modules)
+ delete v;
+ builtin_modules.clear();
+}
+
+int AbstractNode::idx_counter;
+
+AbstractNode::AbstractNode(const ModuleInstantiation *mi)
+{
+ modinst = mi;
+ idx = idx_counter++;
+}
+
+AbstractNode::~AbstractNode()
+{
+ foreach (AbstractNode *v, children)
+ 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(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;
+ }
+ }
+
+ 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
+{
+ return render_cgal_nef_polyhedron_backend(this, false);
+}
+
+CGAL_Nef_polyhedron AbstractIntersectionNode::render_cgal_nef_polyhedron() const
+{
+ return render_cgal_nef_polyhedron_backend(this, true);
+}
+
+#endif /* ENABLE_CGAL */
+
+static CSGTerm *render_csg_term_backend(const AbstractNode *that, bool intersect, double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background)
+{
+ 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;
+}
+
+CSGTerm *AbstractNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const
+{
+ return render_csg_term_backend(this, false, m, highlights, background);
+}
+
+CSGTerm *AbstractIntersectionNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) const
+{
+ return render_csg_term_backend(this, true, m, highlights, background);
+}
+
+QString AbstractNode::dump(QString indent) 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;
+}
+
+QString AbstractIntersectionNode::dump(QString indent) 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;
+}
+
+int progress_report_count;
+void (*progress_report_f)(const class AbstractNode*, void*, int);
+void *progress_report_vp;
+
+void AbstractNode::progress_prepare()
+{
+ foreach (AbstractNode *v, children)
+ v->progress_prepare();
+ progress_mark = ++progress_report_count;
+}
+
+void AbstractNode::progress_report() const
+{
+ if (progress_report_f)
+ progress_report_f(this, progress_report_vp, progress_mark);
+}
+
+void progress_report_prep(AbstractNode *root, void (*f)(const class AbstractNode *node, void *vp, int mark), void *vp)
+{
+ progress_report_count = 0;
+ progress_report_f = f;
+ progress_report_vp = vp;
+ root->progress_prepare();
+}
+
+void progress_report_fin()
+{
+ progress_report_count = 0;
+ progress_report_f = NULL;
+ progress_report_vp = NULL;
+}
+
diff --git a/src/nef2dxf.cc b/src/nef2dxf.cc
new file mode 100644
index 0000000..9c69e84
--- /dev/null
+++ b/src/nef2dxf.cc
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+
+DxfData::DxfData(const struct CGAL_Nef_polyhedron &N)
+{
+ Grid2d<int> grid(GRID_COARSE);
+
+ typedef CGAL_Nef_polyhedron2::Explorer Explorer;
+ typedef Explorer::Face_const_iterator fci_t;
+ typedef Explorer::Halfedge_around_face_const_circulator heafcc_t;
+ Explorer E = N.p2.explorer();
+
+ for (fci_t fit = E.faces_begin(), fend = E.faces_end(); fit != fend; ++fit)
+ {
+ heafcc_t fcirc(E.halfedge(fit)), fend(fcirc);
+ int first_point = -1, last_point = -1;
+ CGAL_For_all(fcirc, fend) {
+ if (E.is_standard(E.target(fcirc))) {
+ Explorer::Point ep = E.point(E.target(fcirc));
+ double x = to_double(ep.x()), y = to_double(ep.y());
+ int this_point = -1;
+ if (grid.has(x, y)) {
+ this_point = grid.align(x, y);
+ } else {
+ this_point = grid.align(x, y) = points.size();
+ points.append(Point(x, y));
+ }
+ if (first_point < 0) {
+ paths.append(Path());
+ first_point = this_point;
+ }
+ if (this_point != last_point) {
+ paths.last().points.append(&points[this_point]);
+ last_point = this_point;
+ }
+ }
+ }
+ if (first_point >= 0) {
+ paths.last().is_closed = 1;
+ paths.last().points.append(&points[first_point]);
+ }
+ }
+
+ fixup_path_direction();
+}
+
diff --git a/src/openscad.cc b/src/openscad.cc
new file mode 100644
index 0000000..8c521b6
--- /dev/null
+++ b/src/openscad.cc
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "MainWindow.h"
+
+#include <QApplication>
+#include <QFile>
+#include <QDir>
+#include <QSet>
+#include <QSettings>
+#include <getopt.h>
+#ifdef Q_WS_MAC
+#include "EventFilter.h"
+#endif
+
+static void help(const char *progname)
+{
+ fprintf(stderr, "Usage: %s [ { -s stl_file | -o off_file | -x dxf_file } [ -d deps_file ] ]\\\n"
+ "%*s[ -m make_command ] [ -D var=val [..] ] filename\n",
+ progname, int(strlen(progname))+8, "");
+ exit(1);
+}
+
+QString commandline_commands;
+const char *make_command = NULL;
+QSet<QString> dependencies;
+
+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);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int rc = 0;
+
+ initialize_builtin_functions();
+ initialize_builtin_modules();
+
+#ifdef Q_WS_X11
+ // see <http://qt.nokia.com/doc/4.5/qapplication.html#QApplication-2>:
+ // On X11, the window system is initialized if GUIenabled is true. If GUIenabled
+ // is false, the application does not connect to the X server. On Windows and
+ // Macintosh, currently the window system is always initialized, regardless of the
+ // value of GUIenabled. This may change in future versions of Qt.
+ bool useGUI = getenv("DISPLAY") != 0;
+#else
+ bool useGUI = true;
+#endif
+ QApplication app(argc, argv, useGUI);
+#ifdef Q_WS_MAC
+ app.setLibraryPaths(QStringList(app.applicationDirPath() + "/../PlugIns"));
+ app.installEventFilter(new EventFilter(&app));
+#endif
+
+ // set up groups for QSettings
+ QCoreApplication::setOrganizationName("OpenSCAD");
+ QCoreApplication::setOrganizationDomain("openscad.org");
+ QCoreApplication::setApplicationName("OpenSCAD");
+
+
+ const char *filename = NULL;
+ const char *stl_output_file = NULL;
+ const char *off_output_file = NULL;
+ const char *dxf_output_file = NULL;
+ const char *deps_output_file = NULL;
+
+ int opt;
+
+ while ((opt = getopt(argc, argv, "s:o:x:d:m:D:")) != -1)
+ {
+ switch (opt)
+ {
+ case 's':
+ if (stl_output_file || off_output_file || dxf_output_file)
+ help(argv[0]);
+ stl_output_file = optarg;
+ break;
+ case 'o':
+ if (stl_output_file || off_output_file || dxf_output_file)
+ help(argv[0]);
+ off_output_file = optarg;
+ break;
+ case 'x':
+ if (stl_output_file || off_output_file || dxf_output_file)
+ help(argv[0]);
+ dxf_output_file = optarg;
+ break;
+ case 'd':
+ if (deps_output_file)
+ help(argv[0]);
+ deps_output_file = optarg;
+ break;
+ case 'm':
+ if (make_command)
+ help(argv[0]);
+ make_command = optarg;
+ break;
+ case 'D':
+ commandline_commands += QString(optarg) + QString(";\n");
+ break;
+ default:
+ help(argv[0]);
+ }
+ }
+
+ if (optind < argc)
+ filename = argv[optind++];
+
+#ifndef ENABLE_MDI
+ if (optind != argc)
+ help(argv[0]);
+#endif
+
+ if (stl_output_file || off_output_file || dxf_output_file)
+ {
+ if (!filename)
+ help(argv[0]);
+
+#ifdef ENABLE_CGAL
+ 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;
+
+ 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(), false);
+ }
+
+ QString original_path = QDir::currentPath();
+ QFileInfo fileInfo(filename);
+ QDir::setCurrent(fileInfo.dir().absolutePath());
+
+ AbstractNode::idx_counter = 1;
+ 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());
+
+ QDir::setCurrent(original_path);
+
+ if (deps_output_file) {
+ fp = fopen(deps_output_file, "wt");
+ if (!fp) {
+ fprintf(stderr, "Can't open dependencies file `%s' for writing!\n", deps_output_file);
+ exit(1);
+ }
+ fprintf(fp, "%s:", stl_output_file ? stl_output_file : off_output_file);
+ QSetIterator<QString> i(dependencies);
+ while (i.hasNext())
+ fprintf(fp, " \\\n\t%s", i.next().toUtf8().data());
+ fprintf(fp, "\n");
+ fclose(fp);
+ }
+
+ if (stl_output_file)
+ export_stl(root_N, stl_output_file, NULL);
+
+ if (off_output_file)
+ export_off(root_N, off_output_file, NULL);
+
+ if (dxf_output_file)
+ export_dxf(root_N, dxf_output_file, NULL);
+
+ delete root_node;
+ delete root_N;
+#else
+ fprintf(stderr, "OpenSCAD has been compiled without CGAL support!\n");
+ exit(1);
+#endif
+ }
+ else if (useGUI)
+ {
+ // turn on anti-aliasing
+ QGLFormat f;
+ f.setSampleBuffers(true);
+ f.setSamples(4);
+ QGLFormat::setDefaultFormat(f);
+#ifdef ENABLE_MDI
+ new MainWindow(filename);
+ while (optind < argc)
+ new MainWindow(argv[optind++]);
+ app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
+#else
+ MainWindow *m = new MainWindow(filename);
+ app.connect(m, SIGNAL(destroyed()), &app, SLOT(quit()));
+#endif
+ rc = app.exec();
+ }
+ else
+ {
+ fprintf(stderr, "Requested GUI mode but can't open display!\n");
+ exit(1);
+ }
+
+ destroy_builtin_functions();
+ destroy_builtin_modules();
+
+ return rc;
+}
+
diff --git a/src/openscad.h b/src/openscad.h
new file mode 100644
index 0000000..03ea1cb
--- /dev/null
+++ b/src/openscad.h
@@ -0,0 +1,738 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#ifndef OPENSCAD_H
+#define OPENSCAD_H
+
+#ifdef ENABLE_OPENCSG
+// this must be included before the GL headers
+# include <GL/glew.h>
+#endif
+
+#include <qgl.h>
+
+#include <QHash>
+#include <QCache>
+#include <QVector>
+#include <QProgressDialog>
+#include <QSyntaxHighlighter>
+#include <QPointer>
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include <fstream>
+#include <iostream>
+
+// for win32 and maybe others..
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+class Value;
+class Expression;
+
+class AbstractFunction;
+class BuiltinFunction;
+class Function;
+
+class AbstractModule;
+class ModuleInstantiation;
+class Module;
+
+class Context;
+class PolySet;
+class CSGTerm;
+class CSGChain;
+class AbstractNode;
+class AbstractIntersectionNode;
+class AbstractPolyNode;
+struct CGAL_Nef_polyhedron;
+
+const double GRID_COARSE = 0.001;
+const double GRID_FINE = 0.000001;
+
+template <typename T>
+class Grid2d
+{
+public:
+ double res;
+ QHash<QPair<int64_t,int64_t>, T> db;
+
+ Grid2d(double resolution) {
+ res = resolution;
+ }
+ /*!
+ Aligns x,y to the grid or to existing point if one close enough exists.
+ Returns the value stored if a point already existing or an uninitialized new value
+ if not.
+ */
+ T &align(double &x, double &y) {
+ int64_t ix = (int64_t)round(x / res);
+ int64_t iy = (int64_t)round(y / res);
+ if (!db.contains(QPair<int64_t,int64_t>(ix, iy))) {
+ int dist = 10;
+ for (int64_t jx = ix - 1; jx <= ix + 1; jx++) {
+ for (int64_t jy = iy - 1; jy <= iy + 1; jy++) {
+ if (!db.contains(QPair<int64_t,int64_t>(jx, jy)))
+ continue;
+ if (abs(ix-jx) + abs(iy-jy) < dist) {
+ dist = abs(ix-jx) + abs(iy-jy);
+ ix = jx;
+ iy = jy;
+ }
+ }
+ }
+ }
+ x = ix * res, y = iy * res;
+ return db[QPair<int64_t,int64_t>(ix, iy)];
+ }
+ bool has(double x, double y) const {
+ int64_t ix = (int64_t)round(x / res);
+ int64_t iy = (int64_t)round(y / res);
+ if (db.contains(QPair<int64_t,int64_t>(ix, iy)))
+ return true;
+ for (int64_t jx = ix - 1; jx <= ix + 1; jx++)
+ for (int64_t jy = iy - 1; jy <= iy + 1; jy++) {
+ if (db.contains(QPair<int64_t,int64_t>(jx, jy)))
+ return true;
+ }
+ return false;
+ }
+ bool eq(double x1, double y1, double x2, double y2) {
+ align(x1, y1);
+ align(x2, y2);
+ if (fabs(x1 - x2) < res && fabs(y1 - y2) < res)
+ return true;
+ return false;
+ }
+ T &data(double x, double y) {
+ return align(x, y);
+ }
+ T &operator()(double x, double y) {
+ return align(x, y);
+ }
+};
+
+template <typename T>
+class Grid3d
+{
+public:
+ double res;
+ QHash<QPair<QPair<int64_t,int64_t>,int64_t>, T> db;
+
+ Grid3d(double resolution) {
+ res = resolution;
+ }
+ T &align(double &x, double &y, double &z) {
+ int64_t ix = (int64_t)round(x / res);
+ int64_t iy = (int64_t)round(y / res);
+ int64_t iz = (int64_t)round(z / res);
+ if (!db.contains(QPair<QPair<int64_t,int64_t>,int64_t>(QPair<int64_t,int64_t>(ix, iy), iz))) {
+ int dist = 10;
+ for (int64_t jx = ix - 1; jx <= ix + 1; jx++) {
+ for (int64_t jy = iy - 1; jy <= iy + 1; jy++) {
+ for (int64_t jz = iz - 1; jz <= iz + 1; jz++) {
+ if (!db.contains(QPair<QPair<int64_t,int64_t>,int64_t>(QPair<int64_t,int64_t>(jx, jy), jz)))
+ continue;
+ if (abs(ix-jx) + abs(iy-jy) + abs(iz-jz) < dist) {
+ dist = abs(ix-jx) + abs(iy-jy) + abs(iz-jz);
+ ix = jx;
+ iy = jy;
+ iz = jz;
+ }
+ }
+ }
+ }
+ }
+ x = ix * res, y = iy * res, z = iz * res;
+ return db[QPair<QPair<int64_t,int64_t>,int64_t>(QPair<int64_t,int64_t>(ix, iy), iz)];
+ }
+ bool has(double x, double y, double z) {
+ int64_t ix = (int64_t)round(x / res);
+ int64_t iy = (int64_t)round(y / res);
+ int64_t iz = (int64_t)round(z / res);
+ if (db.contains(QPair<QPair<int64_t,int64_t>,int64_t>(QPair<int64_t,int64_t>(ix, iy), iz)))
+ return true;
+ for (int64_t jx = ix - 1; jx <= ix + 1; jx++)
+ for (int64_t jy = iy - 1; jy <= iy + 1; jy++)
+ for (int64_t jz = iz - 1; jz <= iz + 1; jz++) {
+ if (db.contains(QPair<QPair<int64_t,int64_t>,int64_t>(QPair<int64_t,int64_t>(jx, jy), jz)))
+ return true;
+ }
+ return false;
+
+ }
+ bool eq(double x1, double y1, double z1, double x2, double y2, double z2) {
+ align(x1, y1, z1);
+ align(x2, y2, z2);
+ if (fabs(x1 - x2) < res && fabs(y1 - y2) < res && fabs(z1 - z2) < res)
+ return true;
+ return false;
+ }
+ T &data(double x, double y, double z) {
+ return align(x, y, z);
+ }
+ T &operator()(double x, double y, double z) {
+ return align(x, y, z);
+ }
+};
+
+class Value
+{
+public:
+ enum type_e {
+ UNDEFINED,
+ BOOL,
+ NUMBER,
+ RANGE,
+ VECTOR,
+ STRING
+ };
+
+ enum type_e type;
+
+ bool b;
+ double num;
+ QVector<Value*> vec;
+ double range_begin;
+ double range_step;
+ double range_end;
+ QString text;
+
+ Value();
+ ~Value();
+
+ Value(bool v);
+ Value(double v);
+ Value(const QString &t);
+
+ Value(const Value &v);
+ Value& operator = (const Value &v);
+
+ Value operator ! () const;
+ Value operator && (const Value &v) const;
+ Value operator || (const Value &v) const;
+
+ Value operator + (const Value &v) const;
+ Value operator - (const Value &v) const;
+ Value operator * (const Value &v) const;
+ Value operator / (const Value &v) const;
+ Value operator % (const Value &v) const;
+
+ Value operator < (const Value &v) const;
+ Value operator <= (const Value &v) const;
+ Value operator == (const Value &v) const;
+ Value operator != (const Value &v) const;
+ Value operator >= (const Value &v) const;
+ Value operator > (const Value &v) const;
+
+ Value inv() const;
+
+ bool getnum(double &v) const;
+ bool getv2(double &x, double &y) const;
+ bool getv3(double &x, double &y, double &z) const;
+
+ QString dump() const;
+
+private:
+ void reset_undef();
+};
+
+class Expression
+{
+public:
+ QVector<Expression*> children;
+
+ Value *const_value;
+ QString var_name;
+
+ QString call_funcname;
+ QVector<QString> call_argnames;
+
+ // Boolean: ! && ||
+ // Operators: * / % + -
+ // Relations: < <= == != >= >
+ // Vector element: []
+ // Condition operator: ?:
+ // Invert (prefix '-'): I
+ // Constant value: C
+ // Create Range: R
+ // Create Vector: V
+ // Create Matrix: M
+ // Lookup Variable: L
+ // Lookup member per name: N
+ // Function call: F
+ QString type;
+
+ Expression();
+ ~Expression();
+
+ Value evaluate(const Context *context) const;
+ QString dump() const;
+};
+
+class AbstractFunction
+{
+public:
+ virtual ~AbstractFunction();
+ virtual Value evaluate(const Context *ctx, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues) const;
+ virtual QString dump(QString indent, QString name) const;
+};
+
+class BuiltinFunction : public AbstractFunction
+{
+public:
+ typedef Value (*eval_func_t)(const QVector<QString> &argnames, const QVector<Value> &args);
+ eval_func_t eval_func;
+
+ BuiltinFunction(eval_func_t f) : eval_func(f) { }
+ virtual ~BuiltinFunction();
+
+ virtual Value evaluate(const Context *ctx, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues) const;
+ virtual QString dump(QString indent, QString name) const;
+};
+
+class Function : public AbstractFunction
+{
+public:
+ QVector<QString> argnames;
+ QVector<Expression*> argexpr;
+
+ Expression *expr;
+
+ Function() { }
+ virtual ~Function();
+
+ virtual Value evaluate(const Context *ctx, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues) const;
+ virtual QString dump(QString indent, QString name) const;
+};
+
+extern QHash<QString, AbstractFunction*> builtin_functions;
+extern void initialize_builtin_functions();
+extern void initialize_builtin_dxf_dim();
+extern void destroy_builtin_functions();
+
+class AbstractModule
+{
+public:
+ virtual ~AbstractModule();
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+ virtual QString dump(QString indent, QString name) const;
+};
+
+class ModuleInstantiation
+{
+public:
+ QString label;
+ QString modname;
+ QVector<QString> argnames;
+ QVector<Expression*> argexpr;
+ QVector<Value> argvalues;
+ QVector<ModuleInstantiation*> children;
+
+ bool tag_root;
+ bool tag_highlight;
+ bool tag_background;
+ const Context *ctx;
+
+ ModuleInstantiation() : tag_root(false), tag_highlight(false), tag_background(false), ctx(NULL) { }
+ ~ModuleInstantiation();
+
+ QString dump(QString indent) const;
+ AbstractNode *evaluate(const Context *ctx) const;
+};
+
+class Module : public AbstractModule
+{
+public:
+ QVector<QString> argnames;
+ QVector<Expression*> argexpr;
+
+ QVector<QString> assignments_var;
+ QVector<Expression*> assignments_expr;
+
+ QHash<QString, AbstractFunction*> functions;
+ QHash<QString, AbstractModule*> modules;
+
+ QVector<ModuleInstantiation*> children;
+
+ Module() { }
+ virtual ~Module();
+
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+ virtual QString dump(QString indent, QString name) const;
+};
+
+extern QHash<QString, AbstractModule*> builtin_modules;
+extern void initialize_builtin_modules();
+extern void destroy_builtin_modules();
+
+extern void register_builtin_csgops();
+extern void register_builtin_transform();
+extern void register_builtin_primitives();
+extern void register_builtin_surface();
+extern void register_builtin_control();
+extern void register_builtin_render();
+extern void register_builtin_import();
+extern void register_builtin_dxf_linear_extrude();
+extern void register_builtin_dxf_rotate_extrude();
+
+class Context
+{
+public:
+ const Context *parent;
+ QHash<QString, Value> variables;
+ QHash<QString, Value> config_variables;
+ const QHash<QString, AbstractFunction*> *functions_p;
+ const QHash<QString, AbstractModule*> *modules_p;
+ const ModuleInstantiation *inst_p;
+
+ static QVector<const Context*> ctx_stack;
+
+ Context(const Context *parent = NULL);
+ ~Context();
+
+ void args(const QVector<QString> &argnames, const QVector<Expression*> &argexpr, const QVector<QString> &call_argnames, const QVector<Value> &call_argvalues);
+
+ void set_variable(QString name, Value value);
+ Value lookup_variable(QString name, bool silent = false) const;
+
+ Value evaluate_function(QString name, const QVector<QString> &argnames, const QVector<Value> &argvalues) const;
+ AbstractNode *evaluate_module(const ModuleInstantiation *inst) const;
+};
+
+class DxfData
+{
+public:
+ struct Point {
+ double x, y;
+ Point() : x(0), y(0) { }
+ Point(double x, double y) : x(x), y(y) { }
+ };
+ struct Path {
+ QList<Point*> points;
+ bool is_closed, is_inner;
+ Path() : is_closed(false), is_inner(false) { }
+ };
+ struct Dim {
+ unsigned int type;
+ double coords[7][2];
+ double angle;
+ double length;
+ QString name;
+ Dim() {
+ for (int i = 0; i < 7; i++)
+ for (int j = 0; j < 2; j++)
+ coords[i][j] = 0;
+ type = 0;
+ angle = 0;
+ length = 0;
+ }
+ };
+
+ QList<Point> points;
+ QList<Path> paths;
+ QList<Dim> dims;
+
+ DxfData();
+ DxfData(double fn, double fs, double fa, QString filename, QString layername = QString(), double xorigin = 0.0, double yorigin = 0.0, double scale = 1.0);
+ DxfData(const struct CGAL_Nef_polyhedron &N);
+
+ Point *addPoint(double x, double y);
+
+private:
+ void fixup_path_direction();
+};
+
+// The CGAL template magic slows down the compilation process by a factor of 5.
+// So we only include the declaration of AbstractNode where it is needed...
+#ifdef INCLUDE_ABSTRACT_NODE_DETAILS
+
+#ifdef ENABLE_CGAL
+
+#include <CGAL/Gmpq.h>
+#include <CGAL/Extended_cartesian.h>
+#include <CGAL/Nef_polyhedron_2.h>
+#include <CGAL/Cartesian.h>
+#include <CGAL/Polyhedron_3.h>
+#include <CGAL/Nef_polyhedron_3.h>
+#include <CGAL/IO/Polyhedron_iostream.h>
+
+typedef CGAL::Extended_cartesian<CGAL::Gmpq> CGAL_Kernel2;
+typedef CGAL::Nef_polyhedron_2<CGAL_Kernel2> CGAL_Nef_polyhedron2;
+typedef CGAL_Kernel2::Aff_transformation_2 CGAL_Aff_transformation2;
+
+typedef CGAL::Cartesian<CGAL::Gmpq> CGAL_Kernel3;
+typedef CGAL::Polyhedron_3<CGAL_Kernel3> CGAL_Polyhedron;
+typedef CGAL_Polyhedron::HalfedgeDS CGAL_HDS;
+typedef CGAL::Polyhedron_incremental_builder_3<CGAL_HDS> CGAL_Polybuilder;
+typedef CGAL::Nef_polyhedron_3<CGAL_Kernel3> CGAL_Nef_polyhedron3;
+typedef CGAL_Nef_polyhedron3::Aff_transformation_3 CGAL_Aff_transformation;
+typedef CGAL_Nef_polyhedron3::Vector_3 CGAL_Vector;
+typedef CGAL_Nef_polyhedron3::Plane_3 CGAL_Plane;
+typedef CGAL_Nef_polyhedron3::Point_3 CGAL_Point;
+
+struct CGAL_Nef_polyhedron
+{
+ int dim;
+ CGAL_Nef_polyhedron2 p2;
+ CGAL_Nef_polyhedron3 p3;
+
+ CGAL_Nef_polyhedron() {
+ dim = 0;
+ }
+
+ CGAL_Nef_polyhedron(const CGAL_Nef_polyhedron2 &p) {
+ dim = 2;
+ p2 = p;
+ }
+
+ CGAL_Nef_polyhedron(const CGAL_Nef_polyhedron3 &p) {
+ dim = 3;
+ p3 = p;
+ }
+
+ int weight() {
+ if (dim == 2)
+ return p2.explorer().number_of_vertices();
+ if (dim == 3)
+ return p3.number_of_vertices();
+ return 0;
+ }
+};
+
+#endif /* ENABLE_CGAL */
+
+#ifdef ENABLE_OPENCSG
+# include <opencsg.h>
+#endif
+
+class PolySet
+{
+public:
+ struct Point {
+ double x, y, z;
+ Point() : x(0), y(0), z(0) { }
+ Point(double x, double y, double z) : x(x), y(y), z(z) { }
+ };
+ typedef QList<Point> Polygon;
+ QVector<Polygon> polygons;
+ QVector<Polygon> borders;
+ Grid3d<void*> grid;
+
+ bool is2d;
+ int convexity;
+
+ PolySet();
+ ~PolySet();
+
+ void append_poly();
+ void append_vertex(double x, double y, double z);
+ void insert_vertex(double x, double y, double z);
+
+ void append_vertex(double x, double y) {
+ append_vertex(x, y, 0.0);
+ }
+ void insert_vertex(double x, double y) {
+ insert_vertex(x, y, 0.0);
+ }
+
+ enum colormode_e {
+ COLORMODE_NONE,
+ COLORMODE_MATERIAL,
+ COLORMODE_CUTOUT,
+ COLORMODE_HIGHLIGHT,
+ COLORMODE_BACKGROUND
+ };
+
+ enum csgmode_e {
+ CSGMODE_NONE,
+ CSGMODE_NORMAL = 1,
+ CSGMODE_DIFFERENCE = 2,
+ CSGMODE_BACKGROUND = 11,
+ CSGMODE_BACKGROUND_DIFFERENCE = 12,
+ CSGMODE_HIGHLIGHT = 21,
+ 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();
+};
+
+class CSGTerm
+{
+public:
+ enum type_e {
+ TYPE_PRIMITIVE,
+ TYPE_UNION,
+ TYPE_INTERSECTION,
+ TYPE_DIFFERENCE
+ };
+
+ type_e type;
+ PolySet *polyset;
+ QString label;
+ CSGTerm *left;
+ CSGTerm *right;
+ double m[20];
+ int refcounter;
+
+ CSGTerm(PolySet *polyset, double m[20], QString label);
+ CSGTerm(type_e type, CSGTerm *left, CSGTerm *right);
+
+ CSGTerm *normalize();
+ CSGTerm *normalize_tail();
+
+ CSGTerm *link();
+ void unlink();
+ QString dump();
+};
+
+class CSGChain
+{
+public:
+ QVector<PolySet*> polysets;
+ QVector<double*> matrices;
+ QVector<CSGTerm::type_e> types;
+ QVector<QString> labels;
+
+ CSGChain();
+
+ void add(PolySet *polyset, double *m, CSGTerm::type_e type, QString label);
+ void import(CSGTerm *term, CSGTerm::type_e type = CSGTerm::TYPE_UNION);
+ QString dump();
+};
+
+class AbstractNode
+{
+public:
+ QVector<AbstractNode*> children;
+ const ModuleInstantiation *modinst;
+
+ int progress_mark;
+ void progress_prepare();
+ void progress_report() const;
+
+ int idx;
+ static int idx_counter;
+ 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(CGAL_Nef_polyhedron N);
+ };
+ static QCache<QString, cgal_nef_cache_entry> cgal_nef_cache;
+ 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;
+};
+
+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;
+};
+
+class AbstractPolyNode : public AbstractNode
+{
+public:
+ enum render_mode_e {
+ RENDER_CGAL,
+ RENDER_OPENCSG
+ };
+ AbstractPolyNode(const ModuleInstantiation *mi) : AbstractNode(mi) { };
+ virtual PolySet *render_polyset(render_mode_e mode) const;
+#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);
+};
+
+extern QHash<QString,Value> dxf_dim_cache;
+extern QHash<QString,Value> dxf_cross_cache;
+
+extern int progress_report_count;
+extern void (*progress_report_f)(const class AbstractNode*, void*, int);
+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();
+
+void dxf_tesselate(PolySet *ps, DxfData *dxf, double rot, bool up, bool do_triangle_splitting, double h);
+void dxf_border_to_ps(PolySet *ps, DxfData *dxf);
+
+#endif /* INCLUDE_ABSTRACT_NODE_DETAILS */
+
+class Highlighter : public QSyntaxHighlighter
+{
+public:
+ Highlighter(QTextDocument *parent);
+ void highlightBlock(const QString &text);
+};
+
+extern AbstractModule *parse(const char *text, int debug);
+extern int get_fragments_from_r(double r, double fn, double fs, double fa);
+
+extern QString commandline_commands;
+extern int parser_error_pos;
+
+#ifdef ENABLE_CGAL
+void export_stl(CGAL_Nef_polyhedron *root_N, QString filename, 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);
+#endif
+extern void handle_dep(QString filename);
+
+#endif
+
diff --git a/src/polyset.cc b/src/polyset.cc
new file mode 100644
index 0000000..5f22fde
--- /dev/null
+++ b/src/polyset.cc
@@ -0,0 +1,706 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+#include "Preferences.h"
+
+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;
+ convexity = 1;
+ refcount = 1;
+}
+
+PolySet::~PolySet()
+{
+ assert(refcount == 0);
+}
+
+PolySet* PolySet::link()
+{
+ refcount++;
+ return this;
+}
+
+void PolySet::unlink()
+{
+ if (--refcount == 0)
+ delete this;
+}
+
+void PolySet::append_poly()
+{
+ polygons.append(Polygon());
+}
+
+void PolySet::append_vertex(double x, double y, double z)
+{
+ grid.align(x, y, z);
+ polygons.last().append(Point(x, y, z));
+}
+
+void PolySet::insert_vertex(double x, double y, double z)
+{
+ grid.align(x, y, z);
+ polygons.last().insert(0, Point(x, y, z));
+}
+
+static void gl_draw_triangle(GLint *shaderinfo, const PolySet::Point *p0, const PolySet::Point *p1, const PolySet::Point *p2, bool e0, bool e1, bool e2, double z, bool mirrored)
+{
+ double ax = p1->x - p0->x, bx = p1->x - p2->x;
+ double ay = p1->y - p0->y, by = p1->y - p2->y;
+ double az = p1->z - p0->z, bz = p1->z - p2->z;
+ double nx = ay*bz - az*by;
+ double ny = az*bx - ax*bz;
+ double nz = ax*by - ay*bx;
+ double nl = sqrt(nx*nx + ny*ny + nz*nz);
+ glNormal3d(nx / nl, ny / nl, nz / nl);
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ double e0f = e0 ? 2.0 : -1.0;
+ double e1f = e1 ? 2.0 : -1.0;
+ double e2f = e2 ? 2.0 : -1.0;
+ glVertexAttrib3d(shaderinfo[3], e0f, e1f, e2f);
+ glVertexAttrib3d(shaderinfo[4], p1->x, p1->y, p1->z + z);
+ glVertexAttrib3d(shaderinfo[5], p2->x, p2->y, p2->z + z);
+ glVertexAttrib3d(shaderinfo[6], 0.0, 1.0, 0.0);
+ glVertex3d(p0->x, p0->y, p0->z + z);
+ if (!mirrored) {
+ glVertexAttrib3d(shaderinfo[3], e0f, e1f, e2f);
+ glVertexAttrib3d(shaderinfo[4], p0->x, p0->y, p0->z + z);
+ glVertexAttrib3d(shaderinfo[5], p2->x, p2->y, p2->z + z);
+ glVertexAttrib3d(shaderinfo[6], 0.0, 0.0, 1.0);
+ glVertex3d(p1->x, p1->y, p1->z + z);
+ }
+ glVertexAttrib3d(shaderinfo[3], e0f, e1f, e2f);
+ glVertexAttrib3d(shaderinfo[4], p0->x, p0->y, p0->z + z);
+ glVertexAttrib3d(shaderinfo[5], p1->x, p1->y, p1->z + z);
+ glVertexAttrib3d(shaderinfo[6], 1.0, 0.0, 0.0);
+ glVertex3d(p2->x, p2->y, p2->z + z);
+ if (mirrored) {
+ glVertexAttrib3d(shaderinfo[3], e0f, e1f, e2f);
+ glVertexAttrib3d(shaderinfo[4], p0->x, p0->y, p0->z + z);
+ glVertexAttrib3d(shaderinfo[5], p2->x, p2->y, p2->z + z);
+ glVertexAttrib3d(shaderinfo[6], 0.0, 0.0, 1.0);
+ glVertex3d(p1->x, p1->y, p1->z + z);
+ }
+ }
+ else
+#endif
+ {
+ glVertex3d(p0->x, p0->y, p0->z + z);
+ if (!mirrored)
+ glVertex3d(p1->x, p1->y, p1->z + z);
+ glVertex3d(p2->x, p2->y, p2->z + z);
+ if (mirrored)
+ glVertex3d(p1->x, p1->y, p1->z + z);
+ }
+}
+
+void PolySet::render_surface(colormode_e colormode, csgmode_e csgmode, double *m, GLint *shaderinfo) const
+{
+ double m_scale_rotate_det =
+ m[0]*m[5]*m[10] + m[4]*m[9]*m[2] + m[8]*m[1]*m[6] -
+ (m[8]*m[5]*m[2] + m[4]*m[1]*m[10] + m[0]*m[9]*m[6]);
+ bool mirrored = m_scale_rotate_det < 0;
+
+ if (colormode == COLORMODE_MATERIAL) {
+ const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_FRONT_COLOR);
+ glColor3f(col.redF(), col.greenF(), col.blueF());
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ glUniform4f(shaderinfo[1], col.redF(), col.greenF(), col.blueF(), 1.0f);
+ glUniform4f(shaderinfo[2], 255 / 255.0, 236 / 255.0, 94 / 255.0, 1.0);
+ }
+#endif /* ENABLE_OPENCSG */
+ }
+ if (colormode == COLORMODE_CUTOUT) {
+ const QColor &col = Preferences::inst()->color(Preferences::OPENCSG_FACE_BACK_COLOR);
+ glColor3f(col.redF(), col.greenF(), col.blueF());
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ glUniform4f(shaderinfo[1], 157 / 255.0, 203 / 255.0, 81 / 255.0, 1.0);
+ glUniform4f(shaderinfo[2], 171 / 255.0, 216 / 255.0, 86 / 255.0, 1.0);
+ }
+#endif /* ENABLE_OPENCSG */
+ }
+ if (colormode == COLORMODE_HIGHLIGHT) {
+ glColor4ub(255, 157, 81, 128);
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ glUniform4f(shaderinfo[1], 255 / 255.0, 157 / 255.0, 81 / 255.0, 0.5);
+ glUniform4f(shaderinfo[2], 255 / 255.0, 171 / 255.0, 86 / 255.0, 0.5);
+ }
+#endif /* ENABLE_OPENCSG */
+ }
+ if (colormode == COLORMODE_BACKGROUND) {
+ glColor4ub(180, 180, 180, 128);
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ glUniform4f(shaderinfo[1], 180 / 255.0, 180 / 255.0, 180 / 255.0, 0.5);
+ glUniform4f(shaderinfo[2], 150 / 255.0, 150 / 255.0, 150 / 255.0, 0.5);
+ }
+#endif /* ENABLE_OPENCSG */
+ }
+#ifdef ENABLE_OPENCSG
+ if (shaderinfo) {
+ glUniform1f(shaderinfo[7], shaderinfo[9]);
+ glUniform1f(shaderinfo[8], shaderinfo[10]);
+ }
+#endif /* ENABLE_OPENCSG */
+ if (this->is2d) {
+ double zbase = csgmode;
+ glBegin(GL_TRIANGLES);
+ for (double z = -zbase/2; z < zbase; z += zbase)
+ {
+ for (int i = 0; i < polygons.size(); i++) {
+ const Polygon *poly = &polygons[i];
+ if (poly->size() == 3) {
+ if (z < 0) {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(2), &poly->at(1), true, true, true, z, mirrored);
+ } else {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(1), &poly->at(2), true, true, true, z, mirrored);
+ }
+ }
+ else if (poly->size() == 4) {
+ if (z < 0) {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(3), &poly->at(1), true, false, true, z, mirrored);
+ gl_draw_triangle(shaderinfo, &poly->at(2), &poly->at(1), &poly->at(3), true, false, true, z, mirrored);
+ } else {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(1), &poly->at(3), true, false, true, z, mirrored);
+ gl_draw_triangle(shaderinfo, &poly->at(2), &poly->at(3), &poly->at(1), true, false, true, z, mirrored);
+ }
+ }
+ else {
+ Point center;
+ for (int j = 0; j < poly->size(); j++) {
+ center.x += poly->at(j).x;
+ center.y += poly->at(j).y;
+ }
+ center.x /= poly->size();
+ center.y /= poly->size();
+ for (int j = 1; j <= poly->size(); j++) {
+ if (z < 0) {
+ gl_draw_triangle(shaderinfo, &center, &poly->at(j % poly->size()), &poly->at(j - 1),
+ false, true, false, z, mirrored);
+ } else {
+ gl_draw_triangle(shaderinfo, &center, &poly->at(j - 1), &poly->at(j % poly->size()),
+ false, true, false, z, mirrored);
+ }
+ }
+ }
+ }
+ }
+ const QVector<Polygon> *borders_p = &borders;
+ if (borders_p->size() == 0)
+ borders_p = &polygons;
+ for (int i = 0; i < borders_p->size(); i++) {
+ const Polygon *poly = &borders_p->at(i);
+ for (int j = 1; j <= poly->size(); j++) {
+ Point p1 = poly->at(j - 1), p2 = poly->at(j - 1);
+ Point p3 = poly->at(j % poly->size()), p4 = poly->at(j % poly->size());
+ p1.z -= zbase/2, p2.z += zbase/2;
+ p3.z -= zbase/2, p4.z += zbase/2;
+ gl_draw_triangle(shaderinfo, &p2, &p1, &p3, true, true, false, 0, mirrored);
+ gl_draw_triangle(shaderinfo, &p2, &p3, &p4, false, true, true, 0, mirrored);
+ }
+ }
+ glEnd();
+ } else {
+ for (int i = 0; i < polygons.size(); i++) {
+ const Polygon *poly = &polygons[i];
+ glBegin(GL_TRIANGLES);
+ if (poly->size() == 3) {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(1), &poly->at(2), true, true, true, 0, mirrored);
+ }
+ else if (poly->size() == 4) {
+ gl_draw_triangle(shaderinfo, &poly->at(0), &poly->at(1), &poly->at(3), true, false, true, 0, mirrored);
+ gl_draw_triangle(shaderinfo, &poly->at(2), &poly->at(3), &poly->at(1), true, false, true, 0, mirrored);
+ }
+ else {
+ Point center;
+ for (int j = 0; j < poly->size(); j++) {
+ center.x += poly->at(j).x;
+ center.y += poly->at(j).y;
+ center.z += poly->at(j).z;
+ }
+ center.x /= poly->size();
+ center.y /= poly->size();
+ center.z /= poly->size();
+ for (int j = 1; j <= poly->size(); j++) {
+ gl_draw_triangle(shaderinfo, &center, &poly->at(j - 1), &poly->at(j % poly->size()), false, true, false, 0, mirrored);
+ }
+ }
+ glEnd();
+ }
+ }
+}
+
+void PolySet::render_edges(colormode_e colormode, csgmode_e csgmode) const
+{
+ if (colormode == COLORMODE_MATERIAL)
+ glColor3ub(255, 236, 94);
+ if (colormode == COLORMODE_CUTOUT)
+ glColor3ub(171, 216, 86);
+ if (colormode == COLORMODE_HIGHLIGHT)
+ glColor4ub(255, 171, 86, 128);
+ if (colormode == COLORMODE_BACKGROUND)
+ glColor4ub(150, 150, 150, 128);
+ if (this->is2d) {
+ double zbase = csgmode;
+ for (double z = -zbase/2; z < zbase; z += zbase)
+ {
+ for (int i = 0; i < borders.size(); i++) {
+ const Polygon *poly = &borders[i];
+ glBegin(GL_LINE_LOOP);
+ for (int j = 0; j < poly->size(); j++) {
+ const Point *p = &poly->at(j);
+ glVertex3d(p->x, p->y, z);
+ }
+ glEnd();
+ }
+ }
+ for (int i = 0; i < borders.size(); i++) {
+ const Polygon *poly = &borders[i];
+ glBegin(GL_LINES);
+ for (int j = 0; j < poly->size(); j++) {
+ const Point *p = &poly->at(j);
+ glVertex3d(p->x, p->y, -zbase/2);
+ glVertex3d(p->x, p->y, +zbase/2);
+ }
+ glEnd();
+ }
+ } else {
+ for (int i = 0; i < polygons.size(); i++) {
+ const Polygon *poly = &polygons[i];
+ glBegin(GL_LINE_LOOP);
+ for (int j = 0; j < poly->size(); j++) {
+ const Point *p = &poly->at(j);
+ glVertex3d(p->x, p->y, p->z);
+ }
+ glEnd();
+ }
+ }
+}
+
+#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
+ 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
+ {
+ 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);
+ }
+ return CGAL_Nef_polyhedron();
+}
+
+#endif /* ENABLE_CGAL */
+
+PolySet *AbstractPolyNode::render_polyset(render_mode_e) const
+{
+ return NULL;
+}
+
+#ifdef ENABLE_CGAL
+
+CGAL_Nef_polyhedron AbstractPolyNode::render_cgal_nef_polyhedron() 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();
+
+ PolySet *ps = render_polyset(RENDER_CGAL);
+ 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;
+}
+
+#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;
+}
+
diff --git a/src/primitives.cc b/src/primitives.cc
new file mode 100644
index 0000000..2f50578
--- /dev/null
+++ b/src/primitives.cc
@@ -0,0 +1,529 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+
+enum primitive_type_e {
+ CUBE,
+ SPHERE,
+ CYLINDER,
+ POLYHEDRON,
+ SQUARE,
+ CIRCLE,
+ POLYGON
+};
+
+class PrimitiveModule : public AbstractModule
+{
+public:
+ primitive_type_e type;
+ PrimitiveModule(primitive_type_e type) : type(type) { }
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+};
+
+class PrimitiveNode : public AbstractPolyNode
+{
+public:
+ 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;
+};
+
+AbstractNode *PrimitiveModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const
+{
+ PrimitiveNode *node = new PrimitiveNode(inst, type);
+
+ node->center = false;
+ node->x = node->y = node->z = node->h = node->r1 = node->r2 = 1;
+
+ QVector<QString> argnames;
+ QVector<Expression*> argexpr;
+
+ if (type == CUBE) {
+ argnames = QVector<QString>() << "size" << "center";
+ }
+ if (type == SPHERE) {
+ argnames = QVector<QString>() << "r";
+ }
+ if (type == CYLINDER) {
+ argnames = QVector<QString>() << "h" << "r1" << "r2" << "center";
+ }
+ if (type == POLYHEDRON) {
+ argnames = QVector<QString>() << "points" << "triangles" << "convexity";
+ }
+ if (type == SQUARE) {
+ argnames = QVector<QString>() << "size" << "center";
+ }
+ if (type == CIRCLE) {
+ argnames = QVector<QString>() << "r";
+ }
+ if (type == POLYGON) {
+ argnames = QVector<QString>() << "points" << "paths" << "convexity";
+ }
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ node->fn = c.lookup_variable("$fn").num;
+ node->fs = c.lookup_variable("$fs").num;
+ node->fa = c.lookup_variable("$fa").num;
+
+ if (type == CUBE) {
+ Value size = c.lookup_variable("size");
+ Value center = c.lookup_variable("center");
+ size.getnum(node->x);
+ size.getnum(node->y);
+ size.getnum(node->z);
+ size.getv3(node->x, node->y, node->z);
+ if (center.type == Value::BOOL) {
+ node->center = center.b;
+ }
+ }
+
+ if (type == SPHERE) {
+ Value r = c.lookup_variable("r");
+ if (r.type == Value::NUMBER) {
+ node->r1 = r.num;
+ }
+ }
+
+ if (type == CYLINDER) {
+ Value h = c.lookup_variable("h");
+ Value r, r1, r2;
+ r1 = c.lookup_variable("r1");
+ r2 = c.lookup_variable("r2");
+ if (r1.type != Value::NUMBER && r1.type != Value::NUMBER)
+ r = c.lookup_variable("r");
+ Value center = c.lookup_variable("center");
+ if (h.type == Value::NUMBER) {
+ node->h = h.num;
+ }
+ if (r.type == Value::NUMBER) {
+ node->r1 = r.num;
+ node->r2 = r.num;
+ }
+ if (r1.type == Value::NUMBER) {
+ node->r1 = r1.num;
+ }
+ if (r2.type == Value::NUMBER) {
+ node->r2 = r2.num;
+ }
+ if (center.type == Value::BOOL) {
+ node->center = center.b;
+ }
+ }
+
+ if (type == POLYHEDRON) {
+ node->points = c.lookup_variable("points");
+ node->triangles = c.lookup_variable("triangles");
+ }
+
+ if (type == SQUARE) {
+ Value size = c.lookup_variable("size");
+ Value center = c.lookup_variable("center");
+ size.getnum(node->x);
+ size.getnum(node->y);
+ size.getv2(node->x, node->y);
+ if (center.type == Value::BOOL) {
+ node->center = center.b;
+ }
+ }
+
+ if (type == CIRCLE) {
+ Value r = c.lookup_variable("r");
+ if (r.type == Value::NUMBER) {
+ node->r1 = r.num;
+ }
+ }
+
+ if (type == POLYGON) {
+ node->points = c.lookup_variable("points");
+ node->paths = c.lookup_variable("paths");
+ }
+
+ node->convexity = c.lookup_variable("convexity", true).num;
+ if (node->convexity < 1)
+ node->convexity = 1;
+
+ return node;
+}
+
+void register_builtin_primitives()
+{
+ builtin_modules["cube"] = new PrimitiveModule(CUBE);
+ builtin_modules["sphere"] = new PrimitiveModule(SPHERE);
+ builtin_modules["cylinder"] = new PrimitiveModule(CYLINDER);
+ builtin_modules["polyhedron"] = new PrimitiveModule(POLYHEDRON);
+ builtin_modules["square"] = new PrimitiveModule(SQUARE);
+ builtin_modules["circle"] = new PrimitiveModule(CIRCLE);
+ builtin_modules["polygon"] = new PrimitiveModule(POLYGON);
+}
+
+/*!
+ Returns the number of subdivision of a whole circle, given radius and
+ the three special variables $fn, $fs and $fa
+*/
+int get_fragments_from_r(double r, double fn, double fs, double fa)
+{
+ if (fn > 0.0)
+ return (int)fn;
+ return (int)ceil(fmax(fmin(360.0 / fa, r*M_PI / fs), 5));
+}
+
+PolySet *PrimitiveNode::render_polyset(render_mode_e) const
+{
+ PolySet *p = new PolySet();
+
+ if (type == CUBE && x > 0 && y > 0 && 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;
+ } else {
+ x1 = y1 = z1 = 0;
+ x2 = x;
+ y2 = y;
+ z2 = z;
+ }
+
+ p->append_poly(); // top
+ p->append_vertex(x1, y1, z2);
+ p->append_vertex(x2, y1, z2);
+ p->append_vertex(x2, y2, z2);
+ p->append_vertex(x1, y2, z2);
+
+ p->append_poly(); // bottom
+ p->append_vertex(x1, y2, z1);
+ p->append_vertex(x2, y2, z1);
+ p->append_vertex(x2, y1, z1);
+ p->append_vertex(x1, y1, z1);
+
+ p->append_poly(); // side1
+ p->append_vertex(x1, y1, z1);
+ p->append_vertex(x2, y1, z1);
+ p->append_vertex(x2, y1, z2);
+ p->append_vertex(x1, y1, z2);
+
+ p->append_poly(); // side2
+ p->append_vertex(x2, y1, z1);
+ p->append_vertex(x2, y2, z1);
+ p->append_vertex(x2, y2, z2);
+ p->append_vertex(x2, y1, z2);
+
+ p->append_poly(); // side3
+ p->append_vertex(x2, y2, z1);
+ p->append_vertex(x1, y2, z1);
+ p->append_vertex(x1, y2, z2);
+ p->append_vertex(x2, y2, z2);
+
+ p->append_poly(); // side4
+ p->append_vertex(x1, y2, z1);
+ p->append_vertex(x1, y1, z1);
+ p->append_vertex(x1, y1, z2);
+ p->append_vertex(x1, y2, z2);
+ }
+
+ if (type == SPHERE && r1 > 0)
+ {
+ struct point2d {
+ double x, y;
+ };
+
+ struct ring_s {
+ int fragments;
+ point2d *points;
+ double r, z;
+ };
+
+ int rings = get_fragments_from_r(r1, fn, fs, fa);
+ ring_s ring[rings];
+
+ for (int i = 0; i < rings; i++) {
+ double phi = (M_PI * (i + 0.5)) / rings;
+ ring[i].r = r1 * sin(phi);
+ ring[i].z = r1 * cos(phi);
+ ring[i].fragments = get_fragments_from_r(ring[i].r, fn, fs, fa);
+ ring[i].points = new point2d[ring[i].fragments];
+ for (int j = 0; j < ring[i].fragments; j++) {
+ phi = (M_PI*2*j) / ring[i].fragments;
+ ring[i].points[j].x = ring[i].r * cos(phi);
+ ring[i].points[j].y = ring[i].r * sin(phi);
+ }
+ }
+
+ p->append_poly();
+ for (int i = 0; i < ring[0].fragments; i++)
+ p->append_vertex(ring[0].points[i].x, ring[0].points[i].y, ring[0].z);
+
+ for (int i = 0; i < rings-1; i++) {
+ ring_s *r1 = &ring[i];
+ ring_s *r2 = &ring[i+1];
+ int r1i = 0, r2i = 0;
+ while (r1i < r1->fragments || r2i < r2->fragments)
+ {
+ if (r1i >= r1->fragments)
+ goto sphere_next_r2;
+ if (r2i >= r2->fragments)
+ goto sphere_next_r1;
+ if ((double)r1i / r1->fragments <
+ (double)r2i / r2->fragments)
+ {
+sphere_next_r1:
+ p->append_poly();
+ int r1j = (r1i+1) % r1->fragments;
+ p->insert_vertex(r1->points[r1i].x, r1->points[r1i].y, r1->z);
+ p->insert_vertex(r1->points[r1j].x, r1->points[r1j].y, r1->z);
+ p->insert_vertex(r2->points[r2i % r2->fragments].x, r2->points[r2i % r2->fragments].y, r2->z);
+ r1i++;
+ } else {
+sphere_next_r2:
+ p->append_poly();
+ int r2j = (r2i+1) % r2->fragments;
+ p->append_vertex(r2->points[r2i].x, r2->points[r2i].y, r2->z);
+ p->append_vertex(r2->points[r2j].x, r2->points[r2j].y, r2->z);
+ p->append_vertex(r1->points[r1i % r1->fragments].x, r1->points[r1i % r1->fragments].y, r1->z);
+ r2i++;
+ }
+ }
+ }
+
+ p->append_poly();
+ for (int i = 0; i < ring[rings-1].fragments; i++)
+ p->insert_vertex(ring[rings-1].points[i].x, ring[rings-1].points[i].y, ring[rings-1].z);
+ }
+
+ if (type == CYLINDER && h > 0 && r1 >=0 && r2 >= 0 && (r1 > 0 || r2 > 0))
+ {
+ int fragments = get_fragments_from_r(fmax(r1, r2), fn, fs, fa);
+
+ double z1, z2;
+ if (center) {
+ z1 = -h/2;
+ z2 = +h/2;
+ } else {
+ z1 = 0;
+ z2 = h;
+ }
+
+ struct point2d {
+ double x, y;
+ };
+
+ point2d circle1[fragments];
+ point2d circle2[fragments];
+
+ for (int i=0; i<fragments; i++) {
+ double phi = (M_PI*2*i) / fragments;
+ if (r1 > 0) {
+ circle1[i].x = r1*cos(phi);
+ circle1[i].y = r1*sin(phi);
+ } else {
+ circle1[i].x = 0;
+ circle1[i].y = 0;
+ }
+ if (r2 > 0) {
+ circle2[i].x = r2*cos(phi);
+ circle2[i].y = r2*sin(phi);
+ } else {
+ circle2[i].x = 0;
+ circle2[i].y = 0;
+ }
+ }
+
+ for (int i=0; i<fragments; i++) {
+ int j = (i+1) % fragments;
+ if (r1 > 0) {
+ p->append_poly();
+ p->insert_vertex(circle1[i].x, circle1[i].y, z1);
+ p->insert_vertex(circle2[i].x, circle2[i].y, z2);
+ p->insert_vertex(circle1[j].x, circle1[j].y, z1);
+ }
+ if (r2 > 0) {
+ p->append_poly();
+ p->insert_vertex(circle2[i].x, circle2[i].y, z2);
+ p->insert_vertex(circle2[j].x, circle2[j].y, z2);
+ p->insert_vertex(circle1[j].x, circle1[j].y, z1);
+ }
+ }
+
+ if (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) {
+ p->append_poly();
+ for (int i=0; i<fragments; i++)
+ p->append_vertex(circle2[i].x, circle2[i].y, z2);
+ }
+ }
+
+ if (type == POLYHEDRON)
+ {
+ p->convexity = convexity;
+ for (int i=0; i<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()) {
+ double px, py, pz;
+ if (points.vec[pt]->getv3(px, py, pz))
+ p->insert_vertex(px, py, pz);
+ }
+ }
+ }
+ }
+
+ if (type == SQUARE)
+ {
+ double x1, x2, y1, y2;
+ if (center) {
+ x1 = -x/2;
+ x2 = +x/2;
+ y1 = -y/2;
+ y2 = +y/2;
+ } else {
+ x1 = y1 = 0;
+ x2 = x;
+ y2 = y;
+ }
+
+ p->is2d = true;
+ p->append_poly();
+ p->append_vertex(x1, y1);
+ p->append_vertex(x2, y1);
+ p->append_vertex(x2, y2);
+ p->append_vertex(x1, y2);
+ }
+
+ if (type == CIRCLE)
+ {
+ int fragments = get_fragments_from_r(r1, fn, fs, fa);
+
+ struct point2d {
+ double x, y;
+ };
+
+ point2d circle[fragments];
+
+ for (int i=0; i<fragments; i++) {
+ double phi = (M_PI*2*i) / fragments;
+ circle[i].x = r1*cos(phi);
+ circle[i].y = r1*sin(phi);
+ }
+
+ p->is2d = true;
+ p->append_poly();
+ for (int i=0; i<fragments; i++)
+ p->append_vertex(circle[i].x, circle[i].y);
+ }
+
+ if (type == POLYGON)
+ {
+ DxfData dd;
+
+ for (int i=0; i<points.vec.size(); i++) {
+ double x = points.vec[i]->vec[0]->num;
+ double y = points.vec[i]->vec[1]->num;
+ dd.points.append(DxfData::Point(x, y));
+ }
+
+ if (paths.vec.size() == 0)
+ {
+ dd.paths.append(DxfData::Path());
+ for (int i=0; i<points.vec.size(); i++) {
+ if (i < dd.points.size()) {
+ DxfData::Point *p = &dd.points[i];
+ dd.paths.last().points.append(p);
+ }
+ }
+ if (dd.paths.last().points.size() > 0) {
+ dd.paths.last().points.append(dd.paths.last().points.first());
+ dd.paths.last().is_closed = true;
+ }
+ }
+ else
+ {
+ for (int i=0; i<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;
+ if (idx < dd.points.size()) {
+ DxfData::Point *p = &dd.points[idx];
+ dd.paths.last().points.append(p);
+ }
+ }
+ if (dd.paths.last().points.isEmpty()) {
+ dd.paths.removeLast();
+ } else {
+ dd.paths.last().points.append(dd.paths.last().points.first());
+ dd.paths.last().is_closed = true;
+ }
+ }
+ }
+
+ p->is2d = true;
+ p->convexity = convexity;
+ dxf_tesselate(p, &dd, 0, true, false, 0);
+ dxf_border_to_ps(p, &dd);
+ }
+
+ return p;
+}
+
+QString PrimitiveNode::dump(QString indent) 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;
+ }
+ return dump_cache;
+}
+
diff --git a/src/printutils.cc b/src/printutils.cc
new file mode 100644
index 0000000..a75cf14
--- /dev/null
+++ b/src/printutils.cc
@@ -0,0 +1,44 @@
+#include "printutils.h"
+#include "MainWindow.h"
+
+QList<QString> print_messages_stack;
+
+void print_messages_push()
+{
+ print_messages_stack.append(QString());
+}
+
+void print_messages_pop()
+{
+ QString msg = print_messages_stack.last();
+ print_messages_stack.removeLast();
+ if (print_messages_stack.size() > 0 && !msg.isNull()) {
+ if (!print_messages_stack.last().isEmpty())
+ print_messages_stack.last() += "\n";
+ print_messages_stack.last() += msg;
+ }
+}
+
+void PRINT(const QString &msg)
+{
+ if (msg.isNull())
+ return;
+ if (print_messages_stack.size() > 0) {
+ if (!print_messages_stack.last().isEmpty())
+ print_messages_stack.last() += "\n";
+ print_messages_stack.last() += msg;
+ }
+ PRINT_NOCACHE(msg);
+}
+
+void PRINT_NOCACHE(const QString &msg)
+{
+ if (msg.isNull())
+ return;
+ if (MainWindow::current_win.isNull()) {
+ fprintf(stderr, "%s\n", msg.toAscii().data());
+ } else {
+ MainWindow::current_win->console->append(msg);
+ }
+}
+
diff --git a/src/printutils.h b/src/printutils.h
new file mode 100644
index 0000000..18cef93
--- /dev/null
+++ b/src/printutils.h
@@ -0,0 +1,19 @@
+#ifndef PRINTUTILS_H_
+#define PRINTUTILS_H_
+
+#include <QString>
+#include <QList>
+
+extern QList<QString> print_messages_stack;
+void print_messages_push();
+void print_messages_pop();
+
+void PRINT(const QString &msg);
+#define PRINTF(_fmt, ...) do { QString _m; _m.sprintf(_fmt, ##__VA_ARGS__); PRINT(_m); } while (0)
+#define PRINTA(_fmt, ...) do { QString _m = QString(_fmt).arg(__VA_ARGS__); PRINT(_m); } while (0)
+
+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)
+
+#endif
diff --git a/src/qtcolorbutton/README.txt b/src/qtcolorbutton/README.txt
new file mode 100644
index 0000000..4b55f67
--- /dev/null
+++ b/src/qtcolorbutton/README.txt
@@ -0,0 +1,2 @@
+The QtColorButton class is copied from the gradient editor in Qt's shared tool classes:
+http://qt.gitorious.org/qt/qt/trees/master/tools/shared/qtgradienteditor
diff --git a/src/qtcolorbutton/qtcolorbutton.cpp b/src/qtcolorbutton/qtcolorbutton.cpp
new file mode 100644
index 0000000..21b9848
--- /dev/null
+++ b/src/qtcolorbutton/qtcolorbutton.cpp
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtcolorbutton.h"
+#include <QtGui/QColorDialog>
+#include <QtGui/QPainter>
+#include <QtCore/QMimeData>
+#include <QtGui/QDragEnterEvent>
+#include <QtGui/QApplication>
+
+QT_BEGIN_NAMESPACE
+
+class QtColorButtonPrivate
+{
+ QtColorButton *q_ptr;
+ Q_DECLARE_PUBLIC(QtColorButton)
+public:
+ QColor m_color;
+#ifndef QT_NO_DRAGANDDROP
+ QColor m_dragColor;
+ QPoint m_dragStart;
+ bool m_dragging;
+#endif
+ bool m_backgroundCheckered;
+
+ void slotEditColor();
+ QColor shownColor() const;
+ QPixmap generatePixmap() const;
+};
+
+void QtColorButtonPrivate::slotEditColor()
+{
+ bool ok;
+ const QRgb rgba = QColorDialog::getRgba(m_color.rgba(), &ok, q_ptr);
+ if (!ok)
+ return;
+ const QColor c = QColor::fromRgba(rgba);
+ if (c == q_ptr->color())
+ return;
+ q_ptr->setColor(c);
+ emit q_ptr->colorChanged(m_color);
+}
+
+QColor QtColorButtonPrivate::shownColor() const
+{
+#ifndef QT_NO_DRAGANDDROP
+ if (m_dragging)
+ return m_dragColor;
+#endif
+ return m_color;
+}
+
+QPixmap QtColorButtonPrivate::generatePixmap() const
+{
+ QPixmap pix(24, 24);
+
+ int pixSize = 20;
+ QBrush br(shownColor());
+
+ QPixmap pm(2 * pixSize, 2 * pixSize);
+ QPainter pmp(&pm);
+ pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray);
+ pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray);
+ pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray);
+ pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray);
+ pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, shownColor());
+ br = QBrush(pm);
+
+ QPainter p(&pix);
+ int corr = 1;
+ QRect r = pix.rect().adjusted(corr, corr, -corr, -corr);
+ p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
+ p.fillRect(r, br);
+
+ p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr,
+ r.width() / 2, r.height() / 2,
+ QColor(shownColor().rgb()));
+ p.drawRect(pix.rect().adjusted(0, 0, -1, -1));
+
+ return pix;
+}
+
+///////////////
+
+QtColorButton::QtColorButton(QWidget *parent)
+ : QToolButton(parent)
+{
+ d_ptr = new QtColorButtonPrivate;
+ d_ptr->q_ptr = this;
+ d_ptr->m_dragging = false;
+ d_ptr->m_backgroundCheckered = true;
+
+ setAcceptDrops(true);
+
+ connect(this, SIGNAL(clicked()), this, SLOT(slotEditColor()));
+ setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
+}
+
+QtColorButton::~QtColorButton()
+{
+ delete d_ptr;
+}
+
+void QtColorButton::setColor(const QColor &color)
+{
+ if (d_ptr->m_color == color)
+ return;
+ d_ptr->m_color = color;
+ update();
+}
+
+QColor QtColorButton::color() const
+{
+ return d_ptr->m_color;
+}
+
+void QtColorButton::setBackgroundCheckered(bool checkered)
+{
+ if (d_ptr->m_backgroundCheckered == checkered)
+ return;
+ d_ptr->m_backgroundCheckered = checkered;
+ update();
+}
+
+bool QtColorButton::isBackgroundCheckered() const
+{
+ return d_ptr->m_backgroundCheckered;
+}
+
+void QtColorButton::paintEvent(QPaintEvent *event)
+{
+ QToolButton::paintEvent(event);
+ if (!isEnabled())
+ return;
+
+ const int pixSize = 10;
+ QBrush br(d_ptr->shownColor());
+ if (d_ptr->m_backgroundCheckered) {
+ QPixmap pm(2 * pixSize, 2 * pixSize);
+ QPainter pmp(&pm);
+ pmp.fillRect(0, 0, pixSize, pixSize, Qt::white);
+ pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white);
+ pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black);
+ pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black);
+ pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor());
+ br = QBrush(pm);
+ }
+
+ QPainter p(this);
+ const int corr = 4;
+ QRect r = rect().adjusted(corr, corr, -corr, -corr);
+ p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
+ p.fillRect(r, br);
+
+ //const int adjX = qRound(r.width() / 4.0);
+ //const int adjY = qRound(r.height() / 4.0);
+ //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY),
+ // QColor(d_ptr->shownColor().rgb()));
+ /*
+ p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0),
+ QColor(d_ptr->shownColor().rgb()));
+ p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4),
+ QColor(d_ptr->shownColor().rgb()));
+ */
+ /*
+ const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha())));
+ p.setPen(frameColor0);
+ p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1));
+ */
+
+ const QColor frameColor1(0, 0, 0, 26);
+ p.setPen(frameColor1);
+ p.drawRect(r.adjusted(1, 1, -2, -2));
+ const QColor frameColor2(0, 0, 0, 51);
+ p.setPen(frameColor2);
+ p.drawRect(r.adjusted(0, 0, -1, -1));
+}
+
+void QtColorButton::mousePressEvent(QMouseEvent *event)
+{
+#ifndef QT_NO_DRAGANDDROP
+ if (event->button() == Qt::LeftButton)
+ d_ptr->m_dragStart = event->pos();
+#endif
+ QToolButton::mousePressEvent(event);
+}
+
+void QtColorButton::mouseMoveEvent(QMouseEvent *event)
+{
+#ifndef QT_NO_DRAGANDDROP
+ if (event->buttons() & Qt::LeftButton &&
+ (d_ptr->m_dragStart - event->pos()).manhattanLength() > QApplication::startDragDistance()) {
+ QMimeData *mime = new QMimeData;
+ mime->setColorData(color());
+ QDrag *drg = new QDrag(this);
+ drg->setMimeData(mime);
+ drg->setPixmap(d_ptr->generatePixmap());
+ setDown(false);
+ event->accept();
+ drg->start();
+ return;
+ }
+#endif
+ QToolButton::mouseMoveEvent(event);
+}
+
+#ifndef QT_NO_DRAGANDDROP
+void QtColorButton::dragEnterEvent(QDragEnterEvent *event)
+{
+ const QMimeData *mime = event->mimeData();
+ if (!mime->hasColor())
+ return;
+
+ event->accept();
+ d_ptr->m_dragColor = qvariant_cast<QColor>(mime->colorData());
+ d_ptr->m_dragging = true;
+ update();
+}
+
+void QtColorButton::dragLeaveEvent(QDragLeaveEvent *event)
+{
+ event->accept();
+ d_ptr->m_dragging = false;
+ update();
+}
+
+void QtColorButton::dropEvent(QDropEvent *event)
+{
+ event->accept();
+ d_ptr->m_dragging = false;
+ if (d_ptr->m_dragColor == color())
+ return;
+ setColor(d_ptr->m_dragColor);
+ emit colorChanged(color());
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "moc_qtcolorbutton.cpp"
diff --git a/src/qtcolorbutton/qtcolorbutton.h b/src/qtcolorbutton/qtcolorbutton.h
new file mode 100644
index 0000000..26bdde0
--- /dev/null
+++ b/src/qtcolorbutton/qtcolorbutton.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTCOLORBUTTON_H
+#define QTCOLORBUTTON_H
+
+#include <QtGui/QToolButton>
+
+QT_BEGIN_NAMESPACE
+
+class QtColorButton : public QToolButton
+{
+ Q_OBJECT
+ Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered)
+public:
+ QtColorButton(QWidget *parent = 0);
+ ~QtColorButton();
+
+ bool isBackgroundCheckered() const;
+ void setBackgroundCheckered(bool checkered);
+
+ QColor color() const;
+
+public slots:
+
+ void setColor(const QColor &color);
+
+signals:
+ void colorChanged(const QColor &color);
+protected:
+ void paintEvent(QPaintEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+ void mouseMoveEvent(QMouseEvent *event);
+#ifndef QT_NO_DRAGANDDROP
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dragLeaveEvent(QDragLeaveEvent *event);
+ void dropEvent(QDropEvent *event);
+#endif
+private:
+ class QtColorButtonPrivate *d_ptr;
+ Q_DECLARE_PRIVATE(QtColorButton)
+ Q_DISABLE_COPY(QtColorButton)
+ Q_PRIVATE_SLOT(d_func(), void slotEditColor())
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qtcolorbutton/qtcolorbutton.pri b/src/qtcolorbutton/qtcolorbutton.pri
new file mode 100644
index 0000000..0e41068
--- /dev/null
+++ b/src/qtcolorbutton/qtcolorbutton.pri
@@ -0,0 +1,4 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+SOURCES += $$PWD/qtcolorbutton.cpp
+HEADERS += $$PWD/qtcolorbutton.h
diff --git a/src/render.cc b/src/render.cc
new file mode 100644
index 0000000..7c82d92
--- /dev/null
+++ b/src/render.cc
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#include <QProgressDialog>
+#include <QApplication>
+#include <QTime>
+
+class RenderModule : public AbstractModule
+{
+public:
+ RenderModule() { }
+ 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);
+
+ QVector<QString> argnames = QVector<QString>() << "convexity";
+ QVector<Expression*> argexpr;
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ Value v = c.lookup_variable("convexity");
+ if (v.type == Value::NUMBER)
+ node->convexity = (int)v.num;
+
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n != NULL)
+ node->children.append(n);
+ }
+
+ return node;
+}
+
+void register_builtin_render()
+{
+ builtin_modules["render"] = new RenderModule();
+}
+
+#ifdef ENABLE_CGAL
+
+CGAL_Nef_polyhedron RenderNode::render_cgal_nef_polyhedron() 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;
+ 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;
+ }
+ }
+
+ cgal_nef_cache.insert(cache_id, new cgal_nef_cache_entry(N), N.weight());
+ print_messages_pop();
+ progress_report();
+
+ return N;
+}
+
+static void report_func(const class AbstractNode*, void *vp, int mark)
+{
+ QProgressDialog *pd = (QProgressDialog*)vp;
+ int v = (int)((mark*100.0) / progress_report_count);
+ pd->setValue(v < 100 ? v : 99);
+ QString label;
+ label.sprintf("Rendering Polygon Mesh using CGAL (%d/%d)", mark, progress_report_count);
+ pd->setLabelText(label);
+ QApplication::processEvents();
+}
+
+CSGTerm *RenderNode::render_csg_term(double m[20], QVector<CSGTerm*> *highlights, QVector<CSGTerm*> *background) 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
+ {
+ PRINT_NOCACHE("Processing uncached render statement...");
+ // PRINTA("Cache ID: %1", cache_id);
+ QApplication::processEvents();
+
+ QTime t;
+ t.start();
+
+ QProgressDialog *pd = new QProgressDialog("Rendering Polygon Mesh using CGAL...", QString(), 0, 100);
+ pd->setValue(0);
+ pd->setAutoClose(false);
+ pd->show();
+ QApplication::processEvents();
+
+ progress_report_prep((AbstractNode*)this, report_func, pd);
+ N = this->render_cgal_nef_polyhedron();
+ progress_report_fin();
+
+ int s = t.elapsed() / 1000;
+ PRINTF_NOCACHE("..rendering time: %d hours, %d minutes, %d seconds", s / (60*60), (s / 60) % 60, s % 60);
+
+ delete pd;
+ }
+
+ 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 render() isn't valid 2-manifold! Modify your design..");
+ 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;
+}
+
+#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/surface.cc b/src/surface.cc
new file mode 100644
index 0000000..4dc3d98
--- /dev/null
+++ b/src/surface.cc
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+#include <QFile>
+
+class SurfaceModule : public AbstractModule
+{
+public:
+ SurfaceModule() { }
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+};
+
+class SurfaceNode : public AbstractPolyNode
+{
+public:
+ 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;
+};
+
+AbstractNode *SurfaceModule::evaluate(const Context *ctx, const ModuleInstantiation *inst) const
+{
+ SurfaceNode *node = new SurfaceNode(inst);
+ node->center = false;
+ node->convexity = 1;
+
+ QVector<QString> argnames = QVector<QString>() << "file" << "center" << "convexity";
+ QVector<Expression*> argexpr;
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ node->filename = c.lookup_variable("file").text;
+
+ Value center = c.lookup_variable("center", true);
+ if (center.type == Value::BOOL) {
+ node->center = center.b;
+ }
+
+ Value convexity = c.lookup_variable("convexity", true);
+ if (convexity.type == Value::NUMBER) {
+ node->convexity = (int)convexity.num;
+ }
+
+ return node;
+}
+
+void register_builtin_surface()
+{
+ builtin_modules["surface"] = new SurfaceModule();
+}
+
+PolySet *SurfaceNode::render_polyset(render_mode_e) const
+{
+ 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;
+ }
+
+ int lines = 0, columns = 0;
+ QHash<QPair<int,int>,double> data;
+ double min_val = 0;
+
+ while (!f.atEnd())
+ {
+ QString line = QString(f.readLine()).remove("\n").remove("\r");
+ line.replace(QRegExp("^[ \t]+"), "");
+ line.replace(QRegExp("[ \t]+$"), "");
+
+ if (line.startsWith("#"))
+ continue;
+
+ QStringList fields = line.split(QRegExp("[ \t]+"));
+ for (int i = 0; i < fields.count(); i++) {
+ if (i >= columns)
+ columns = i + 1;
+ double v = fields[i].toDouble();
+ data[QPair<int,int>(lines, i)] = v;
+ min_val = fmin(v-1, min_val);
+ }
+ lines++;
+ }
+
+ PolySet *p = new PolySet();
+ p->convexity = convexity;
+
+ double ox = center ? -columns/2.0 : 0;
+ double oy = center ? -lines/2.0 : 0;
+
+ for (int i = 1; i < lines; i++)
+ for (int j = 1; j < columns; j++)
+ {
+ double v1 = data[QPair<int,int>(i-1, j-1)];
+ double v2 = data[QPair<int,int>(i-1, j)];
+ double v3 = data[QPair<int,int>(i, j-1)];
+ double v4 = data[QPair<int,int>(i, j)];
+ double vx = (v1 + v2 + v3 + v4) / 4;
+
+ p->append_poly();
+ p->append_vertex(ox + j-1, oy + i-1, v1);
+ p->append_vertex(ox + j, oy + i-1, v2);
+ p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
+
+ p->append_poly();
+ p->append_vertex(ox + j, oy + i-1, v2);
+ p->append_vertex(ox + j, oy + i, v4);
+ p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
+
+ p->append_poly();
+ p->append_vertex(ox + j, oy + i, v4);
+ p->append_vertex(ox + j-1, oy + i, v3);
+ p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
+
+ p->append_poly();
+ p->append_vertex(ox + j-1, oy + i, v3);
+ p->append_vertex(ox + j-1, oy + i-1, v1);
+ p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
+ }
+
+ for (int i = 1; i < lines; i++)
+ {
+ p->append_poly();
+ p->append_vertex(ox + 0, oy + i-1, min_val);
+ p->append_vertex(ox + 0, oy + i-1, data[QPair<int,int>(i-1, 0)]);
+ p->append_vertex(ox + 0, oy + i, data[QPair<int,int>(i, 0)]);
+ p->append_vertex(ox + 0, oy + i, min_val);
+
+ p->append_poly();
+ p->insert_vertex(ox + columns-1, oy + i-1, min_val);
+ p->insert_vertex(ox + columns-1, oy + i-1, data[QPair<int,int>(i-1, columns-1)]);
+ p->insert_vertex(ox + columns-1, oy + i, data[QPair<int,int>(i, columns-1)]);
+ p->insert_vertex(ox + columns-1, oy + i, min_val);
+ }
+
+ for (int i = 1; i < columns; i++)
+ {
+ p->append_poly();
+ p->insert_vertex(ox + i-1, oy + 0, min_val);
+ p->insert_vertex(ox + i-1, oy + 0, data[QPair<int,int>(0, i-1)]);
+ p->insert_vertex(ox + i, oy + 0, data[QPair<int,int>(0, i)]);
+ p->insert_vertex(ox + i, oy + 0, min_val);
+
+ p->append_poly();
+ p->append_vertex(ox + i-1, oy + lines-1, min_val);
+ p->append_vertex(ox + i-1, oy + lines-1, data[QPair<int,int>(lines-1, i-1)]);
+ p->append_vertex(ox + i, oy + lines-1, data[QPair<int,int>(lines-1, i)]);
+ p->append_vertex(ox + i, oy + lines-1, min_val);
+ }
+
+ p->append_poly();
+ for (int i = 0; i < columns-1; i++)
+ p->insert_vertex(ox + i, oy + 0, min_val);
+ for (int i = 0; i < lines-1; i++)
+ p->insert_vertex(ox + columns-1, oy + i, min_val);
+ for (int i = columns-1; i > 0; i--)
+ p->insert_vertex(ox + i, oy + lines-1, min_val);
+ for (int i = lines-1; i > 0; i--)
+ p->insert_vertex(ox + 0, oy + i, min_val);
+
+ return p;
+}
+
+QString SurfaceNode::dump(QString indent) 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;
+}
+
diff --git a/src/transform.cc b/src/transform.cc
new file mode 100644
index 0000000..ecbfcc2
--- /dev/null
+++ b/src/transform.cc
@@ -0,0 +1,369 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ */
+
+#define INCLUDE_ABSTRACT_NODE_DETAILS
+
+#include "openscad.h"
+#include "printutils.h"
+
+enum transform_type_e {
+ SCALE,
+ ROTATE,
+ MIRROR,
+ TRANSLATE,
+ MULTMATRIX,
+ COLOR
+};
+
+class TransformModule : public AbstractModule
+{
+public:
+ transform_type_e type;
+ TransformModule(transform_type_e type) : type(type) { }
+ virtual AbstractNode *evaluate(const Context *ctx, const ModuleInstantiation *inst) const;
+};
+
+class TransformNode : public AbstractNode
+{
+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;
+};
+
+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;
+ for (int i = 16; i < 20; i++)
+ node->m[i] = -1;
+
+ QVector<QString> argnames;
+ QVector<Expression*> argexpr;
+
+ if (type == SCALE) {
+ argnames = QVector<QString>() << "v";
+ }
+ if (type == ROTATE) {
+ argnames = QVector<QString>() << "a" << "v";
+ }
+ if (type == MIRROR) {
+ argnames = QVector<QString>() << "v";
+ }
+ if (type == TRANSLATE) {
+ argnames = QVector<QString>() << "v";
+ }
+ if (type == MULTMATRIX) {
+ argnames = QVector<QString>() << "m";
+ }
+ if (type == COLOR) {
+ argnames = QVector<QString>() << "c";
+ }
+
+ Context c(ctx);
+ c.args(argnames, argexpr, inst->argnames, inst->argvalues);
+
+ if (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;
+ }
+ if (type == ROTATE)
+ {
+ Value val_a = c.lookup_variable("a");
+ if (val_a.type == Value::VECTOR)
+ {
+ for (int i = 0; i < 3 && i < val_a.vec.size(); i++) {
+ double a;
+ val_a.vec[i]->getnum(a);
+ double c = cos(a*M_PI/180.0);
+ double s = sin(a*M_PI/180.0);
+ double x = i == 0, y = i == 1, z = i == 2;
+ double mr[16] = {
+ x*x*(1-c)+c,
+ y*x*(1-c)+z*s,
+ z*x*(1-c)-y*s,
+ 0,
+ x*y*(1-c)-z*s,
+ y*y*(1-c)+c,
+ z*y*(1-c)+x*s,
+ 0,
+ x*z*(1-c)+y*s,
+ y*z*(1-c)-x*s,
+ z*z*(1-c)+c,
+ 0,
+ 0, 0, 0, 1
+ };
+ double m[16];
+ for (int x = 0; x < 4; x++)
+ for (int y = 0; y < 4; y++)
+ {
+ 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];
+ }
+ for (int i = 0; i < 16; i++)
+ node->m[i] = m[i];
+ }
+ }
+ else
+ {
+ Value val_v = c.lookup_variable("v");
+ double a = 0, x = 0, y = 0, z = 1;
+
+ val_a.getnum(a);
+
+ if (val_v.getv3(x, y, z)) {
+ if (x != 0.0 || y != 0.0 || z != 0.0) {
+ double sn = 1.0 / sqrt(x*x + y*y + z*z);
+ x *= sn, y *= sn, z *= sn;
+ }
+ }
+
+ if (x != 0.0 || y != 0.0 || z != 0.0)
+ {
+ 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->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->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;
+ }
+ }
+ }
+ if (type == MIRROR)
+ {
+ Value val_v = c.lookup_variable("v");
+ double x = 1, y = 0, z = 0;
+
+ if (val_v.getv3(x, y, z)) {
+ if (x != 0.0 || y != 0.0 || z != 0.0) {
+ double sn = 1.0 / sqrt(x*x + y*y + z*z);
+ x *= sn, y *= sn, z *= sn;
+ }
+ }
+
+ 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->m[ 4] = -2*x*y;
+ node->m[ 5] = 1-2*y*y;
+ node->m[ 6] = -2*z*y;
+
+ node->m[ 8] = -2*x*z;
+ node->m[ 9] = -2*y*z;
+ node->m[10] = 1-2*z*z;
+ }
+ }
+ if (type == TRANSLATE)
+ {
+ Value v = c.lookup_variable("v");
+ v.getv3(node->m[12], node->m[13], node->m[14]);
+ }
+ if (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]);
+ }
+ }
+ }
+ if (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;
+ }
+ }
+
+ foreach (ModuleInstantiation *v, inst->children) {
+ AbstractNode *n = v->evaluate(inst->ctx);
+ if (n != NULL)
+ node->children.append(n);
+ }
+
+ return node;
+}
+
+#ifdef ENABLE_CGAL
+
+CGAL_Nef_polyhedron TransformNode::render_cgal_nef_polyhedron() 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;
+
+ 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;
+ }
+ }
+
+ 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());
+ }
+
+ 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];
+ }
+
+ 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;
+}
+
+QString TransformNode::dump(QString indent) 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;
+}
+
+void register_builtin_transform()
+{
+ builtin_modules["scale"] = new TransformModule(SCALE);
+ builtin_modules["rotate"] = new TransformModule(ROTATE);
+ builtin_modules["mirror"] = new TransformModule(MIRROR);
+ builtin_modules["translate"] = new TransformModule(TRANSLATE);
+ builtin_modules["multmatrix"] = new TransformModule(MULTMATRIX);
+ builtin_modules["color"] = new TransformModule(COLOR);
+}
+
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 0000000..a02a27f
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ *
+ * 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 "openscad.h"
+
+Value::Value()
+{
+ reset_undef();
+}
+
+Value::~Value()
+{
+ for (int i = 0; i < vec.size(); i++)
+ delete vec[i];
+ vec.clear();
+}
+
+Value::Value(bool v)
+{
+ reset_undef();
+ type = BOOL;
+ b = v;
+}
+
+Value::Value(double v)
+{
+ reset_undef();
+ type = NUMBER;
+ num = v;
+}
+
+Value::Value(const QString &t)
+{
+ reset_undef();
+ type = STRING;
+ text = t;
+}
+
+Value::Value(const Value &v)
+{
+ *this = v;
+}
+
+Value& Value::operator = (const Value &v)
+{
+ reset_undef();
+ type = v.type;
+ b = v.b;
+ num = v.num;
+ for (int i = 0; i < v.vec.size(); i++)
+ vec.append(new Value(*v.vec[i]));
+ range_begin = v.range_begin;
+ range_step = v.range_step;
+ range_end = v.range_end;
+ text = v.text;
+ return *this;
+}
+
+Value Value::operator ! () const
+{
+ if (type == BOOL) {
+ return Value(!b);
+ }
+ return Value();
+}
+
+Value Value::operator && (const Value &v) const
+{
+ if (type == BOOL && v.type == BOOL) {
+ return Value(b && v.b);
+ }
+ return Value();
+}
+
+Value Value::operator || (const Value &v) const
+{
+ if (type == BOOL && v.type == BOOL) {
+ return Value(b || v.b);
+ }
+ return Value();
+}
+
+Value Value::operator + (const Value &v) const
+{
+ if (type == VECTOR && v.type == VECTOR) {
+ Value r;
+ r.type = VECTOR;
+ for (int i = 0; i < vec.size() && i < v.vec.size(); i++)
+ r.vec.append(new Value(*vec[i] + *v.vec[i]));
+ return r;
+ }
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num + v.num);
+ }
+ return Value();
+}
+
+Value Value::operator - (const Value &v) const
+{
+ if (type == VECTOR && v.type == VECTOR) {
+ Value r;
+ r.type = VECTOR;
+ for (int i = 0; i < vec.size() && i < v.vec.size(); i++)
+ r.vec.append(new Value(*vec[i] - *v.vec[i]));
+ return r;
+ }
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num - v.num);
+ }
+ return Value();
+}
+
+Value Value::operator * (const Value &v) const
+{
+ if (type == VECTOR && v.type == NUMBER) {
+ Value r;
+ r.type = VECTOR;
+ for (int i = 0; i < vec.size(); i++)
+ r.vec.append(new Value(*vec[i] * v));
+ return r;
+ }
+ if (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]));
+ return r;
+ }
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num * v.num);
+ }
+ return Value();
+}
+
+Value Value::operator / (const Value &v) const
+{
+ if (type == VECTOR && v.type == NUMBER) {
+ Value r;
+ r.type = VECTOR;
+ for (int i = 0; i < vec.size(); i++)
+ r.vec.append(new Value(*vec[i] / v));
+ return r;
+ }
+ if (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]));
+ return r;
+ }
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num / v.num);
+ }
+ return Value();
+}
+
+Value Value::operator % (const Value &v) const
+{
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(fmod(num, v.num));
+ }
+ return Value();
+}
+
+Value Value::operator < (const Value &v) const
+{
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num < v.num);
+ }
+ return Value();
+}
+
+Value Value::operator <= (const Value &v) const
+{
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num <= v.num);
+ }
+ return Value();
+}
+
+Value Value::operator == (const Value &v) const
+{
+ if (type == BOOL && v.type == BOOL) {
+ return Value(b == v.b);
+ }
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num == v.num);
+ }
+ if (type == RANGE && v.type == RANGE) {
+ return Value(range_begin == v.range_begin && range_step == v.range_step && range_end == v.range_end);
+ }
+ if (type == VECTOR && v.type == VECTOR) {
+ if (vec.size() != v.vec.size())
+ return Value(false);
+ for (int i=0; i<vec.size(); i++)
+ if (!(*vec[i] == *v.vec[i]).b)
+ return Value(false);
+ return Value(true);
+ }
+ if (type == STRING && v.type == STRING) {
+ return Value(text == v.text);
+ }
+ return Value(false);
+}
+
+Value Value::operator != (const Value &v) const
+{
+ Value eq = *this == v;
+ return Value(!eq.b);
+}
+
+Value Value::operator >= (const Value &v) const
+{
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num >= v.num);
+ }
+ return Value();
+}
+
+Value Value::operator > (const Value &v) const
+{
+ if (type == NUMBER && v.type == NUMBER) {
+ return Value(num > v.num);
+ }
+ return Value();
+}
+
+Value Value::inv() const
+{
+ if (type == VECTOR) {
+ Value r;
+ r.type = VECTOR;
+ for (int i = 0; i < vec.size(); i++)
+ r.vec.append(new Value(vec[i]->inv()));
+ return r;
+ }
+ if (type == NUMBER)
+ return Value(-num);
+ return Value();
+}
+
+bool Value::getnum(double &v) const
+{
+ if (type != NUMBER)
+ return false;
+ v = num;
+ return true;
+}
+
+bool Value::getv2(double &x, double &y) const
+{
+ if (type != VECTOR || vec.size() != 2)
+ return false;
+ if (vec[0]->type != NUMBER)
+ return false;
+ if (vec[1]->type != NUMBER)
+ return false;
+ x = vec[0]->num;
+ y = vec[1]->num;
+ return true;
+}
+
+bool Value::getv3(double &x, double &y, double &z) const
+{
+ if (type == VECTOR && vec.size() == 2) {
+ if (getv2(x, y)) {
+ z = 0;
+ return true;
+ }
+ return false;
+ }
+ if (type != VECTOR || vec.size() != 3)
+ return false;
+ if (vec[0]->type != NUMBER)
+ return false;
+ if (vec[1]->type != NUMBER)
+ return false;
+ if (vec[2]->type != NUMBER)
+ return false;
+ x = vec[0]->num;
+ y = vec[1]->num;
+ z = vec[2]->num;
+ return true;
+}
+
+QString Value::dump() const
+{
+ if (type == STRING) {
+ return QString("\"") + text + QString("\"");
+ }
+ if (type == VECTOR) {
+ QString text = "[";
+ for (int i = 0; i < vec.size(); i++) {
+ if (i > 0)
+ text += ", ";
+ text += vec[i]->dump();
+ }
+ return text + "]";
+ }
+ if (type == RANGE) {
+ QString text;
+ text.sprintf("[ %g : %g : %g ]", range_begin, range_step, range_end);
+ return text;
+ }
+ if (type == NUMBER) {
+ QString text;
+ text.sprintf("%g", num);
+ return text;
+ }
+ if (type == BOOL) {
+ return QString(b ? "true" : "false");
+ }
+ return QString("undef");
+}
+
+void Value::reset_undef()
+{
+ type = UNDEFINED;
+ b = false;
+ num = 0;
+ for (int i = 0; i < vec.size(); i++)
+ delete vec[i];
+ vec.clear();
+ range_begin = 0;
+ range_step = 0;
+ range_end = 0;
+ text = QString();
+}
+
contact: Jan Huwald // Impressum