Browse Source

stl file format

Former-commit-id: 6398310d6a33f437f942eace4e10ef32bbae65b8
Alec Jacobson (jalec 11 years ago
parent
commit
404a719d25

+ 1 - 0
RELEASE_HISTORY.txt

@@ -1,3 +1,4 @@
+0.4.4  STL file format support
 0.4.3  ARAP implementation
 0.4.1  Migrated much of the FAST code including extra for Sifakis' 3x3 svd
 0.4.0  Release under MPL2 license

+ 1 - 1
VERSION.txt

@@ -3,4 +3,4 @@
 # Anyone may increment Minor to indicate a small change.
 # Major indicates a large change or large number of changes (upload to website)
 # World indicates a substantial change or release
-0.4.3
+0.4.4

+ 20 - 0
examples/quicklook-mesh/Info.plist

@@ -13,6 +13,7 @@
 			<array>
 				<string>org.mesh</string>
 				<string>org.obj</string>
+				<string>org.stl</string>
 				<string>org.off</string>
 				<string>org.wrl</string>
 			</array>
@@ -102,6 +103,25 @@
 				</array>
 			</dict>
 		</dict>
+		<dict>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.image</string>
+			</array>
+			<key>UTTypeDescription</key>
+			<string>Stereolithography CAD model</string>
+			<key>UTTypeIdentifier</key>
+			<string>org.stl</string>
+			<key>UTTypeReferenceURL</key>
+			<string>http://en.wikipedia.org/wiki/STL_(file_format)</string>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>stl</string>
+				</array>
+			</dict>
+		</dict>
 		<dict>
 			<key>UTTypeConformsTo</key>
 			<array>

+ 4 - 4
examples/quicklook-mesh/Makefile

@@ -4,10 +4,10 @@
 # include the Foundation headers
 #CXX=llvm-g++
 #C=llvm-gcc
-CXX=clang++
-C=clang
-#CXX=clang++-mp-3.4
-#C=clang-mp-3.4
+#CXX=clang++
+#C=clang
+CXX=clang++-mp-3.4
+C=clang-mp-3.4
 CXXFLAGS += -stdlib=libc++ -std=c++11
 
 EIGEN=/opt/local/include/eigen3/

+ 9 - 0
examples/quicklook-mesh/src/render_to_buffer.cpp

@@ -13,6 +13,7 @@ extern "C" {
 #include <igl/material_colors.h>
 #include <igl/pathinfo.h>
 #include <igl/readOBJ.h>
+#include <igl/readSTL.h>
 #include <igl/readWRL.h>
 #include <igl/triangulate.h>
 #include <igl/readOFF.h>
@@ -375,6 +376,14 @@ bool render_to_buffer(
       red(width,height,buffer);
       return false;
     }
+  }else if(ext == "stl")
+  {
+    // Convert extension to lower case
+    if(!igl::readSTL(filename,vV,vF,vN))
+    {
+      red(width,height,buffer);
+      return false;
+    }
   }else
   {
     // Convert extension to lower case

+ 1 - 0
file-formats/index.html

@@ -36,6 +36,7 @@
     <li><i>.poly</i> Piecewise-linear complex. This format comes in many similar but importantly different flavors: 
       <a href="https://www.cs.cmu.edu/~quake/triangle.poly.html">triangle's</a>, <a href="http://tetgen.berlios.de/fformats.poly.html">tetgen's</a>, <a href="http://sparse-meshing.com/svr/0.2.1/format-poly.html">pyramid/SVR's</a></li>
     <li><a href=./rbr.html>.rbr</a> ASCII files for saving state of ReAntTweakBar</li>
+    <li><a href=http://en.wikipedia.org/wiki/STL_(file_format)>.stl</a> 3D Systems' CAD and 3D printing mesh file format. ASCII and binary versions.</li>
     <li><a href=http://en.wikipedia.org/wiki/Truevision_TGA>.tga</a> Truevision TGA or TARGA image file format. IGLLIB supports only very basic reading and writing RGB/RGBA files without colormaps and (unverified) run-length compression.</li>
     <li><a href="./tgf.html">.tgf</a> ASCII files for representing control handle graphs</li>
     <li><a href="http://en.wikipedia.org/wiki/VRML#WRL_File_Format">.wrl</a>

+ 2 - 2
include/igl/draw_mesh.cpp

@@ -89,9 +89,9 @@ IGL_INLINE void igl::draw_mesh(
   {
     assert(F.maxCoeff() < V.rows());
     assert(V.cols() == 3);
-    assert(C.rows() == V.rows() || C.rows() == F.rows()*3 || C.size() == 0);
+    assert(rC == rV || rC == rF || rC == rF*3 || C.size() == 0);
     assert(C.cols() == 3 || C.size() == 0);
-    assert(N.cols() == 3);
+    assert(N.cols() == 3 || N.size() == 0);
     assert(TC.cols() == 2 || TC.size() == 0);
   }
   if(W.size()>0)

+ 1 - 0
include/igl/project.cpp

@@ -117,6 +117,7 @@ template Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> > igl::proj
 template int igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1>, Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> >&);
 template Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&);
 template Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > igl::project<Eigen::Matrix<double, 1, -1, 1, 1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > const&);
+template int igl::project<Eigen::Matrix<double, 1, 3, 1, 1, 3>, Eigen::Matrix<double, 1, 3, 1, 1, 3> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> >&);
 #endif
 
 #endif

+ 19 - 19
include/igl/readOBJ.cpp

@@ -38,7 +38,7 @@ IGL_INLINE bool igl::readOBJ(
   F.clear();
   FTC.clear();
   FN.clear();
-  
+
   // variables an constants to assist parsing the .obj file
   // Constant strings to compare against
   std::string v("v");
@@ -46,15 +46,15 @@ IGL_INLINE bool igl::readOBJ(
   std::string vt("vt");
   std::string f("f");
   std::string tic_tac_toe("#");
-#ifndef LINE_MAX
-#  define LINE_MAX 2048
+#ifndef IGL_LINE_MAX
+#  define IGL_LINE_MAX 2048
 #endif
-  
-  char line[LINE_MAX];
+
+  char line[IGL_LINE_MAX];
   int line_no = 1;
-  while (fgets(line, LINE_MAX, obj_file) != NULL) 
+  while (fgets(line, IGL_LINE_MAX, obj_file) != NULL) 
   {
-    char type[LINE_MAX];
+    char type[IGL_LINE_MAX];
     // Read first word containing type
     if(sscanf(line, "%s",type) == 1)
     {
@@ -123,7 +123,7 @@ IGL_INLINE bool igl::readOBJ(
         std::vector<Index > ftc;
         std::vector<Index > fn;
         // Read each "word" after type
-        char word[LINE_MAX];
+        char word[IGL_LINE_MAX];
         int offset;
         while(sscanf(l,"%s%n",word,&offset) == 1)
         {
@@ -196,10 +196,10 @@ IGL_INLINE bool igl::readOBJ(
     line_no++;
   }
   fclose(obj_file);
-  
+
   assert(F.size() == FN.size());
   assert(F.size() == FTC.size());
-  
+
   return true;
 }
 
@@ -243,7 +243,7 @@ IGL_INLINE bool igl::readOBJ(
       return false;
     }
   }
-  
+
   if(!vFN.empty())
   {
     bool FN_rect = igl::list_to_matrix(vFN,FN);
@@ -253,10 +253,10 @@ IGL_INLINE bool igl::readOBJ(
       return false;
     }
   }
-  
+
   if(!vTC.empty())
   {
-    
+
     bool T_rect = igl::list_to_matrix(vTC,TC);
     if(!T_rect)
     {
@@ -266,7 +266,7 @@ IGL_INLINE bool igl::readOBJ(
   }
   if(!vFTC.empty())
   {
-    
+
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     if(!FTC_rect)
     {
@@ -308,31 +308,31 @@ IGL_INLINE bool igl::readOBJPoly(
     return false;
 
   F = vF;
-  
+
   if(!vN.empty())
   {
     bool VN_rect = igl::list_to_matrix(vN,CN);
     if(!VN_rect)
       return false;
   }
-  
+
   if(!vFN.empty())
   {
     bool FN_rect = igl::list_to_matrix(vFN,FN);
     if(!FN_rect)
       return false;
   }
-  
+
   if(!vTC.empty())
   {
-    
+
     bool T_rect = igl::list_to_matrix(vTC,TC);
     if(!T_rect)
       return false;
   }
   if(!vFTC.empty())
   {
-    
+
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     if(!FTC_rect)
       return false;

+ 212 - 0
include/igl/readSTL.cpp

@@ -0,0 +1,212 @@
+#include "readSTL.h"
+#include "list_to_matrix.h"
+
+#include <iostream>
+template <typename DerivedV, typename DerivedF, typename DerivedN>
+IGL_INLINE bool igl::readSTL(
+  const std::string & filename,
+  Eigen::PlainObjectBase<DerivedV> & V,
+  Eigen::PlainObjectBase<DerivedF> & F,
+  Eigen::PlainObjectBase<DerivedN> & N)
+{
+  using namespace std;
+  vector<vector<typename DerivedV::Scalar> > vV;
+  vector<vector<typename DerivedN::Scalar> > vN;
+  vector<vector<typename DerivedF::Scalar> > vF;
+  if(!readSTL(filename,vV,vF,vN))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vV,V))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vF,F))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vN,N))
+  {
+    return false;
+  }
+  return true;
+}
+
+template <typename TypeV, typename TypeF, typename TypeN>
+IGL_INLINE bool igl::readSTL(
+  const std::string & filename,
+  std::vector<std::vector<TypeV> > & V,
+  std::vector<std::vector<TypeF> > & F,
+  std::vector<std::vector<TypeN> > & N)
+{
+  using namespace std;
+  // Should test for ascii
+
+  // Open file, and check for error
+  FILE * stl_file = fopen(filename.c_str(),"r");
+  if(NULL==stl_file)
+  {
+    fprintf(stderr,"IOError: %s could not be opened...\n",
+            filename.c_str());
+    return false;
+  }
+  V.clear();
+  F.clear();
+  N.clear();
+#ifndef IGL_LINE_MAX
+#  define IGL_LINE_MAX 2048
+#endif
+  char solid[IGL_LINE_MAX];
+  if(fscanf(stl_file,"%s",solid)!=1)
+  {
+    // file too short
+    cerr<<"IOError: "<<filename<<" too short."<<endl;
+    goto close_false;
+  }
+  if(string("solid") == solid)
+  {
+    // Eat file name
+    char name[IGL_LINE_MAX];
+    if(NULL==fgets(name,IGL_LINE_MAX,stl_file))
+    {
+      cerr<<"IOError: "<<filename<<" too short."<<endl;
+      goto close_false;
+    }
+    // ascii
+    while(true)
+    {
+      int ret;
+      char facet[IGL_LINE_MAX],normal[IGL_LINE_MAX];
+      vector<TypeN > n(3);
+      ret = fscanf(stl_file,"%s %s %lg %lg %lg",facet,normal,&n[0],&n[1],&n[2]);
+      if(string("endsolid") == facet)
+      {
+        break;
+      }
+      if(ret != 5 || string("facet") != facet || string("normal") != normal)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (1)."<<endl;
+        goto close_false;
+      }
+      N.push_back(n);
+      char outer[IGL_LINE_MAX], loop[IGL_LINE_MAX];
+      ret = fscanf(stl_file,"%s %s",outer,loop);
+      if(ret != 2 || string("outer") != outer || string("loop") != loop)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (2)."<<endl;
+        goto close_false;
+      }
+      vector<TypeF> f;
+      while(true)
+      {
+        char word[IGL_LINE_MAX];
+        int ret = fscanf(stl_file,"%s",word);
+        if(ret == 1 && string("endloop") == word)
+        {
+          break;
+        }else if(ret == 1 && string("vertex") == word)
+        {
+          vector<TypeV> v(3);
+          int ret = fscanf(stl_file,"%lg %lg %lg",&v[0],&v[1],&v[2]);
+          if(ret != 3)
+          {
+            cerr<<"IOError: "<<filename<<" bad format (3)."<<endl;
+            goto close_false;
+          }
+          f.push_back(V.size());
+          V.push_back(v);
+        }else
+        {
+          cerr<<"IOError: "<<filename<<" bad format (4)."<<endl;
+          goto close_false;
+        }
+      }
+      F.push_back(f);
+      char endfacet[IGL_LINE_MAX];
+      ret = fscanf(stl_file,"%s",endfacet);
+      if(ret != 1 || string("endfacet") != endfacet)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (5)."<<endl;
+        goto close_false;
+      }
+    }
+    // read endfacet
+    fclose(stl_file);
+    goto close_true;
+  }else
+  {
+    // Binary
+    fclose(stl_file);
+    stl_file = fopen(filename.c_str(),"rb");
+    if(NULL==stl_file)
+    {
+      fprintf(stderr,"IOError: %s could not be opened...\n",
+              filename.c_str());
+      return false;
+    }
+    // Read 80 header
+    char header[80];
+    if(fread(header,sizeof(char),80,stl_file)!=80)
+    {
+      cerr<<"IOError: "<<filename<<" bad format (1)."<<endl;
+      goto close_false;
+    }
+    // Read number of triangles
+    unsigned int num_tri;
+    if(fread(&num_tri,sizeof(unsigned int),1,stl_file)!=1)
+    {
+      cerr<<"IOError: "<<filename<<" bad format (2)."<<endl;
+      goto close_false;
+    }
+    V.resize(num_tri*3,vector<TypeV >(3,0));
+    N.resize(num_tri,vector<TypeN >(3,0));
+    F.resize(num_tri,vector<TypeF >(3,0));
+    for(int t = 0;t<num_tri;t++)
+    {
+      // Read normal
+      float n[3];
+      if(fread(n,sizeof(float),3,stl_file)!=3)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (3)."<<endl;
+        goto close_false;
+      }
+      // Read each vertex
+      for(int c = 0;c<3;c++)
+      {
+        F[t][c] = 3*t+c;
+        N[t][c] = n[c];
+        float v[3];
+        if(fread(v,sizeof(float),3,stl_file)!=3)
+        {
+          cerr<<"IOError: "<<filename<<" bad format (4)."<<endl;
+          goto close_false;
+        }
+        V[3*t+c][0] = v[0];
+        V[3*t+c][1] = v[1];
+        V[3*t+c][2] = v[2];
+      }
+      // Read attribute size
+      unsigned short att_count;
+      if(fread(&att_count,sizeof(unsigned short),1,stl_file)!=1)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (5)."<<endl;
+        goto close_false;
+      }
+    }
+    goto close_true;
+  }
+close_false:
+  fclose(stl_file);
+  return false;
+close_true:
+  fclose(stl_file);
+  return true;
+}
+
+#ifndef IGL_HEADER_ONLY
+// Explicit template instanciation
+template bool igl::readSTL<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+#endif

+ 57 - 0
include/igl/readSTL.h

@@ -0,0 +1,57 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// This Source Code Form is subject to the terms of the Mozilla Public License 
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef IGL_READSTL_H
+#define IGL_READSTL_H
+#include "igl_inline.h"
+
+#ifndef IGL_NO_EIGEN
+#  include <Eigen/Core>
+#endif
+#include <string>
+#include <vector>
+
+namespace igl 
+{
+  // Read a mesh from an ascii/binary stl file.
+  //
+  // Templates:
+  //   Scalar  type for positions and vectors (will be read as double and cast
+  //     to Scalar)
+  // Inputs:
+  //   filename path to .obj file
+  // Outputs:
+  //   V  double matrix of vertex positions  #F*3 by 3
+  //   F  index matrix of triangle indices #F by 3
+  //   N  double matrix of vertex positions  #F by 3
+  // Returns true on success, false on errors
+  //
+  // Example:
+  //   bool success = readSTL(filename,temp_V,F,N);
+  //   remove_duplicate_vertices(temp_V,0,V,SVI,SVJ);
+  //   for_each(F.data(),F.data()+F.size(),[&SVJ](int & f){f=SVJ(f);});
+  //   writeOBJ("Downloads/cat.obj",V,F);
+  template <typename DerivedV, typename DerivedF, typename DerivedN>
+  IGL_INLINE bool readSTL(
+    const std::string & filename,
+    Eigen::PlainObjectBase<DerivedV> & V,
+    Eigen::PlainObjectBase<DerivedF> & F,
+    Eigen::PlainObjectBase<DerivedN> & N);
+  template <typename TypeV, typename TypeF, typename TypeN>
+  IGL_INLINE bool readSTL(
+    const std::string & filename,
+    std::vector<std::vector<TypeV> > & V,
+    std::vector<std::vector<TypeF> > & F,
+    std::vector<std::vector<TypeN> > & N);
+}
+
+#ifdef IGL_HEADER_ONLY
+#  include "readSTL.cpp"
+#endif
+
+#endif
+

+ 101 - 0
include/igl/writeSTL.cpp

@@ -0,0 +1,101 @@
+#include "writeSTL.h"
+
+template <typename DerivedV, typename DerivedF, typename DerivedN>
+IGL_INLINE bool igl::writeSTL(
+  const std::string & filename,
+  const Eigen::PlainObjectBase<DerivedV> & V,
+  const Eigen::PlainObjectBase<DerivedF> & F,
+  const Eigen::PlainObjectBase<DerivedN> & N,
+  const bool ascii)
+{
+  using namespace std;
+  assert(N.rows() == 0 || F.rows() == N.rows());
+  if(ascii)
+  {
+    FILE * stl_file = fopen(filename.c_str(),"w");
+    if(stl_file == NULL)
+    {
+      cerr<<"IOError: "<<filename<<" could not be opened for writing."<<endl;
+      return false;
+    }
+    fprintf(stl_file,"solid %s\n",filename.c_str());
+    for(int f = 0;f<F.rows();f++)
+    {
+      fprintf(stl_file,"facet normal ");
+      if(N.rows()>0)
+      {
+        fprintf(stl_file,"%e %e %e\n",
+          (float)N(f,0),
+          (float)N(f,1),
+          (float)N(f,2));
+      }else
+      {
+        fprintf(stl_file,"0 0 0\n");
+      }
+      fprintf(stl_file,"outer loop\n");
+      for(int c = 0;c<F.cols();c++)
+      {
+        fprintf(stl_file,"vertex %e %e %e\n",
+          (float)V(F(f,c),0),
+          (float)V(F(f,c),1),
+          (float)V(F(f,c),2));
+      }
+      fprintf(stl_file,"endloop\n");
+      fprintf(stl_file,"endfacet\n");
+    }
+    fprintf(stl_file,"endsolid %s\n",filename.c_str());
+    fclose(stl_file);
+    return true;
+  }else
+  {
+    FILE * stl_file = fopen(filename.c_str(),"wb");
+    if(stl_file == NULL)
+    {
+      cerr<<"IOError: "<<filename<<" could not be opened for writing."<<endl;
+      return false;
+    }
+    // Write unused 80-char header
+    for(char h = 0;h<80;h++)
+    {
+      fwrite(&h,sizeof(char),1,stl_file);
+    }
+    // Write number of triangles
+    unsigned int num_tri = F.rows();
+    fwrite(&num_tri,sizeof(unsigned int),1,stl_file);
+    assert(F.cols() == 3);
+    // Write each triangle
+    for(int f = 0;f<F.rows();f++)
+    {
+      vector<float> n(3,0);
+      if(N.rows() > 0)
+      {
+        n[0] = N(f,0);
+        n[1] = N(f,1);
+        n[2] = N(f,2);
+      }
+      fwrite(&n[0],sizeof(float),3,stl_file);
+      for(int c = 0;c<3;c++)
+      {
+        vector<float> v(3);
+        v[0] = V(F(f,c),0);
+        v[1] = V(F(f,c),1);
+        v[2] = V(F(f,c),2);
+        fwrite(&v[0],sizeof(float),3,stl_file);
+      }
+      unsigned short att_count = 0;
+      fwrite(&att_count,sizeof(unsigned short),1,stl_file);
+    }
+    fclose(stl_file);
+    return true;
+  }
+}
+
+template <typename DerivedV, typename DerivedF>
+IGL_INLINE bool igl::writeSTL(
+  const std::string & filename,
+  const Eigen::PlainObjectBase<DerivedV> & V,
+  const Eigen::PlainObjectBase<DerivedF> & F,
+  const bool ascii)
+{
+  return writeSTL(filename,V,F, Eigen::PlainObjectBase<DerivedV>(), ascii);
+}

+ 52 - 0
include/igl/writeSTL.h

@@ -0,0 +1,52 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// This Source Code Form is subject to the terms of the Mozilla Public License 
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef IGL_WRITESTL_H
+#define IGL_WRITESTL_H
+#include "igl_inline.h"
+
+#ifndef IGL_NO_EIGEN
+#  include <Eigen/Core>
+#endif
+#include <string>
+#include <vector>
+
+namespace igl 
+{
+  // Write a mesh to an stl file.
+  //
+  // Templates:
+  //   Scalar  type for positions and vectors (will be read as double and cast
+  //     to Scalar)
+  // Inputs:
+  //   filename path to .obj file
+  //   V  double matrix of vertex positions  #F*3 by 3
+  //   F  index matrix of triangle indices #F by 3
+  //   N  double matrix of vertex positions  #F by 3
+  //   asci  write ascii file {true}
+  // Returns true on success, false on errors
+  //
+  template <typename DerivedV, typename DerivedF, typename DerivedN>
+  IGL_INLINE bool writeSTL(
+    const std::string & filename,
+    const Eigen::PlainObjectBase<DerivedV> & V,
+    const Eigen::PlainObjectBase<DerivedF> & F,
+    const Eigen::PlainObjectBase<DerivedN> & N,
+    const bool ascii=true);
+  template <typename DerivedV, typename DerivedF>
+  IGL_INLINE bool writeSTL(
+    const std::string & filename,
+    const Eigen::PlainObjectBase<DerivedV> & V,
+    const Eigen::PlainObjectBase<DerivedF> & F,
+    const bool ascii=true);
+}
+
+#ifdef IGL_HEADER_ONLY
+#  include "writeSTL.cpp"
+#endif
+
+#endif

+ 9 - 3
matlab-to-eigen.html

@@ -6,7 +6,7 @@
     <table>
       <tr class="header">
         <th>MATLAB</th>
-        <th>Eigen</th>
+        <th>Eigen with libigl</th>
         <th>Notes</th>
       </tr>
 
@@ -202,13 +202,19 @@
       </tr>
 
       <tr class=d0>
-        <td><pre><code>B = IM(A)</code></pre></td>
+        <td><pre><code>B = IM(A)
+
+A = IM(A);
+  </code></pre></td>
         <td><pre><code>B = A.unaryExpr(bind1st(mem_fun( 
   static_cast&lt;VectorXi::Scalar&amp;(VectorXi::*)(VectorXi::Index)&gt;
   (&amp;VectorXi::operator())), &amp;IM)).eval();
+
+for_each(A.data(),A.data()+A.size(),[&amp;IM](int &amp; a){a=IM(a);});
   </code></pre></td>
         <td>Where IM is an "index map" column vector and A is an arbitrary
-        matrix. The <code>.eval()</code> is not necessary if A != B</td>
+        matrix. The <code>.eval()</code> is not necessary if A != B, but then
+        the second option should be used.</td>
       </tr>
 
       <tr class=d1>