The points read with stlread are different from when the same points are saved with stlwrite and read again.
9 views (last 30 days)
Show older comments
As written in the title, after reading the stl file with stlread, I saved the points and connectivityList as variables, then recreated the stl file using the stlwrite function.
Obviously, points and connectivityList used the same data, but the points read from the newly created stl file differed from the previously used variable in the fractional part.
Is there any way to solve these problems?
1 Comment
Answers (2)
DGM
on 3 Jul 2025
Edited: DGM
on 23 Jul 2025
When writing to an STL from vertex data generated in the workspace, you're losing precision. Your vertex data in memory is represented as double-precision (64b) float. An STL file is capable of nominally 32b float.
The question isn't about the loss of precision when writing fresh data to a file, though. It's about transcoding an existing file without deliberate change. We should expect this workflow to be 32b -> 64b -> 32b, with no obvious loss of precision. This is where the "nominally 32b" subtlety comes into play.
We can expect binary encoded STL files to transcode exactly back to binary STL files, but if we're dealing with an ASCII-encoded STL at either end, it depends on the encoder that was used.
% let's generate some F,V data
unzip holypringle.stl.zip
T = stlread('holypringle.stl');
[F V] = t2fv(T);
% let's say it's a large object making full use
% of the resolution of 64b float
V = 1E5*(V + randn(size(V)));
% cram it back in a triangulation object
T = triangulation(F,V);
% a binary STL has only 32b of precision
%stlwrite(T,'testbin.stl','binary') % built-in
stlWrite('testbin.stl',F,V,'mode','binary') % FEX #20922 or #51200
% an ascii STL typically has about 9 digits of precision.
% this is nominally comparable to 32b float,
% but the exact number and formatting depends on the encoder
% this variability means that we might not be losing the exact
% same amount of precision depending on the encoder we used.
%stlwrite(T,'testascii.stl','text') % built-in
stlWrite('testascii.stl',F,V,'mode','ascii') % FEX #20922 or #51200
% compare the error between the written data and the 64b data in memory
% we should expect these errors to be unavoidable, since we're losing precision.
% we should expect these errors to be similar in magnitude, but not necessarily equal.
Tb = stlread('testbin.stl');
Ta = stlread('testascii.stl');
immse(Tb.Points,V)
immse(Ta.Points,V)
% compare the error between the two files themselves (both nominally 32b float)
% this is the error we might see when we recreate a file using a different encoding type.
immse(Tb.Points,Ta.Points)
I have arranged this example such that there are two encoders that can optionally be used. Both encoders will write the exact same data when encoding as binary, but their text representations vary slightly when ASCII mode is used. If the encoder casts the vertex data as single-precision float and/or writes enough digits to avoid truncation, the ASCII mode representations should be as consistent as the binary encodings, but not all encoders do. I've seen encoders that use as few as 5 significant digits. (EDIT: I've since found encoders using narrow fixed-point formats in ASCII mode, so I guess there can be completely lossy cases. :D)
The built-in encoder appears to use an ASCII number formatting of '%.9g' (9 sig figs). Consequently, it will produce binary and ASCII STL files which match exactly when decoded. On the other hand, the encoder from #20922/51200 does not. If you open the encoder and change every instance of '%.7E' to '%.8E' (or '%.9g'), it will write ASCII encodings which decode to match the binary encodings. That's the difference that one significant digit makes.
Why do some encoders truncate to fewer digits than is necessary for lossless representation of 32b float? I'm sure some cases are just mistakes, but there's a significant incentive to keep file size under control, even if it comes at the loss of a little bit of information. The STL format is going on 40 years old. ASCII representations can be large, and storage/transport wasn't always as cheap as it is now. It's up to you to decide if you want to accept the reduced precision that some encoders use.
So error can happen in encoding, what about decoding?
I've only focused on the encoders, but not every change happens during encoding. However, unless we have direct access to the source data that was encoded into the file before MATLAB reads it, we're blind to any error that happens during the initial decoding step. It's not suggested by the question, but let's pretend we had particular expectations that extend beyond MATLAB's awareness.
Consider a hypothetical scenario. Say you have a CAD tool that encodes ASCII STL files with 6SF (OpenSCAD only uses 6). You create a cube with vertices equidistant from the the origin, [0.3 0.3 0.3], etc.. You encode this into an ASCII STL, and you open the file in a text editor and find facet blocks which exactly record your intended dimensions:
vertex 3.00000E-01 3.00000E-01 3.00000E-01
... and so on. Now you go to read and write the file using the robust built-in tools that I've already suggested have the highest fidelity of those available. The new ASCII file should be a lossless representation of what was in memory, right (well, down to single-precision)?. What will we find in the new STL file?
vertex 3.00000012E-01 3.00000012E-01 3.00000012E-01
That's not the same as the original file, but this didn't happen during encoding. It happened during decoding. Remember that with ASCII-encoded files, we're also doing a base change. We can't store 0.3 or 0.300000 in a single-precision float exactly to begin with. The original encoder simply fooled us into thinking that we could.
OP can't elaborate, and I'm inclined to be overly broad in reference answers to dead questions, so I figured I'd add this extra bit of doubt to the back of everyone's minds. Use the built-in STL tools. Use binary encodings. You should be fine.
A note on gross errors caused by comparing dissimilarly-formatted data
Bear in mind also that not all decoders will return the vertex list in the exact same order or format, even if there is no loss of information. That means that you can potentially wind up with huge differences if you try to compare the vertex data. To understand why, it helps to know that an STL file does not store data by referencing a global vertex list. Each facet in the file is a direct representation of the numerical position of its vertices, not a set of indices into a vertex list. That means that each vertex is described redundantly by each of its neighboring facets.
The decoder should discard the redundant vertex entries and remap the faces to match the pruned vertex list -- but many decoders don't. FEX #22409, #29906 (and others) don't prune the vertex list at all, so you can't even compare them directly without pruning it yourself. On the other hand, the readers from FEX #51200 will prune the vertex list, but pruning results in the list being reordered. There is no loss of information, and the vertex list length does not change; it's just reordered due to the default sorting behavior of unique() being used.
0 Comments
akshatsood
on 4 Sep 2023
Edited: akshatsood
on 4 Sep 2023
Hi Jungwu,
I understand that you are experiencing differences in the fractional part of the coordinates when reading and writing an STL file using stlread and stlwrite functions in MATLAB. As per my understanding, it may be due to the floating-point precision used during the process. This can happen due to the inherent limitations of representing real numbers in computers.
One possible workaround involves using the round function to set the coordinates to desired precision followed by specifying the file format parameter while leveraging the stlwrite function. To demonstrate the workaround, an example script can be opened up by entering the following command in the MATLAB command window
>> openExample('matlab/ReadTriangulationFromSTLTextFileExample')
Adjust the precision variable as per your requirements. By doing this, you can ensure that the fractional part of the coordinates is consistent when reading and writing the STL file. Additionally, you can mention the file format as 'text' while calling stlwrite instead of the default binary to mitigate the differences.
TR = stlread('tristltext.stl');
triangulation with properties:
Points: [6×3 double]
ConnectivityList: [4×3 double]
% set the desired precision for the floating-point numbers
precision = 6;
points = round(TR.Points,precision);
% create a new triangulation object
roundedTR = triangulation(TR.ConnectivityList,points);
% write the rounded data to a new STL file with the file format as 'text'
stlwrite(roundedTR, 'new_file.stl', 'text');
% validate that data is consistent using stlread and stlwrite
newTR = stlread('new_file.stl');
disp(newTR.Points - TR.Points);
I hope this helps.
0 Comments
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!