diff options
Diffstat (limited to 'src')
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, ¢er, &poly->at(j % poly->size()), &poly->at(j - 1), + false, true, false, z, mirrored); + } else { + gl_draw_triangle(shaderinfo, ¢er, &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, ¢er, &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(); +} + |