diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 9094b18c..4afb22b9 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -14,6 +14,8 @@ #include #include +#include +using boost::format; namespace CGALUtils { @@ -590,7 +592,7 @@ holding the polygon and it's holes. */ bool tessellate_3d_face_with_holes( std::vector &polygons, std::vector &triangles, CGAL_Plane_3 &plane ) { if (polygons.size()==1 && polygons[0].size()==3) { - PRINT("input polygon has 3 points. shortcut tessellation."); + PRINTD("input polygon has 3 points. shortcut tessellation."); CGAL_Polygon_3 t; t.push_back(polygons[0][2]); t.push_back(polygons[0][1]); @@ -602,12 +604,12 @@ bool tessellate_3d_face_with_holes( std::vector &polygons, std:: CDT cdt; std::map vertmap; - PRINT("finding good projection"); + PRINTD("finding good projection"); projection_t goodproj = find_good_projection( plane ); - PRINTB("plane %s",plane ); - PRINTB("proj: %i %i",goodproj.plane % goodproj.flip); - PRINT("Inserting points and edges into Constrained Delaunay Triangulation"); + PRINTDB("plane %s",plane ); + PRINTDB("proj: %i %i",goodproj.plane % goodproj.flip); + PRINTD("Inserting points and edges into Constrained Delaunay Triangulation"); std::vector< std::vector > polygons2d; for (size_t i=0;i vhandles; @@ -636,7 +638,7 @@ bool tessellate_3d_face_with_holes( std::vector &polygons, std:: } size_t numholes = polygons2d.size()-1; - PRINTB("seeding %i holes",numholes); + PRINTDB("seeding %i holes",numholes); std::list list_of_seeds; for (size_t i=1;i &pgon = polygons2d[i]; @@ -654,19 +656,18 @@ bool tessellate_3d_face_with_holes( std::vector &polygons, std:: } std::list::iterator li = list_of_seeds.begin(); for (;li!=list_of_seeds.end();li++) { - //PRINTB("seed %s",*li); double x = CGAL::to_double( li->x() ); double y = CGAL::to_double( li->y() ); - PRINTB("seed %f,%f",x%y); + PRINTDB("seed %f,%f",x%y); } - PRINT("seeding done"); + PRINTD("seeding done"); - PRINT( "meshing" ); + PRINTD( "meshing" ); CGAL::refine_Delaunay_mesh_2_without_edge_refinement( cdt, list_of_seeds.begin(), list_of_seeds.end(), DummyCriteria() ); - PRINT("meshing done"); + PRINTD("meshing done"); // this fails because it calls is_simple and is_simple fails on many // Nef Polyhedron faces //CGAL::Orientation original_orientation = @@ -695,7 +696,7 @@ bool tessellate_3d_face_with_holes( std::vector &polygons, std:: } } - PRINTB("built %i triangles\n",triangles.size()); + PRINTDB("built %i triangles\n",triangles.size()); return err; } /////// Tessellation end @@ -917,13 +918,15 @@ public: #ifdef GEN_SURFACE_DEBUG printf("],\n"); - printf("points=["); + dbg.str(""); + dbg<<"points=["; for (int vidx=0;vidx 0) printf(","); + if (vidx > 0) dbg<<","; const Vector3d &v = vertices.getArray()[vidx]; - printf("[%g,%g,%g]", v[0], v[1], v[2]); + dbg<\n"; } -static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) +CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { assert(ps.getDimension() == 3); if (ps.isEmpty()) return new CGAL_Nef_polyhedron(); @@ -1025,16 +1028,19 @@ static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } } - if (plane_error) try { + if (plane_error) { + try { PolySet ps2(3); CGAL_Polyhedron P; PolysetUtils::tessellate_faces(ps, ps2); bool err = createPolyhedronFromPolySet(ps2,P); if (!err) N = new CGAL_Nef_polyhedron3(P); } - catch (const CGAL::Assertion_exception &e) { + catch (const CGAL::Failure_exception &e) { PRINTB("Alternate construction failed. CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); + N = new CGAL_Nef_polyhedron3(); } + } CGAL::set_error_behaviour(old_behaviour); return new CGAL_Nef_polyhedron(N); } diff --git a/src/cgalutils.h b/src/cgalutils.h index 9e6102c5..fcd5b897 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -13,6 +13,7 @@ namespace CGALUtils { CGAL_Iso_cuboid_3 boundingBox(const CGAL_Nef_polyhedron3 &N); }; +CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps); CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolySetFromNefPolyhedron3(const CGAL_Nef_polyhedron3 &N, PolySet &ps); diff --git a/src/import.cc b/src/import.cc index 7014ec13..7d62b5c2 100644 --- a/src/import.cc +++ b/src/import.cc @@ -106,6 +106,7 @@ AbstractNode *ImportModule::instantiate(const Context *ctx, const ModuleInstanti if (ext == ".stl") actualtype = TYPE_STL; else if (ext == ".off") actualtype = TYPE_OFF; else if (ext == ".dxf") actualtype = TYPE_DXF; + else if (ext == ".obj") actualtype = TYPE_OBJ; } ImportNode *node = new ImportNode(inst, actualtype); @@ -171,7 +172,7 @@ void uint32_byte_swap( uint32_t &x ) #endif } -void read_stl_facet( std::ifstream &f, stl_facet &facet ) +void read_stl_facet( std::istream &f, stl_facet &facet ) { f.read( (char*)facet.data8, STL_FACET_NUMBYTES ); #ifdef BOOST_BIG_ENDIAN @@ -182,118 +183,260 @@ void read_stl_facet( std::ifstream &f, stl_facet &facet ) #endif } +// translate the polyset so it's center is at 0,0,0 +void center_polyset( PolySet &p ) +{ + BoundingBox bb = p.getBoundingBox(); + Vector3d t = -bb.min(); + t -= (bb.max()-bb.min()) / 2; + p.translate( t ); +} + +/* create PolySet from GeomView's OFF format. return true on error, +false on success. This is an alternative back-up to CGAL's OFF loader. +CGAL's loader doesn't work on some OFF files, like those from the +Antiprism program that have single-vertex color faces. +Faces with <3 points, and colors, are completely ignored. +On error, the PolySet may be left in a partially-built state. */ +bool createPolySetFromOFF( std::istream &in, PolySet &ps ) +{ + bool err = false; + if (!in.good()) return true; + std::vector vertlist; + std::string line; + size_t numvertices, numfaces, numfaceverts, vertindex; + double x,y,z; + if (!std::getline(in, line)) return true; + if (line.find("OFF")==std::string::npos) return true; + if (!std::getline(in, line)) return true; + if (!(std::istringstream(line) >> numvertices >> numfaces)) return true; + if (numvertices==0 || numfaces==0) return true; + for (size_t i=0;i> x >> y >> z)) return true; + vertlist.push_back( Vector3d(x,y,z) ); + } + for (size_t i=0;i> numfaceverts)) return true; + // face with single vertex might be a weird color thing. skip. + if (numfaceverts<3) continue; + ps.append_poly(); + for (size_t j=0;j> vertindex)) return true; + Vector3d v = vertlist[vertindex%vertlist.size()]; + ps.append_vertex( v.x(), v.y(), v.z() ); + } + } + if (ps.polygons.size()==0) return true; + return err; +} + + +/* create PolySet from Wavefront(TM) OBJ format stream. return true on error, +false on success. Error during read can result in a mal-formed PolySet. +This code only reads simple vertices and faces, everything else is +ignored. Colors, Normals, and Textures are completely ignored. Face +point indexes with '/' textures will be read, but the textures are +ignored. +*/ +bool createPolySetFromOBJ( std::istream &in, PolySet &ps ) +{ + PRINTD("OBJ import"); + bool err = false; + if (!in.good()) return true; + std::vector vertlist; + std::string line,keyword,tmpline,faceindex; + int vertindex; + size_t pos; + double x,y,z; + while (std::getline(in, line)) { + // deal with the 'continued' line backslash feature of OBJ + while ((pos=line.find("\\"))!=std::string::npos) { + PRINTDB("line with backslash continuation: %s",line); + line = line.substr(0,pos==0 ? 0 : pos-1); + if (std::getline(in, tmpline)) { + PRINTDB(" adding tmpline %s",tmpline); + line += tmpline; + } + } + PRINTDB("line, full read: %s",line); + std::istringstream ss(line); + bool ok = (ss >> keyword); + if (ok && keyword == "v") { + if (!(ss >> x >> y >> z)) { return true; } + Vector3d v( x, y, z ); + vertlist.push_back( v ); + PRINTDB("vertex: %s",v.transpose()); + } + else if (ok && keyword == "f") { // face + ps.append_poly(); + while (ss >> faceindex) { + PRINTDB("face (vindexes) %s",faceindex); + if ((pos=faceindex.find("/"))!=std::string::npos) { + PRINTD(" vindex with /"); + std::istringstream ss2(faceindex.substr(0,pos)); + if (!(ss2 >> vertindex)) return true; + } else { + std::istringstream ss2(faceindex); + if (!(ss2 >> vertindex)) return true; + } + PRINTDB("vertindex %s",vertindex); + if (vertindex<0) { + PRINTD("<0, relative index"); + vertindex = vertlist.size()+vertindex; + } else { + PRINTD(">0, absolute index (1based)"); + vertindex--; + } + Vector3d v = vertlist[vertindex%vertlist.size()]; + ps.append_vertex( v.x(), v.y(), v.z() ); + } + if (ps.polygons.back().size()<3) return true; + } + } + if (ps.polygons.size()==0) return true; + return err; +} + +/* create PolySet from STL format stream. return true on error, false on +success. stream should be positioned at the end of file before passing +to this function. */ +bool createPolySetFromSTL( std::istream &f, PolySet &p ) +{ + bool err = false; + boost::regex ex_sfe("solid|facet|endloop"); + boost::regex ex_outer("outer loop"); + boost::regex ex_vertex("vertex"); + boost::regex ex_vertices("\\s*vertex\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)"); + + bool binary = false; + std::streampos file_size = f.tellg(); + if (file_size==0) { + PRINT("WARNING: Import file has size of 0"); + return true; + } + f.seekg(80); + if (!f.eof()) { + uint32_t facenum = 0; + f.read((char *)&facenum, sizeof(uint32_t)); +#ifdef BOOST_BIG_ENDIAN + uint32_byte_swap( facenum ); +#endif + if (file_size == static_cast(80 + 4 + 50*facenum)) { + binary = true; + } + } + f.seekg(0); + + char data[5]; + f.read(data, 5); + if (!binary && !f.eof() && !memcmp(data, "solid", 5)) { + int i = 0; + double vdata[3][3]; + std::string line; + std::getline(f, line); + while (!f.eof()) { + + std::getline(f, line); + boost::trim(line); + if (boost::regex_search(line, ex_sfe)) { + continue; + } + if (boost::regex_search(line, ex_outer)) { + i = 0; + continue; + } + boost::smatch results; + if (boost::regex_search(line, results, ex_vertices)) { + try { + for (int v=0;v<3;v++) { + vdata[i][v] = boost::lexical_cast(results[v+1]); + } + } + catch (const boost::bad_lexical_cast &blc) { + PRINTB("WARNING: Can't parse vertex line '%s'.", line); + i = 10; + continue; + } + 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.ignore(80-5+4); + while (1) { + stl_facet facet; + read_stl_facet( f, facet ); + if (f.eof()) break; + p.append_poly(); + p.append_vertex(facet.data.x1, facet.data.y1, facet.data.z1); + p.append_vertex(facet.data.x2, facet.data.y2, facet.data.z2); + p.append_vertex(facet.data.x3, facet.data.y3, facet.data.z3); + } + } + if (p.polygons.size()==0) err = true; + return err; +} + /*! - Will return an empty geometry if the import failed, but not NULL + Create a new Geometry from the data in the file at this->filename. + Will return an empty geometry if the import failed, but not NULL. + The file will be closed before this function returns. + The caller is responsible for freeing the new Geometry memory. */ Geometry *ImportNode::createGeometry() const { Geometry *g = NULL; + bool err = false; + PolySet *p = NULL; + + if (this->type & (TYPE_STL|TYPE_OFF|TYPE_OBJ)) { + p = new PolySet(3); + handle_dep((std::string)this->filename); + } switch (this->type) { case TYPE_STL: { - PolySet *p = new PolySet(3); - g = p; - - handle_dep((std::string)this->filename); // Open file and position at the end std::ifstream f(this->filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - if (!f.good()) { - PRINTB("WARNING: Can't open import file '%s'.", this->filename); - return g; - } - - boost::regex ex_sfe("solid|facet|endloop"); - boost::regex ex_outer("outer loop"); - boost::regex ex_vertex("vertex"); - boost::regex ex_vertices("\\s*vertex\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)"); - - bool binary = false; - std::streampos file_size = f.tellg(); - f.seekg(80); - if (!f.eof()) { - uint32_t facenum = 0; - f.read((char *)&facenum, sizeof(uint32_t)); -#ifdef BOOST_BIG_ENDIAN - uint32_byte_swap( facenum ); -#endif - if (file_size == static_cast(80 + 4 + 50*facenum)) { - binary = true; - } - } - f.seekg(0); - - char data[5]; - f.read(data, 5); - if (!binary && !f.eof() && !memcmp(data, "solid", 5)) { - int i = 0; - double vdata[3][3]; - std::string line; - std::getline(f, line); - while (!f.eof()) { - - std::getline(f, line); - boost::trim(line); - if (boost::regex_search(line, ex_sfe)) { - continue; - } - if (boost::regex_search(line, ex_outer)) { - i = 0; - continue; - } - boost::smatch results; - if (boost::regex_search(line, results, ex_vertices)) { - try { - for (int v=0;v<3;v++) { - vdata[i][v] = boost::lexical_cast(results[v+1]); - } - } - catch (const boost::bad_lexical_cast &blc) { - PRINTB("WARNING: Can't parse vertex line '%s'.", line); - i = 10; - continue; - } - 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.ignore(80-5+4); - while (1) { - stl_facet facet; - read_stl_facet( f, facet ); - if (f.eof()) break; - p->append_poly(); - p->append_vertex(facet.data.x1, facet.data.y1, facet.data.z1); - p->append_vertex(facet.data.x2, facet.data.y2, facet.data.z2); - p->append_vertex(facet.data.x3, facet.data.y3, facet.data.z3); - } - } + if (!(err=f.bad())) err = createPolySetFromSTL( f, *p ); + else PRINTB("WARNING: Can't open import file '%s'.", this->filename); + f.close(); } break; case TYPE_OFF: { - PolySet *p = new PolySet(3); - g = p; -#ifdef ENABLE_CGAL - CGAL_Polyhedron poly; + bool try_polyset_read = true; +#ifdef ENABLE_CGAL // we try CGAL read first, if it fails, we use polyset read std::ifstream file(this->filename.c_str(), std::ios::in | std::ios::binary); - if (!file.good()) { - PRINTB("WARNING: Can't open import file '%s'.", this->filename); + if (!(err=file.bad())) { + CGAL_Polyhedron poly; + file >> poly; + if (poly.size_of_vertices()==0) { + PRINTDB("CGAL import of %s failed. Attempting PolySet import.", filename); + } else { + err = createPolySetFromPolyhedron(poly, *p); + if (!err) try_polyset_read = false; + } } else { - file >> poly; - file.close(); - - bool err = createPolySetFromPolyhedron(poly, *p); + PRINTB("WARNING: Can't open import file '%s'.", this->filename); + } + file.close(); +#endif // ENABLE_CGAL + if (try_polyset_read) { + std::ifstream file(this->filename.c_str(), std::ios::in | std::ios::binary); + if (!(err=file.bad())) err = createPolySetFromOFF( file, *p ); + else PRINTB("WARNING: Can't open import file '%s'.", this->filename); + file.close(); } -#else - PRINT("WARNING: OFF import requires CGAL."); -#endif } break; case TYPE_DXF: { @@ -301,11 +444,30 @@ Geometry *ImportNode::createGeometry() const g = dd.toPolygon2d(); } break; + case TYPE_OBJ: { + std::ifstream file(this->filename.c_str(), std::ios::in | std::ios::binary); + if (!(err=file.bad())) err = createPolySetFromOBJ( file, *p ); + else PRINTB("WARNING: Can't open import file '%s'.", this->filename); + file.close(); + } + break; default: PRINTB("ERROR: Unsupported file format while trying to import file '%s'", this->filename); g = new PolySet(0); } + if (this->type & (TYPE_STL|TYPE_OFF|TYPE_OBJ)) { + if (err) { + PRINTB("WARNING: Import of %s failed",this->filename); + p->polygons.clear(); + } else if (this->center) { + center_polyset(*p); + } + if (OpenSCAD::debug!="") + PRINTDB("imported polyset:\n %s \n-----.", p->dump()); + g = p; + } + if (g) g->setConvexity(this->convexity); return g; } diff --git a/src/importnode.h b/src/importnode.h index bde614e1..6c6b6eb8 100644 --- a/src/importnode.h +++ b/src/importnode.h @@ -9,7 +9,8 @@ enum import_type_e { TYPE_UNKNOWN, TYPE_STL, TYPE_OFF, - TYPE_DXF + TYPE_DXF, + TYPE_OBJ }; class ImportNode : public LeafNode diff --git a/src/mainwin.cc b/src/mainwin.cc index cc46ced2..9b422528 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -493,7 +493,7 @@ MainWindow::openFile(const QString &new_filename) { QString actual_filename = new_filename; QFileInfo fi(new_filename); - if (fi.suffix().toLower().contains(QRegExp("^(stl|off|dxf)$"))) { + if (fi.suffix().toLower().contains(QRegExp("^(stl|off|dxf|obj)$"))) { actual_filename = QString(); } #ifdef ENABLE_MDI diff --git a/src/polyset-utils.cc b/src/polyset-utils.cc index 9504085d..1a60d9bb 100644 --- a/src/polyset-utils.cc +++ b/src/polyset-utils.cc @@ -199,8 +199,8 @@ namespace PolysetUtils { triangles.push_back( pgon ); } } - } catch (const CGAL::Assertion_exception &e) { - PRINTB("CGAL error in dxftess triangulate_polygon: %s", e.what()); + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error while triangulating polygon: %s", e.what()); err = true; } CGAL::set_error_behaviour(old_behaviour); diff --git a/src/printutils.h b/src/printutils.h index 18aaddee..1c554f7c 100644 --- a/src/printutils.h +++ b/src/printutils.h @@ -22,7 +22,6 @@ 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) - void PRINT_CONTEXT(const class Context *ctx, const class Module *mod, const class ModuleInstantiation *inst); std::string two_digit_exp_format( std::string doublestr ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6461bd85..0ebe2207 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -842,6 +842,7 @@ list(APPEND CGALPNGTEST_FILES ${FEATURES_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_FILE list(APPEND CGALPNGTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/transform-nan-inf-tests.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/unreadable_by_cgal_off.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles-test.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles_dir/localfiles-compatibility-test.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/rotate-empty-bbox.scad diff --git a/tests/regression/cgalpngtest/unreadable_by_cgal_off-expected.png b/tests/regression/cgalpngtest/unreadable_by_cgal_off-expected.png new file mode 100644 index 00000000..9be87e75 Binary files /dev/null and b/tests/regression/cgalpngtest/unreadable_by_cgal_off-expected.png differ diff --git a/tests/regression/opencsgtest/unreadable_by_cgal_off-expected.png b/tests/regression/opencsgtest/unreadable_by_cgal_off-expected.png new file mode 100644 index 00000000..b2d7aa47 Binary files /dev/null and b/tests/regression/opencsgtest/unreadable_by_cgal_off-expected.png differ diff --git a/tests/regression/throwntogethertest/unreadable_by_cgal_off-expected.png b/tests/regression/throwntogethertest/unreadable_by_cgal_off-expected.png new file mode 100644 index 00000000..b2d7aa47 Binary files /dev/null and b/tests/regression/throwntogethertest/unreadable_by_cgal_off-expected.png differ