stl_stats


This is a  small C++ program to iterate over a STL file and to print out a few things:

  1. Max/Min for X, Y and Z –> Bounding Box
  2. Number of layers
  3. “Useful” layers : given a minimum height, if some layers on Z axis are too small it performs a merge. Loosely speaking, if the ratio between “useful” layers and  real layers of the model is too low, it means that the printing of this given model can potentially be not nice in between some layers (it is a sampling issue).
    • Example of problematic details on the bottom of Darth Vader Pen Cup:

NB: For reading the STL file, it is a simple copy-paste from the really good OpenSCAD.

Usage:

stl_stats filename.stl [minLayerHeight]

Dependencies:

boost_regex and boost_system

Output examples:

 

./stl_stats  ../../../Téléchargements/20mm-box.stl
BinarySTL = 0
Layer 0: Axis Z 0
Layer 1: Axis Z 10
—> New Layer height = 10
Number of layers 2
Useful layers 1 (small bug actually it is 2)
Face Number = 12
XMin = -10 XMax = 10, Distance = 20
YMin = -10 YMax = 10, Distance = 20
ZMin = 0 ZMax = 10, Distance = 10
Minimum layer height = 0.3
Max number of layers = 33.3333

./stl_stats  ../../../Téléchargements/darth_vader_pen_cup.stl

Layer 1142: Axis Z 80.001
Previous layer too thin, useless
Number of layers 1143
Useful layers 115
Face Number = 5406
XMin = -36.001 XMax = 54, Distance = 90.001
YMin = -49 YMax = 41.001, Distance = 90.001
ZMin = -0.001 ZMax = 80.001, Distance = 80.002
Minimum layer height = 0.3
Max number of layers = 266.673

./stl_stats  ../../../Téléchargements/darth_vader_pen_cup.stl 0.1

Layer 1142: Axis Z 80.001
Previous layer too thin, useless
Number of layers 1143
Useful layers 214
Face Number = 5406
XMin = -36.001 XMax = 54, Distance = 90.001
YMin = -49 YMax = 41.001, Distance = 90.001
ZMin = -0.001 ZMax = 80.001, Distance = 80.002
Minimum layer height = 0.1
Max number of layers = 800.02

 

Source code:

#include <iostream>
#include <fstream>
#include <sstream>

#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>

#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#include <boost/assign/std/vector.hpp>
using namespace boost::assign; // bring 'operator+=()' into scope

#include <boost/detail/endian.hpp>
#include <boost/cstdint.hpp>

#include <map>

//CODE COMING FROM OPENSCAD

#define STL_FACET_NUMBYTES 4*3*4+2
// as there is no 'float32_t' standard, we assume the systems 'float'
// is a 'binary32' aka 'single' standard IEEE 32-bit floating point type
union stl_facet {
	uint8_t data8[ STL_FACET_NUMBYTES ];
	uint32_t data32[4*3];
	struct facet_data {
	  float i, j, k;
	  float x1, y1, z1;
	  float x2, y2, z2;
	  float x3, y3, z3;
	  uint16_t attribute_byte_count;
	} data;
};

inline void uint32_byte_swap( uint32_t &x )
{
#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 3
	x = __builtin_bswap32( x );
#elif defined(__clang__)
	x = __builtin_bswap32( x );
#elif defined(_MSC_VER)
	x = _byteswap_ulong( x );
#else
	uint32_t b1 = ( 0x000000FF & x ) << 24;
	uint32_t b2 = ( 0x0000FF00 & x ) << 8;
	uint32_t b3 = ( 0x00FF0000 & x ) >> 8;
	uint32_t b4 = ( 0xFF000000 & x ) >> 24;
	x = b1 | b2 | b3 | b4;
#endif
}

inline void read_stl_facet( std::ifstream &f, stl_facet &facet )
{
	f.read( (char*)facet.data8, STL_FACET_NUMBYTES );
#ifdef BOOST_BIG_ENDIAN
	for ( int i = 0; i < 12; i++ ) {
		uint32_byte_swap( facet.data32[ i ] );
	}
	// we ignore attribute byte count
#endif
}

inline int readStlFile(const char *filename, double minLayerHeight = 0.3 ) {
  // Open file and position at the end
  std::ifstream f(filename, std::ios::in | std::ios::binary | std::ios::ate);
  if (!f.good()) {
    std::cerr << "WARNING: Can't open stl file:" <<  filename << std::endl;
    return -1;
  }

  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);
  uint32_t facenum = 0;
  if (f.good() && !f.eof()) {
    f.read((char *)&facenum, sizeof(uint32_t));
#ifdef BOOST_BIG_ENDIAN
    uint32_byte_swap( facenum );
#endif
    if (file_size ==  static_cast<std::streamoff>(80 + 4 + 50*facenum)) {
      binary = true;
    }
  }
  f.seekg(0);

  //START OF MY STAT STUFF
  std::cout << "BinarySTL = " << binary << std::endl;

  double XMin = 1000000000.0, XMax = -10000000000.0;
  double YMin = 1000000000.0, YMax = -10000000000.0;
  double ZMin = 1000000000.0, ZMax = -10000000000.0;

  facenum = 0;

  std::map<double, uint32_t> stlLayers; //number of layers

  //END OF MY STAT STUFF

  char data[5];
  f.read(data, 5);
  if (!binary && !f.eof() && f.good() && !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<double>(results[v+1]);
	  }
	}
	catch (const boost::bad_lexical_cast &blc) {
	  std::cout << "WARNING: Can't parse vertex line:" <<  line;
	  i = 10;
	  continue;
	}
	if (++i == 3) { //CREATE TRIANGLE
	  /*	  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]); */

	  facenum++;

	  //min,max
	  for(unsigned j = 0 ; j < 3 ; j ++ ) {
	    if ( XMin > vdata[j][0] ) {
	      XMin = vdata[j][0];
	    }
	    if ( XMax < vdata[j][0] ) {
	      XMax = vdata[j][0];
	    }
	    if ( YMin > vdata[j][1] ) {
	      YMin = vdata[j][1];
	    }
	    if ( YMax < vdata[j][1] ) {
	      YMax = vdata[j][1];
	    }
	    if ( ZMin > vdata[j][2] ) {
	      ZMin = vdata[j][2];
	    }
	    if ( ZMax < vdata[j][2] ) {
	      ZMax = vdata[j][2];
	    }
	  }

	  //stl layers
	  for(unsigned j = 0 ; j < 3 ; j ++ ) {
	    stlLayers[ vdata[j][2] ] = 1;
	  }

	}
      }
    }
  }
  else if (binary && !f.eof() && f.good())
    {
      f.ignore(80-5+4);
      while (1) {
	stl_facet facet;
	read_stl_facet( f, facet );
	if (f.eof()) break;

	//CREATE TRIANGLE

	facenum++;

// 	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);

	//min,max
	if ( XMin > facet.data.x1 ) {
	  XMin = facet.data.x1;
	}
	if ( XMin > facet.data.x2 ) {
	  XMin = facet.data.x2;
	}
	if ( XMin > facet.data.x3 ) {
	  XMin = facet.data.x3;
	}
	if ( XMax < facet.data.x1 ) {
	  XMax = facet.data.x1;
	}
	if ( XMax < facet.data.x2 ) {
	  XMax = facet.data.x2;
	}
	if ( XMax < facet.data.x3 ) {
	  XMax = facet.data.x3;
	}

	if ( YMin > facet.data.y1 ) {
	  YMin = facet.data.y1;
	}
	if ( YMin > facet.data.y2 ) {
	  YMin = facet.data.y2;
	}
	if ( YMin > facet.data.y3 ) {
	  YMin = facet.data.y3;
	}
	if ( YMax < facet.data.y1 ) {
	  YMax = facet.data.y1;
	}
	if ( YMax < facet.data.y2 ) {
	  YMax = facet.data.y2;
	}
	if ( YMax < facet.data.y3 ) {
	  YMax = facet.data.y3;
	}

	if ( ZMin > facet.data.z1 ) {
	  ZMin = facet.data.z1;
	}
	if ( ZMin > facet.data.z2 ) {
	  ZMin = facet.data.z2;
	}
	if ( ZMin > facet.data.z3 ) {
	  ZMin = facet.data.z3;
	}
	if ( ZMax < facet.data.z1 ) {
	  ZMax = facet.data.z1;
	}
	if ( ZMax < facet.data.z2 ) {
	  ZMax = facet.data.z2;
	}
	if ( ZMax < facet.data.z3 ) {
	  ZMax = facet.data.z3;
	}

	//stl layers
	stlLayers[ facet.data.z1 ] = 1;
	stlLayers[ facet.data.z2 ] = 1;
	stlLayers[ facet.data.z3 ] = 1;

      }
    }

  std::map<double, uint32_t>::iterator stlLayer_it;
  uint32_t layers = 0, usefulLayers = 0;
  double previousLayer = -10000000.0;
  for( std::map<double, uint32_t>::iterator it = stlLayers.begin();
       it != stlLayers.end();
       ++ it ) {
    std::cout << "Layer " << layers << ": Axis Z " << it->first << std::endl;
    if ( layers > 0 ) {
      if ( minLayerHeight > (it->first - previousLayer) )  {
	std::cout << "Previous layer too thin, useless" << std::endl;
      }
      else {
	usefulLayers++;
	std::cout << "              ---> New Layer height = " << (it->first - previousLayer) << std::endl;
	previousLayer = it->first;
      }
    }
    else {
      previousLayer = it->first;
    }
    layers ++;
  }
  std::cout << "Number of layers " << layers << std::endl;
  std::cout << "Useful layers " << usefulLayers << std::endl;

  std::cout << "Face Number = " << facenum << std::endl;
  std::cout << "XMin = " << XMin << " XMax = " << XMax << ", Distance = " << (XMax - XMin) << std::endl;
  std::cout << "YMin = " << YMin << " YMax = " << YMax << ", Distance = " << (YMax - YMin) << std::endl;
  std::cout << "ZMin = " << ZMin << " ZMax = " << ZMax << ", Distance = " << (ZMax - ZMin) << std::endl;

  std::cout << "Minimum layer height = " << minLayerHeight << std::endl;
  std::cout << "Max number of layers = " << ((ZMax - ZMin)/minLayerHeight) << std::endl;

  return 0;
}

static int usage() {

  std::cout << "stl_stats filename.stl [minLayerHeight]" << std::endl;

  return -1;
}

int main(int argc, char **argv) {

  if ( argc < 2 || argc > 3 ) {
    return usage();
  }

  const char *filename = argv[1];
  double minLayerHeight = 0.3;
  if ( argc == 3 ) {
    minLayerHeight = atof(argv[2]);
  }

  return readStlFile(filename,minLayerHeight);

}

syntax highlighted by Code2HTML, v. 0.9.1

Comments are closed.