diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/AboutDialog.h | 4 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron.cc | 3 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron.h | 2 | ||||
-rw-r--r-- | src/CGAL_Nef_polyhedron_DxfData.cc | 31 | ||||
-rw-r--r-- | src/PolySetCGALEvaluator.cc | 181 | ||||
-rw-r--r-- | src/cgal.h | 12 | ||||
-rw-r--r-- | src/cgalutils.cc | 28 | ||||
-rw-r--r-- | src/cgalutils.h | 2 | ||||
-rw-r--r-- | src/printutils.h | 22 | ||||
-rw-r--r-- | src/svg.cc | 249 | ||||
-rw-r--r-- | src/svg.h | 28 |
11 files changed, 477 insertions, 85 deletions
diff --git a/src/AboutDialog.h b/src/AboutDialog.h index 34122a0..1ae6533 100644 --- a/src/AboutDialog.h +++ b/src/AboutDialog.h @@ -3,12 +3,16 @@ #include "ui_AboutDialog.h" +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + class AboutDialog : public QDialog, public Ui::AboutDialog { Q_OBJECT; public: AboutDialog(QWidget *) { setupUi(this); + this->setWindowTitle( QString("About OpenSCAD ") + QString(TOSTRING( OPENSCAD_VERSION)) ); this->aboutText->setOpenExternalLinks(true); QUrl flattr_qurl(":icons/flattr.png" ); this->aboutText->loadResource( QTextDocument::ImageResource, flattr_qurl ); diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 56232a3..ad2e4e2 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -84,7 +84,8 @@ int CGAL_Nef_polyhedron::weight() const */ PolySet *CGAL_Nef_polyhedron::convertToPolyset() { - assert(!this->empty()); + if (this->empty()) + return new PolySet(); PolySet *ps = NULL; if (this->dim == 2) { ps = new PolySet(); diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 694b420..e8c505b 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -20,7 +20,7 @@ public: CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron copy() const; - std::string dump_p2() const; + std::string dump() const; int weight() const; class PolySet *convertToPolyset(); class DxfData *convertToDxfData() const; diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 411a340..0d0b8f0 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -28,6 +28,8 @@ #include "grid.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" +#include "cgalutils.h" +#include "svg.h" #ifdef ENABLE_CGAL @@ -77,29 +79,14 @@ DxfData *CGAL_Nef_polyhedron::convertToDxfData() const return dxfdata; } -// dump the 2 dimensional nef_poly. -std::string CGAL_Nef_polyhedron::dump_p2() const +std::string CGAL_Nef_polyhedron::dump() const { - std::stringstream out; - CGAL_Nef_polyhedron2::Explorer explorer = this->p2->explorer(); - CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator i; - out << "CGAL_Nef_polyhedron::p2 Vertices"; - for (i = explorer.vertices_begin(); i != explorer.vertices_end(); ++i) { - if ( explorer.is_standard( i ) ) { - CGAL_Nef_polyhedron2::Explorer::Point point = explorer.point( i ); - out << "\n Point x y: " - << CGAL::to_double(point.x()) << " " - << CGAL::to_double(point.y()); - } else { - CGAL_Nef_polyhedron2::Explorer::Ray ray = explorer.ray( i ); - CGAL_Nef_polyhedron2::Explorer::Point point = ray.point( 0 ); - out << "\n Ray x y dx dy: " - << CGAL::to_double(point.x()) << " " << CGAL::to_double(point.y()) << " " - << CGAL::to_double(ray.direction().dx()) << " " << CGAL::to_double(ray.direction().dy()); - } - } - out << "\nCGAL_Nef_polyhedron::p2 Vertices end"; - return out.str(); + if (this->dim==2) + return OpenSCAD::dump_svg( *this->p2 ); + else if (this->dim==3) + return OpenSCAD::dump_svg( *this->p3 ); + else + return std::string("Nef Polyhedron with dimension != 2 or 3"); } #endif // ENABLE_CGAL diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index bca9902..8e08e19 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -1,6 +1,8 @@ #include "PolySetCGALEvaluator.h" #include "cgal.h" #include "cgalutils.h" +#include <CGAL/convex_hull_3.h> + #include "polyset.h" #include "CGALEvaluator.h" #include "projectionnode.h" @@ -12,53 +14,53 @@ #include "dxftess.h" #include "module.h" +#include "svg.h" #include "printutils.h" #include "openscad.h" // get_fragments_from_r() #include <boost/foreach.hpp> +#include <vector> /* -This Visitor is used in the 'cut' process. Essentially, one or more of -our 3d nef polyhedrons have been 'cut' by the xy-plane, forming a number -of polygons that are essentially 'flat'. This Visitor object, when -called repeatedly, collects those flat polygons together and forms a new -2d nef polyhedron out of them. It keeps track of the 'holes' so that -in the final resulting polygon, they are preserved properly. +ZRemover -The output polygon is stored in the output_nefpoly2d variable. +This class converts one or more already 'flat' Nef3 polyhedra into a Nef2 +polyhedron by stripping off the 'z' coordinates from the vertices. The +resulting Nef2 poly is accumulated in the 'output_nefpoly2d' member variable. -For more information on 3d + 2d nef polyhedrons, facets, halffacets, -facet cycles, etc, please see these websites: +The 'z' coordinates will either be all 0s, for an xy-plane intersected Nef3, +or, they will be a mixture of -eps and +eps, for a thin-box intersected Nef3. -http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html -http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron_3-Traits---Halffacet.html +Notes on CGAL's Nef Polyhedron2: -The halffacet iteration 'circulator' code is based on OGL_helper.h +1. The 'mark' on a 2d Nef face is important when doing unions/intersections. + If the 'mark' of a face is wrong the resulting nef2 poly will be unexpected. +2. The 'mark' can be dependent on the points fed to the Nef2 constructor. + This is why we iterate through the 3d faces using the halfedge cycle + source()->target() instead of the ordinary source()->source(). The + the latter can generate sequences of points that will fail the + the CGAL::is_simple_2() test, resulting in improperly marked nef2 polys. +3. 3d facets have 'two sides'. we throw out the 'down' side to prevent dups. -Why do we throw out all 'down' half-facets? Imagine a triangle in 3d -space - it has one 'half face' for one 'side' and another 'half face' for the -other 'side'. If we iterated over both half-faces we would get the same vertex -coordinates twice. Instead, we only need one side, in our case, we -chose the 'up' side. +The class uses the 'visitor' pattern from the CGAL manual. See also +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html +http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3_ref/Class_Nef_polyhedron3.html +OGL_helper.h */ -class NefShellVisitor_for_cut { + +class ZRemover { public: - std::stringstream out; + logstream log; CGAL_Nef_polyhedron2::Boundary boundary; shared_ptr<CGAL_Nef_polyhedron2> tmpnef2d; shared_ptr<CGAL_Nef_polyhedron2> output_nefpoly2d; CGAL::Direction_3<CGAL_Kernel3> up; - bool debug; - NefShellVisitor_for_cut(bool debug=false) + ZRemover() { output_nefpoly2d.reset( new CGAL_Nef_polyhedron2() ); boundary = CGAL_Nef_polyhedron2::INCLUDED; up = CGAL::Direction_3<CGAL_Kernel3>(0,0,1); - this->debug = debug; - } - std::string dump() - { - return out.str(); + log = logstream(5); } void visit( CGAL_Nef_polyhedron3::Vertex_const_handle ) {} void visit( CGAL_Nef_polyhedron3::Halfedge_const_handle ) {} @@ -66,42 +68,66 @@ public: void visit( CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {} void visit( CGAL_Nef_polyhedron3::SFace_const_handle ) {} void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) { + log << " <!-- Halffacet visit. Mark: " << hfacet->mark() << " -->\n"; if ( hfacet->plane().orthogonal_direction() != this->up ) { - if (debug) out << "down facing half-facet. skipping\n"; + log << " <!-- down-facing half-facet. skipping -->\n"; + log << " <!-- Halffacet visit end-->\n"; return; } - int numcontours = 0; - CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator i; - CGAL_forall_facet_cycles_of( i, hfacet ) { - CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(i), c2(c1); - std::list<CGAL_Nef_polyhedron2::Point> contour; - CGAL_For_all( c1, c2 ) { - CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->source()->point(); - CGAL_Nef_polyhedron2::Point point2d( point3d.x(), point3d.y() ); - contour.push_back( point2d ); - } - tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); - if ( numcontours == 0 ) { - if (debug) out << " contour is a body. make union(). " << contour.size() << " points.\n" ; - *output_nefpoly2d += *tmpnef2d; + // possible optimization - throw out facets that are 'side facets' between + // the top & bottom of the big thin box. (i.e. mixture of z=-eps and z=eps) + + CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator fci; + int contour_counter = 0; + CGAL_forall_facet_cycles_of( fci, hfacet ) { + if ( fci.is_shalfedge() ) { + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(fci), cend(c1); + std::vector<CGAL_Nef_polyhedron2::Explorer::Point> contour; + CGAL_For_all( c1, cend ) { + CGAL_Nef_polyhedron3::Point_3 point3d = c1->source()->target()->point(); + CGAL_Nef_polyhedron2::Explorer::Point point2d( point3d.x(), point3d.y() ); + contour.push_back( point2d ); + } + + if (contour.size()==0) continue; + + log << " <!-- is_simple_2:" << CGAL::is_simple_2( contour.begin(), contour.end() ) << " --> \n"; + + tmpnef2d.reset( new CGAL_Nef_polyhedron2( contour.begin(), contour.end(), boundary ) ); + + if ( contour_counter == 0 ) { + log << " <!-- contour is a body. make union(). " << contour.size() << " points. -->\n" ; + *(output_nefpoly2d) += *(tmpnef2d); + } else { + log << " <!-- contour is a hole. make intersection(). " << contour.size() << " points. -->\n"; + *(output_nefpoly2d) *= *(tmpnef2d); + } + + log << "\n<!-- ======== output tmp nef: ==== -->\n" + << OpenSCAD::dump_svg( *tmpnef2d ) << "\n" + << "\n<!-- ======== output accumulator: ==== -->\n" + << OpenSCAD::dump_svg( *output_nefpoly2d ) << "\n"; + + contour_counter++; } else { - if (debug) out << " contour is a hole. make intersection(). " << contour.size() << " points.\n"; - *output_nefpoly2d *= *tmpnef2d; + log << " <!-- trivial facet cycle skipped -->\n"; } - numcontours++; } // next facet cycle (i.e. next contour) + log << " <!-- Halffacet visit end -->\n"; } // visit() }; PolySetCGALEvaluator::PolySetCGALEvaluator(CGALEvaluator &cgalevaluator) : PolySetEvaluator(cgalevaluator.getTree()), cgalevaluator(cgalevaluator) { - this->debug = false; } PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) { + //openscad_loglevel = 6; + logstream log(5); + // Before projecting, union all children CGAL_Nef_polyhedron sum; BOOST_FOREACH (AbstractNode * v, node.getChildren()) { @@ -120,38 +146,71 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) } } + //std::cout << sum.dump(); + //std::cout.flush(); + CGAL_Nef_polyhedron nef_poly; if (node.cut_mode) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { - // intersect 'sum' with the x-y plane CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3( 0,0,1,0 ); *sum.p3 = sum.p3->intersection( xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY); - - // Visit each polygon in sum.p3 and union/intersect into a 2d polygon (with holes) - // For info on Volumes, Shells, Facets, and the 'visitor' pattern, please see - // http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html - NefShellVisitor_for_cut shell_visitor; + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + try { + PRINT("Trying alternative intersection using very large thin box: "); + std::vector<CGAL_Point_3> pts; + // dont use z of 0. there are bugs in CGAL. + double inf = 1e8; + double eps = 0.001; + CGAL_Point_3 minpt( -inf, -inf, -eps ); + CGAL_Point_3 maxpt( inf, inf, eps ); + CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); + for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); + CGAL_Polyhedron bigbox; + CGAL::convex_hull_3( pts.begin(), pts.end(), bigbox ); + CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); + *sum.p3 = nef_bigbox.intersection( *sum.p3 ); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + sum.p3->clear(); + } + } + + if ( sum.p3->is_empty() ) { + CGAL::set_error_behaviour(old_behaviour); + PRINT("WARNING: projection() failed."); + return NULL; + } + + // remove z coordinates to make CGAL_Nef_polyhedron2 + log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; + try { + ZRemover zremover; CGAL_Nef_polyhedron3::Volume_const_iterator i; CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; for ( i = sum.p3->volumes_begin(); i != sum.p3->volumes_end(); ++i ) { + log << "<!-- volume. mark: " << i->mark() << " -->\n"; for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "<!-- shell. mark: " << i->mark() << " -->\n"; sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); - sum.p3->visit_shell_objects( sface_handle , shell_visitor ); + sum.p3->visit_shell_objects( sface_handle , zremover ); + log << "<!-- shell. end. -->\n"; } + log << "<!-- volume end. -->\n"; } - if (debug) std::cout << "shell visitor\n" << shell_visitor.dump() << "\nshell visitor end\n"; - - nef_poly.p2 = shell_visitor.output_nefpoly2d; + nef_poly.p2 = zremover.output_nefpoly2d; nef_poly.dim = 2; - if (debug) std::cout << "--\n" << nef_poly.dump_p2() << "\n"; - } - catch (const CGAL::Failure_exception &e) { - PRINTB("CGAL error in projection node: %s", e.what()); - return NULL; + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node while flattening: %s", e.what()); } + log << "</svg>\n"; + + CGAL::set_error_behaviour(old_behaviour); // Extract polygons in the XY plane, ignoring all other polygons // FIXME: If the polyhedron is really thin, there might be unwanted polygons @@ -239,7 +298,7 @@ PolySet *PolySetCGALEvaluator::evaluatePolySet(const ProjectionNode &node) PolySet *ps = nef_poly.convertToPolyset(); assert( ps != NULL ); ps->convexity = node.convexity; - if (debug) std::cout << "--\n" << ps->dump() << "\n"; + logstream(9) << ps->dump() << "\n"; return ps; } @@ -33,6 +33,8 @@ using boost::uintmax_t; #include <CGAL/Polygon_with_holes_2.h> #include <CGAL/minkowski_sum_2.h> #include <CGAL/minkowski_sum_3.h> +#include <CGAL/bounding_box.h> +#include <CGAL/utils.h> #include <CGAL/assertions_behaviour.h> #include <CGAL/exceptions.h> @@ -54,6 +56,16 @@ 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::Point_3<CGAL_Kernel3> CGAL_Point_3; +typedef CGAL::Iso_cuboid_3<CGAL_Kernel3> CGAL_Iso_cuboid_3; + +// CGAL_Nef_polyhedron2 uses CGAL_Kernel2, but Iso_rectangle_2 needs to match +// CGAL_Nef_polyhedron2::Explorer::Point which is different than +// CGAL_Kernel2::Point. Hence the suffix 'e' +typedef CGAL_Nef_polyhedron2::Explorer::Point CGAL_Point_2e; +typedef CGAL::Iso_rectangle_2< CGAL::Simple_cartesian<NT> > CGAL_Iso_rectangle_2e; + + #ifdef PREV_NDEBUG #define NDEBUG PREV_NDEBUG #endif diff --git a/src/cgalutils.cc b/src/cgalutils.cc index e402139..51838df 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -145,5 +145,33 @@ CGAL_Polyhedron *createPolyhedronFromPolySet(const PolySet &ps) return P; } +CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ) +{ + CGAL_Iso_cuboid_3 result(-1,-1,-1,1,1,1); + CGAL_Nef_polyhedron3::Vertex_const_iterator vi; + std::vector<CGAL_Nef_polyhedron3::Point_3> points; + // can be optimized by rewriting bounding_box to accept vertices + CGAL_forall_vertices( vi, N ) + points.push_back( vi->point() ); + if (points.size()) + result = CGAL::bounding_box( points.begin(), points.end() ); + return result; +} + +CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ) +{ + CGAL_Iso_rectangle_2e result(-1,-1,1,1); + CGAL_Nef_polyhedron2::Explorer explorer = N.explorer(); + CGAL_Nef_polyhedron2::Explorer::Vertex_const_iterator vi; + std::vector<CGAL_Point_2e> points; + // can be optimized by rewriting bounding_box to accept vertices + for ( vi = explorer.vertices_begin(); vi != explorer.vertices_end(); ++vi ) + if ( explorer.is_standard( vi ) ) + points.push_back( explorer.point( vi ) ); + if (points.size()) + result = CGAL::bounding_box( points.begin(), points.end() ); + return result; +} + #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index a249697..9093c3f 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -5,5 +5,7 @@ class PolySet *createPolySetFromPolyhedron(const CGAL_Polyhedron &p); CGAL_Polyhedron *createPolyhedronFromPolySet(const class PolySet &ps); +CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); +CGAL_Iso_rectangle_2e bounding_box( const CGAL_Nef_polyhedron2 &N ); #endif diff --git a/src/printutils.h b/src/printutils.h index 521682c..9d99a19 100644 --- a/src/printutils.h +++ b/src/printutils.h @@ -22,4 +22,26 @@ void PRINT(const std::string &msg); void PRINT_NOCACHE(const std::string &msg); #define PRINTB_NOCACHE(_fmt, _arg) do { PRINT_NOCACHE(str(boost::format(_fmt) % _arg)); } while (0) +// extremely simple logging, eventually replace with something like boost.log +// usage: logstream out(5); openscad_loglevel=6; out << "hi"; +static int openscad_loglevel = 0; +class logstream +{ +public: + std::ostream *out; + int loglevel; + logstream( int level = 0 ) { + loglevel = level; + out = &(std::cout); + } + template <typename T> logstream & operator<<( T const &t ) { + if (out && loglevel <= openscad_loglevel) { + (*out) << t ; + out->flush(); + } + return *this; + } +}; + + #endif diff --git a/src/svg.cc b/src/svg.cc new file mode 100644 index 0000000..e5130b0 --- /dev/null +++ b/src/svg.cc @@ -0,0 +1,249 @@ +#include "cgalutils.h" +#include "svg.h" +#include <boost/algorithm/string.hpp> +#include <map> + +namespace OpenSCAD { + +// SVG code +// currently for debugging, not necessarily pretty or useful for users. (yet) +int svg_cursor_py = 0; +int svg_px_width = SVG_PXW; +int svg_px_height = SVG_PXH; + +std::string svg_header( int widthpx, int heightpx ) +{ + std::stringstream out; + out << "<svg width='" << widthpx << "px' height='" << heightpx << "px'" + << " xmlns='http://www.w3.org/2000/svg' version='1.1'>"; + return out.str(); +} + +std::string svg_label(std::string s) +{ + std::stringstream out; + out << " <text fill='black' x='20' y='40' font-size='24'>" << s << "</text>"; + return out.str(); +} + +std::string svg_border() +{ + std::stringstream out; + out << " <!-- border -->\n"; + out << " <polyline points='0,0 " + << svg_px_width << ",0 " + << svg_px_width << "," << svg_px_height + << " 0," << svg_px_height << "'" + << " style='fill:none;stroke:black' />\n"; + out << " <!-- /border -->"; + return out.str(); +} + +std::string svg_axes() +{ + std::stringstream out; + out << " <!-- axes -->\n"; + out << " <polyline points='10,455 10,475 10,465 18,465 2,465 10,465 14,461 6,469 10,465'" + << " style='fill:none;stroke:black;' />\n"; + out << " <!-- /axes -->"; + return out.str(); +} + +CGAL_Point_2e project_svg_3to2( CGAL_Point_3 p, CGAL_Iso_cuboid_3 bbox ) +{ + NT screenw(svg_px_width); + NT screenh(svg_px_height); + NT screenxc = screenw / 2; + NT screenyc = screenh / 2; + NT bboxx = ( bbox.xmax() - bbox.xmin() ); + NT bboxy = ( bbox.ymax() - bbox.ymin() ); + NT bboxz = ( bbox.zmax() - bbox.zmin() ); + NT largest_dim = CGAL::max( CGAL::max( bboxx, bboxy ), bboxz ); + NT bboxxc = bboxx / 2 + bbox.xmin(); + NT bboxyc = bboxy / 2 + bbox.ymin(); + NT bboxzc = bboxz / 2 + bbox.zmin(); + NT xinbox = ( p.x() - bboxxc ) / largest_dim; + NT yinbox = ( p.y() - bboxyc ) / largest_dim; + NT zinbox = ( p.z() - bboxzc ) / largest_dim; + // do simple fake paralell projection + NT tx = screenxc + xinbox * screenw / 1.618 + yinbox * screenh / 3.2; + NT ty = screenyc - zinbox * screenh / 1.618 - yinbox * screenh / 3.2; + return CGAL_Point_2e( tx, ty ); +} + +CGAL_Point_2e project_svg_2to2( CGAL_Point_2e p, CGAL_Iso_rectangle_2e bbox ) +{ + double screenw = svg_px_width; + double screenh = svg_px_height; + double borderw = screenw * 0.1618; + double borderh = screenh * 0.1618; + double vizw = screenw - borderw*2; + double vizh = screenh - borderh*2; + double bboxw = CGAL::to_double( bbox.xmax() - bbox.xmin() ); + double bboxh = CGAL::to_double( bbox.ymax() - bbox.ymin() ); + double xinbox = CGAL::to_double( p.x() ) - CGAL::to_double( bbox.xmin() ); + double yinbox = CGAL::to_double( p.y() ) - CGAL::to_double( bbox.ymin() ); + double tx = borderw + ( xinbox / ( bboxw==0?1:bboxw ) ) * ( vizw ); + double ty = screenh - borderh - ( yinbox / ( bboxh==0?1:bboxh ) ) * ( vizh ); + return CGAL_Point_2e( tx, ty ); +} + +std::string dump_cgal_nef_polyhedron2_face_svg( + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c1, + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c2, + CGAL_Nef_polyhedron2::Explorer explorer, + std::string color, + bool mark, + CGAL_Iso_rectangle_2e bbox ) +{ + std::stringstream out; + CGAL_For_all(c1, c2) { + if ( explorer.is_standard( explorer.target(c1) ) ) { + CGAL_Point_2e source = explorer.point( explorer.source( c1 ) ); + CGAL_Point_2e target = explorer.point( explorer.target( c1 ) ); + CGAL_Point_2e tp1 = project_svg_2to2( source, bbox ); + CGAL_Point_2e tp2 = project_svg_2to2( target, bbox ); + double mod=0; + if (color=="green") mod=10; + out << " <!-- Halfedge. Mark: " << c1->mark() << " -->\n"; + out << " <line" + << " x1='" << CGAL::to_double(tp1.x()) + mod << "'" + << " y1='" << CGAL::to_double(tp1.y()) - mod << "'" + << " x2='" << CGAL::to_double(tp2.x()) + mod << "'" + << " y2='" << CGAL::to_double(tp2.y()) - mod << "'" + << " stroke='" << color << "'"; + if (!mark) out << " stroke-dasharray='4 4' />\n"; + else out << " />\n"; + // crude "arrowhead" to indicate directionality + out << " <circle" + << " cx='" << CGAL::to_double(tp1.x()+ (tp2.x()-tp1.x())* 7/8) + mod << "'" + << " cy='" << CGAL::to_double(tp1.y()+ (tp2.y()-tp1.y())* 7/8) - mod << "'" + << " r='2'" + << " fill='" << color << "' stroke='" << color << "' />\n"; + } else { + out << " <!-- 2d Nef Rays - not implemented -->\n"; + } + } + return out.str(); +} + +std::string dump_svg( const CGAL_Nef_polyhedron2 &N ) +{ + std::stringstream out; + CGAL_Nef_polyhedron2::Explorer explorer = N.explorer(); + + CGAL_Iso_rectangle_2e bbox = bounding_box( N ); + + CGAL_Nef_polyhedron2::Explorer::Face_const_iterator i; + out << " <svg y='" << svg_cursor_py << "' width='" << svg_px_width + << "' height='" << svg_px_height + << "' xmlns='http://www.w3.org/2000/svg' version='1.1'>\n"; + out << svg_border() << "\n" << svg_axes() << "\n"; + svg_cursor_py += svg_px_height; + + for ( i = explorer.faces_begin(); i!= explorer.faces_end(); ++i ) { + out << " <!-- face begin. mark: " << i->mark() << " -->\n"; + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c1 + = explorer.face_cycle( i ), c2 ( c1 ); + out << dump_cgal_nef_polyhedron2_face_svg( c1, c2, explorer, "red", i->mark(), bbox ); + + CGAL_Nef_polyhedron2::Explorer::Hole_const_iterator j; + for ( j = explorer.holes_begin( i ); j!= explorer.holes_end( i ); ++j ) { + out << " <!-- hole begin. mark: " << j->mark() << " -->\n"; + CGAL_Nef_polyhedron2::Explorer::Halfedge_around_face_const_circulator c3( j ), c4 ( c3 ); + out << dump_cgal_nef_polyhedron2_face_svg( c3, c4, explorer, "green", j->mark(), bbox ); + out << " <!-- hole end -->\n"; + } + out << " <!-- face end -->\n"; + } + out << "</svg>"; + std::string tmp = out.str(); + boost::replace_all( tmp, "'", "\"" ); + return tmp; +} + + +// This uses the Shell Explorer pattern from the CGAL Manual to dump the 3d Nef Polyhedron information +// http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Nef_3/Chapter_main.html#Subsection_29.7.2 +class NefPoly3_dumper_svg { +public: + std::stringstream out; + CGAL_Iso_cuboid_3 bbox; + NefPoly3_dumper_svg(const CGAL_Nef_polyhedron3& N) + { + bbox = bounding_box( N ); + } + void visit(CGAL_Nef_polyhedron3::Vertex_const_handle ) {} + void visit(CGAL_Nef_polyhedron3::Halfedge_const_handle ) {} + void visit(CGAL_Nef_polyhedron3::SHalfedge_const_handle ) {} + void visit(CGAL_Nef_polyhedron3::SHalfloop_const_handle ) {} + void visit(CGAL_Nef_polyhedron3::SFace_const_handle ) {} + void visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) + { + int contour_count = 0; + out << " <!-- Halffacet. Mark: " << (*hfacet).mark() << " -->\n"; + std::string color = "gold"; + if (!(*hfacet).mark()) color = "green"; + CGAL_Nef_polyhedron3::Halffacet_cycle_const_iterator i; + CGAL_forall_facet_cycles_of( i, hfacet ) { + CGAL_Nef_polyhedron3::SHalfloop_const_handle shl_handle; + out << " <!-- Halffacet cycle: -->\n"; + if ( contour_count == 0 ) { + out << " <!-- Body contour:--> \n"; + } else { + out << " <!-- Hole contour:--> \n" ; + } + CGAL_Nef_polyhedron3::SHalfedge_around_facet_const_circulator c1(i), c2(c1); + CGAL_For_all( c1, c2 ) { + out << " <line"; + // don't know why we use source()->source(), except thats what CGAL does internally + CGAL_Point_3 source = c1->source()->source()->point(); + CGAL_Point_3 target = c1->source()->target()->point(); + CGAL_Point_2e tp1 = project_svg_3to2 ( source, bbox ); + CGAL_Point_2e tp2 = project_svg_3to2 ( target, bbox ); + out << " " + << "x1='" << CGAL::to_double(tp1.x()) << "' " + << "y1='" << CGAL::to_double(tp1.y()) << "' " + << "x2='" << CGAL::to_double(tp2.x()) << "' " + << "y2='" << CGAL::to_double(tp2.y()) << "' " + << " stroke='" << color << "'"; + if (!(*hfacet).mark()) out << " stroke-dasharray='4 4' />\n"; + else out << " />\n"; + } + contour_count++; + } // next facet cycle (i.e. next contour) + } // visit() + +}; + + +std::string dump_svg( const CGAL_Nef_polyhedron3 &N ) +{ + std::stringstream out; + out << svg_header() << "\n" << svg_border() << "\n" << svg_axes() << "\n"; + out << "<!--CGAL_Nef_polyhedron3 dump begin-->\n"; + + CGAL_Nef_polyhedron3::Volume_const_iterator c; + CGAL_forall_volumes(c,N) { + out << " <!--Processing volume...-->\n"; + out << " <!--Mark: " << (*c).mark() << "-->\n"; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator it; + CGAL_forall_shells_of(it,c) { + out << " <!--Processing shell...-->\n"; + NefPoly3_dumper_svg dumper_svg(N); + N.visit_shell_objects(CGAL_Nef_polyhedron3::SFace_const_handle(it), dumper_svg ); + out << dumper_svg.out.str(); + out << " <!--Processing shell end-->\n"; + } + out << " <!--Processing volume end-->\n"; + } + out << "<!--CGAL_Nef_polyhedron3 dump end-->\n"; + out << "</svg>"; + std::string tmp = out.str(); + boost::replace_all( tmp, "'", "\"" ); + return tmp; +} + +} // namespace + + diff --git a/src/svg.h b/src/svg.h new file mode 100644 index 0000000..828dc39 --- /dev/null +++ b/src/svg.h @@ -0,0 +1,28 @@ +#ifndef SVG_H_ +#define SVG_H_ + +#include "cgal.h" +#include <boost/algorithm/string.hpp> +#include <map> + +namespace OpenSCAD { + +// currently for debugging, not necessarily pretty or useful for users. (yet) + +#define SVG_PXW 480 +#define SVG_PXH 480 +extern int svg_cursor_py; +extern int svg_px_width; +extern int svg_px_height; + +std::string svg_header( int widthpx = SVG_PXW, int heightpx = SVG_PXH ); +std::string svg_label( std::string s ); +std::string svg_border(); +std::string svg_axes(); +std::string dump_svg( const CGAL_Nef_polyhedron2 &N ); +std::string dump_svg( const CGAL_Nef_polyhedron3 &N ); + +} // namespace + +#endif + |