stl.cc 9.93 KB
Newer Older
1
2
3
4
#include "stl.hh"

#include <cstddef>
#include <fstream>
5
#include <iostream>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sstream>

/*
    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
 */

namespace polymesh
{
23
template <class ScalarT>
24
void write_stl_binary(std::string const& filename, vertex_attribute<std::array<ScalarT, 3>> const& position, face_attribute<std::array<ScalarT, 3>> const* normals)
25
26
{
    std::ofstream file(filename, std::ios_base::binary);
27
    write_stl_binary(file, position, normals);
28
}
29
30

template <class ScalarT>
31
void write_stl_binary(std::ostream& out, vertex_attribute<std::array<ScalarT, 3>> const& position, face_attribute<std::array<ScalarT, 3>> const* normals)
32
{
33
34
    auto const& mesh = position.mesh();

35
36
37
38
    char header[80] = {};
    uint32_t n_triangles = mesh.faces().size();

    out.write(header, sizeof(header));
39
    out.write((char const*)&n_triangles, sizeof(n_triangles));
40
41
42
43

    for (auto f : mesh.faces())
    {
        auto n = f[normals];
44
        out.write((char const*)&n, sizeof(n));
45
46
47
48
49
50
51
52
53
54
55

        auto cnt = 0;
        for (auto v : f.vertices())
        {
            if (cnt >= 3)
            {
                std::cerr << "STL only supports triangles" << std::endl;
                break;
            }

            auto p = position[v];
56
            out.write((char const*)&p, sizeof(p));
57
58
59
60
61

            ++cnt;
        }

        uint16_t attr_cnt = 0;
62
        out.write((char const*)&attr_cnt, sizeof(attr_cnt));
63
64
65
    }
}

66
template <class ScalarT>
67
bool read_stl(const std::string& filename, Mesh& mesh, vertex_attribute<std::array<ScalarT, 3>>& position, face_attribute<std::array<ScalarT, 3>>* normals)
68
69
70
71
72
{
    std::ifstream file(filename);
    if (!file.good())
        return false;

73
74
75
76
77
78
79
80
81
82
83
    if (is_ascii_stl(file))
    {
        return read_stl_ascii(file, mesh, position, normals);
    }
    else
    {
        // Windows interprets binary files differently from ascii which messes with the parsing.
        // Therefore we create a new stream in binary mode.
        file = std::ifstream(filename, std::ios::binary);
        return read_stl_binary(file, mesh, position, normals);
    }
84
85
}

86
template <class ScalarT>
87
bool read_stl(std::istream& input, Mesh& mesh, vertex_attribute<std::array<ScalarT, 3>>& position, face_attribute<std::array<ScalarT, 3>>* normals)
88
89
90
91
{
    return is_ascii_stl(input) ? read_stl_ascii(input, mesh, position, normals) : read_stl_binary(input, mesh, position, normals);
}

92
template <class ScalarT>
93
bool read_stl_binary(std::istream& input, Mesh& mesh, vertex_attribute<std::array<ScalarT, 3>>& position, face_attribute<std::array<ScalarT, 3>>* normals)
94
95
96
{
    mesh.clear();

Philip Trettner's avatar
Philip Trettner committed
97
    size_t savp = input.tellg();
Philip Trettner's avatar
Philip Trettner committed
98
    input.seekg(0, std::ios_base::end);
Philip Trettner's avatar
Philip Trettner committed
99
    size_t fs_real = input.tellg();
Philip Trettner's avatar
Philip Trettner committed
100
101
    input.seekg(savp, std::ios_base::beg);

102
103
104
105
106
107
108
109
110
111
    char header[80];
    input.read(header, sizeof(header));

    // if (header[0] == 's' && header[1] == 'o' && header[2] == 'l' && header[3] == 'i' && header[4] == 'd')
    // {
    //     std::cerr << "ASCII STL is not supported" << std::endl;
    //     return false;
    // }

    uint32_t n_triangles;
112
    input.read(reinterpret_cast<char*>(&n_triangles), sizeof(n_triangles));
113

114
115
    // note: binary stl always stores 32bit floats
    size_t fs_expect = 80 + sizeof(n_triangles) + n_triangles * (sizeof(std::array<float, 3>) * 4 + sizeof(uint16_t));
Philip Trettner's avatar
Philip Trettner committed
116
117
118
119
120
121
    if (fs_expect != fs_real)
    {
        std::cerr << "Expected file size mismatch: " << fs_expect << " vs " << fs_real << " bytes (file corrupt or wrong format?)" << std::endl;
        return false;
    }

122
123
124
125
    mesh.faces().reserve(n_triangles);
    mesh.vertices().reserve(n_triangles * 3);
    mesh.halfedges().reserve(n_triangles * 3);

Philip Trettner's avatar
Philip Trettner committed
126
    for (auto i = 0u; i < n_triangles; ++i)
127
128
129
130
131
132
133
134
135
136
137
138
    {
        if (!input.good())
        {
            std::cerr << "Premature end of file";
            return false;
        }

        auto v0 = mesh.vertices().add();
        auto v1 = mesh.vertices().add();
        auto v2 = mesh.vertices().add();
        auto f = mesh.faces().add(v0, v1, v2);

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
        std::array<float, 3> n;
        input.read(reinterpret_cast<char*>(&n), sizeof(n));

        if (normals)
            f[normals] = {n[0], n[1], n[2]};

        std::array<float, 3> p0;
        std::array<float, 3> p1;
        std::array<float, 3> p2;
        input.read(reinterpret_cast<char*>(&p0), sizeof(p0));
        input.read(reinterpret_cast<char*>(&p1), sizeof(p1));
        input.read(reinterpret_cast<char*>(&p2), sizeof(p2));
        // convert float to ScalarT
        position[v0] = {ScalarT(p0[0]), ScalarT(p0[1]), ScalarT(p0[2])};
        position[v1] = {ScalarT(p1[0]), ScalarT(p1[1]), ScalarT(p1[2])};
        position[v2] = {ScalarT(p2[0]), ScalarT(p2[1]), ScalarT(p2[2])};

156
        uint16_t attr_cnt;
157
        input.read(reinterpret_cast<char*>(&attr_cnt), sizeof(attr_cnt));
158
159
160
161
162
    }

    return true;
}

163
template <class ScalarT>
164
static ScalarT read_real_with_nan(std::istream& input)
Philip Trettner's avatar
Philip Trettner committed
165
166
167
168
{
    std::string s;
    input >> s;
    if (s == "NaN" || s == "nan" || s == "NAN")
169
        return std::numeric_limits<ScalarT>::quiet_NaN();
Philip Trettner's avatar
Philip Trettner committed
170
171

    std::istringstream ss(s);
172
    ScalarT f;
Philip Trettner's avatar
Philip Trettner committed
173
174
175
176
    ss >> f;
    return f;
}

177
template <class ScalarT>
178
bool read_stl_ascii(std::istream& input, Mesh& mesh, vertex_attribute<std::array<ScalarT, 3>>& position, face_attribute<std::array<ScalarT, 3>>* normals)
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
{
    mesh.clear();

    std::string s;
    input >> s;

    if (s != "solid")
    {
        std::cerr << "File does not seem to be ASCII stl" << std::endl;
        return false;
    }

    // name
    do
    {
        input >> s;
Philip Trettner's avatar
Philip Trettner committed
195
    } while (input.good() && s != "endsolid" && s != "facet" && s != "faced");
196
197
198

    while (input.good() && s != "endsolid")
    {
199
        POLYMESH_ASSERT(s == "facet" || s == "faced");
200
201
202
203
204
205
206
207

        vertex_handle v[3];
        v[0] = mesh.vertices().add();
        v[1] = mesh.vertices().add();
        v[2] = mesh.vertices().add();
        auto f = mesh.faces().add(v);

        input >> s;
208
        POLYMESH_ASSERT(s == "normal");
209
210
211
212
        std::array<ScalarT, 3> n;
        n[0] = read_real_with_nan<ScalarT>(input);
        n[1] = read_real_with_nan<ScalarT>(input);
        n[2] = read_real_with_nan<ScalarT>(input);
213
214
215
        f[normals] = n;

        input >> s;
216
        POLYMESH_ASSERT(s == "outer");
217
        input >> s;
218
        POLYMESH_ASSERT(s == "loop");
219
220
221
222

        for (auto i = 0; i < 3; ++i)
        {
            input >> s;
223
            POLYMESH_ASSERT(s == "vertex");
224
225
226
227
            std::array<ScalarT, 3> p;
            p[0] = read_real_with_nan<ScalarT>(input);
            p[1] = read_real_with_nan<ScalarT>(input);
            p[2] = read_real_with_nan<ScalarT>(input);
228
229
230
231
            position[v[i]] = p;
        }

        input >> s;
232
        POLYMESH_ASSERT(s == "endloop");
233
234

        input >> s;
235
        POLYMESH_ASSERT(s == "endfacet");
236
237
238
239
240
241
242

        input >> s; // for next iteration
    }

    return true;
}

243
bool is_ascii_stl(std::istream& input)
244
245
246
{
    auto savp = input.tellg();

Philip Trettner's avatar
Philip Trettner committed
247
    char solid[5];
248
249
    input.read(solid, sizeof(solid));

Philip Trettner's avatar
Philip Trettner committed
250
    if (solid[0] != 's' || solid[1] != 'o' || solid[2] != 'l' || solid[3] != 'i' || solid[4] != 'd')
251
252
253
254
255
256
257
258
    {
        input.seekg(savp, std::ios_base::beg);
        return false;
    }

    std::string s;
    input >> s;

Philip Trettner's avatar
Philip Trettner committed
259
    if (s == "facet" || s == "faced" || s == "endsolid")
260
261
262
263
264
    {
        input.seekg(savp, std::ios_base::beg);
        return true;
    }

Philip Trettner's avatar
Philip Trettner committed
265
    for (auto i = 0; i < 20; ++i)
266
    {
Philip Trettner's avatar
Philip Trettner committed
267
268
269
270
271
272
273
274
275
276
277
278
279
        input >> s;

        if (s == "facet" || s == "faced" || s == "endsolid")
        {
            input.seekg(savp, std::ios_base::beg);
            return true;
        }

        if (!input)
        {
            input.seekg(savp, std::ios_base::beg);
            return false;
        }
280
281
282
283
284
    }

    input.seekg(savp, std::ios_base::beg);
    return false;
}
285

286
287
288
template void write_stl_binary<float>(std::string const& filename,
                                      vertex_attribute<std::array<float, 3>> const& position,
                                      face_attribute<std::array<float, 3>> const* normals);
289
template void write_stl_binary<float>(std::ostream& out, vertex_attribute<std::array<float, 3>> const& position, face_attribute<std::array<float, 3>> const* normals);
290
291
292
293
294
295
296
297
template bool read_stl<float>(std::string const& filename, Mesh& mesh, vertex_attribute<std::array<float, 3>>& position, face_attribute<std::array<float, 3>>* normals);
template bool read_stl<float>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<float, 3>>& position, face_attribute<std::array<float, 3>>* normals);
template bool read_stl_binary<float>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<float, 3>>& position, face_attribute<std::array<float, 3>>* normals);
template bool read_stl_ascii<float>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<float, 3>>& position, face_attribute<std::array<float, 3>>* normals);

template void write_stl_binary<double>(std::string const& filename,
                                       vertex_attribute<std::array<double, 3>> const& position,
                                       face_attribute<std::array<double, 3>> const* normals);
298
template void write_stl_binary<double>(std::ostream& out, vertex_attribute<std::array<double, 3>> const& position, face_attribute<std::array<double, 3>> const* normals);
299
300
301
302
template bool read_stl<double>(std::string const& filename, Mesh& mesh, vertex_attribute<std::array<double, 3>>& position, face_attribute<std::array<double, 3>>* normals);
template bool read_stl<double>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<double, 3>>& position, face_attribute<std::array<double, 3>>* normals);
template bool read_stl_binary<double>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<double, 3>>& position, face_attribute<std::array<double, 3>>* normals);
template bool read_stl_ascii<double>(std::istream& input, Mesh& mesh, vertex_attribute<std::array<double, 3>>& position, face_attribute<std::array<double, 3>>* normals);
303
} // namespace polymesh