PHP + STL Files

Illustrates a bounding box of a pyramidA while back, I had the need to interpret an STL file (stereolithography), which is the general filetype of choice when dealing with rapid prototyping / 3D printing. My side business at the time had released a free 3D model viewer for the iPad, and we wanted to give estimates on prototyping costs whenever someone uploaded one of their own models. I couldn’t find any reasonable STL parsers that would work server-side, so I decided to make my own. The process of doing that, and the resulting files, are described below.

Stereolithography files are used by a slew of 3D modeling programs. There are two main types of STL file, ASCII and Binary. Most of the STL files I had interacted with in my rapid prototyping days were binary, so that’s what I enabled my PHP engine to parse. Binary STL files have the following format:

UINT8[80] – Header
UINT32 – Number of triangles

foreach triangle
REAL32[3] – Normal vector
REAL32[3] – Vertex 1
REAL32[3] – Vertex 2
REAL32[3] – Vertex 3
UINT16 – Attribute byte count
end

In order to offer the user an estimate on rapid prototyping without bogging down the php code by computing the exact volume of the part, I decided that using a bounding box approach would work fine. By stepping through each triangle in the file, and each vertex in each triangle, I only had to check if the X, Y and Z values of each vertex were larger (absolute value) than the preceding maximum value. Once I had to overall volume of the part, I could apply some calculations for our cost of material to offer the user an estimate on printing. Now, on to the code:

<?php

$x_max = 0;
$y_max = 0;
$z_max = 0;
$x_min = 0;
$y_min = 0;
$z_min = 0;

First thing first, we want to set all the minimum and maximum values for each axis to zero. This way, a larger positive/negative value will always overwrite the initialized value of 0.

$code = $_GET['code'];
$filepath = "/apps/mod3d/uploads/stl/".$code;
$fp = fopen($filepath, "rb");
$section = file_get_contents($filepath, NULL, NULL, 0, 79);
fseek($fp, 80);
$data = fread($fp, 4);
$numOfFacets = unpack("I", $data);

The $code variable holds an identifying number passed in from a previous file. This is appended to the filepath to give the correct location of the file in question. The path is opened, and the $section variable gets the first 80 characters of the file. This part isn’t necessary for the actual calculation, but it does give you the file name as part of the header. Next, we seek to the 80th byte in the file and read the next 4 characters into the $data variable. Lastly, we unpack that data into the $numOfFacets variable. The ‘I’ format variable indicates that the $data is in unsigned integer format. Now we have the number of triangles that make up the 3D model, which will become the limiter for our logic loop, which comes next:

for ($i = 0; $i < $numOfFacets[1]; $i++){
	//Start Normal Vector
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$normalVectorsX[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$normalVectorsY[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$normalVectorsZ[$i] = $hold[1];
	//End Normal Vector
	//Start Vertex1
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex1X[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex1Y[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex1Z[$i] = $hold[1];
	//End Vertex1
	//Start Vertex2
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex2X[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex2Y[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex2Z[$i] = $hold[1];
	//End Vertex2
	//Start Vertex3
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex3X[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex3Y[$i] = $hold[1];
	$data = fread($fp, 4);
	$hold = unpack("f", $data);
	$vertex3Z[$i] = $hold[1];
	//End Vertex3
	//Attribute Byte Count
	$data = fread($fp, 2);
	$hold = unpack("S", $data);
	$abc[$i] = $hold[1];

	$x_vals = array($vertex1X[$i], $vertex2X[$i], $vertex3X[$i]);
	$y_vals = array($vertex1Y[$i], $vertex2Y[$i], $vertex3Y[$i]);
	$z_vals = array($vertex1Z[$i], $vertex2Z[$i], $vertex3Z[$i]);

The first chunk of the for loop code reads and unpacks the X, Y, and Z points of the normal vector. This normal vector is the vector orthogonal to the face of the triangle we’re currently operating on. It would be useful if you’re trying to display the model, but not necessary for determining volume. The next portion (after the //End Normal Vector comment) grabs the X, Y, and Z point values for each of the three vertices that make up the triangle. The last portion grabs the attribute byte count, which is useless for our purposes. We then put all the X point, Y point and Z point values into separate arrays. The last portion of the for() loop looks at these arrays to determine if the point is a maximum:

	if(max($x_vals) > $x_max) {
		$x_max = max($x_vals);
	}
	if(max($y_vals) > $y_max) {
		$y_max = max($y_vals);
	}
	if(max($z_vals) > $z_max) {
		$z_max = max($z_vals);
	}
	if(min($x_vals) < $x_min) {
		$x_min = min($x_vals);
	}
	if(min($y_vals) < $y_min) {
		$y_min = min($y_vals);
	}
	if(min($z_vals) < $z_min) {
		$z_min = min($z_vals);
	}

This process repeats over and over for each of the triangles in the file. When it’s all said and done, x_max/x_min will hold the minimum and maximum values of the 3D model along the X axis. The same goes for the Y and Z axes. Lastly, we can get the actual width of the model in each axis direction by subtracting the minimum value from the maximum. Then it’s a simple matter of multiplying all the dimensions of our bounding box to get a volume estimate.

$x_dim = $x_max - $x_min;
$y_dim = $y_max - $y_min;
$z_dim = $z_max - $z_min;

$volume = $x_dim*$y_dim*$z_dim;

Note that this will almost always be an overestimate of volume, unless the item is in fact a box. Hollow areas, large extrusions off the main portion of the model, etc, will change how much the estimated volume approximates the actual volume. But it’s at least a starting point.

Hopefully this exercise will help you understand how STL files store data about 3D models, and how PHP can be used to parse that data. Feel free to use the attached PHP file for your own purposes, but I’d appreciate you leaving my developer info in the file, and an email/comment letting me know that you found it helpful.

Right-click and Save File As… : Link to stlvolume source

Spread the word. Share this post!

17 Comments

  1. Pavan

    Reply

    very nice analyse and work.

    stlvolume.php download link is not working, can you please provide an alternate link

  2. Abraham

    Reply

    Hi Andrew ,

    I am using your excellent script but i am having some problem achieving my goals perhaps you can give me some advice .
    1) I will like to re triangulate the stl file to add in equilateral triangles with a constant edge size (divide the original triangles if necessary ).

    2) i will like to unfold the result to 2d coordinates not necessarily for paper craft i just need to flatten the end 3d stl with equilateral triangles .

    I really don’t have any idea of where to start i am completely new to stl.

    Thanks again for your script.

  3. Will

    Reply

    Hi,

    Great script, however, can you tell me what volume measurement it ends up with? My small models get results in the 100,000’s. I’m thinking that the results are in milimeters cubed rather than cm3? Is that correct?

    Cheers,
    – Will

    • Reply

      Hey Will,

      The ‘units’ for volume will actually depend on what your STL file is generated as. The script has no knowledge of units, it only calculates the absolute difference between maximum and minimum points in each of the three axes. If you generated your STL and told it to export in terms of mm, the script would return the volume in mm^3. Likewise, if you used inches, the script output would be in in^3.

      If I recall correctly, when I used PRO/Engineer, cm^3 wasn’t even an option, only inches and mm. You may have different settings on your programs, but I’d bet your part is in mm, as that’s pretty standard. You could always verify this by creating an STL of a cube with 1mm sides and see if the output of the script is 1mm^3. That would tell you what your program is using as far as units are concerned.

      Glad you found it useful,

      MD Schmidt

  4. Peter

    Reply

    Hi Michael,

    Thanks for posting this script it has been helpful.

    Can you tell me if there is a limitation on the size of a file in mb that it can read?
    This is because I have used this script for a quoting system and can only upload and read a 4mb file anything larger it will stop working or hang.
    So I’m not sure if it’s the script or some other limitation with the server or php.

    Any help would be appreciated.

    Thanks,
    Peter

    • Reply

      Hey Peter, the script doesn’t have a size limitation, this is likely a server limitation. If I recall correctly, the php configuration file on your server should hold some information about max file upload size. The script probably isn’t optimized for large files, but I wouldn’t think it would outright stop working, especially if there is a concrete limit (3.9 MB works, 4.1 does not). That would be indicative of a server limitation.

      HTH,

      Michael

  5. Alex

    Reply

    Great script!

    Is there any way to calculate the TRUE volume of the shape?

    My model has hollow bits inside and it seems that this script only calculates the volume of the bounding box. Volume = x*y*z

    I assume it would be more complicated?

    • Reply

      Hey Alex, yes, it would be more complicated. As it stands we just find the max/min points on each axis to calculate the volume. To account for hollow areas within the volume, I imagine there would be some looped integral you’d have to do over the shape in order to get an idea of what’s closed and what’s open. Then you’re approaching a full 3D model. There might be PHP libraries out there that can do it, or perhaps python. The OpenGL toolkit may also have the ability to render 3d models from stl.

  6. angelo

    Reply

    i like this script but can you tell me what i do to use this?
    can i put on a file html?
    thanks a lot

  7. jothikannan

    Reply

    Hi,

    thanks for the excellent script, this is what i want, how i link the file to `$filepath` ?

    i used like below :

    $filepath= “pr2_head_pan.stl”;

    where the stl file and php file is saved in the same directory, but am getting errors

  8. Peter

    Reply

    Thank you very much for writing this PHP which is the one I am looking for. When I run the program, I print the result of data and numOfFacets. The results are shown below:

    data ��
    numOfFacets

    It seems it is not the correct result of the data and we get nothing of the numOfFacets. Would you mind to let me know that is the problem of it? Thank you.

    PS: I am sure I input the binary STL file and the PHP could read the STL file.

    Peter

  9. Damian

    Reply

    I found a bug. If model is not centered it counts size from 0,0 to end of model. And it should ignore space where there are no objects. Hope you understand and fix it because it is biggest problem I have.

    • castleseven

      Reply

      Correct – this is simple bounding box code. Supposed to be a starting point for more advanced interactions with the 3D stl data. I’m sure there are plenty of libraries released in the last 8 years that will do what you want.

Leave Comment

Your email address will not be published. Required fields are marked *