Welcome to mirror list, hosted at ThFree Co, Russian Federation.

sla_test_utils.cpp « sla_print « tests - github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3da28a50e6fc4a8580dfb45235105ce6807223a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#include "sla_test_utils.hpp"

void test_support_model_collision(const std::string          &obj_filename,
                                  const sla::SupportConfig   &input_supportcfg,
                                  const sla::HollowingConfig &hollowingcfg,
                                  const sla::DrainHoles      &drainholes)
{
    SupportByproducts byproducts;
    
    sla::SupportConfig supportcfg = input_supportcfg;
    
    // Set head penetration to a small negative value which should ensure that
    // the supports will not touch the model body.
    supportcfg.head_penetration_mm = -0.15;
    
    // TODO: currently, the tailheads penetrating into the model body do not
    // respect the penetration parameter properly. No issues were reported so
    // far but we should definitely fix this.
    supportcfg.ground_facing_only = true;
    
    test_supports(obj_filename, supportcfg, hollowingcfg, drainholes, byproducts);
    
    // Slice the support mesh given the slice grid of the model.
    std::vector<ExPolygons> support_slices =
            byproducts.supporttree.slice(byproducts.slicegrid, CLOSING_RADIUS);
    
    // The slices originate from the same slice grid so the numbers must match
    
    bool support_mesh_is_empty =
            byproducts.supporttree.retrieve_mesh(sla::MeshType::Pad).empty() &&
            byproducts.supporttree.retrieve_mesh(sla::MeshType::Support).empty();
    
    if (support_mesh_is_empty)
        REQUIRE(support_slices.empty());
    else
        REQUIRE(support_slices.size() == byproducts.model_slices.size());
    
    bool notouch = true;
    for (size_t n = 0; notouch && n < support_slices.size(); ++n) {
        const ExPolygons &sup_slice = support_slices[n];
        const ExPolygons &mod_slice = byproducts.model_slices[n];
        
        Polygons intersections = intersection(sup_slice, mod_slice);
        
        notouch = notouch && intersections.empty();
    }
    
    /*if (!notouch) */export_failed_case(support_slices, byproducts);
    
    REQUIRE(notouch);
}

void export_failed_case(const std::vector<ExPolygons> &support_slices, const SupportByproducts &byproducts)
{
    for (size_t n = 0; n < support_slices.size(); ++n) {
        const ExPolygons &sup_slice = support_slices[n];
        const ExPolygons &mod_slice = byproducts.model_slices[n];
        Polygons intersections = intersection(sup_slice, mod_slice);
        
        std::stringstream ss;
        if (!intersections.empty()) {
            ss << byproducts.obj_fname << std::setprecision(4) << n << ".svg";
            SVG svg(ss.str());
            svg.draw(sup_slice, "green");
            svg.draw(mod_slice, "blue");
            svg.draw(intersections, "red");
            svg.Close();
        }
    }
    
    TriangleMesh m;
    byproducts.supporttree.retrieve_full_mesh(m);
    m.merge(byproducts.input_mesh);
    m.repair();
    m.require_shared_vertices();
    m.WriteOBJFile(byproducts.obj_fname.c_str());
}

void test_supports(const std::string          &obj_filename,
                   const sla::SupportConfig   &supportcfg,
                   const sla::HollowingConfig &hollowingcfg,
                   const sla::DrainHoles      &drainholes,
                   SupportByproducts          &out)
{
    using namespace Slic3r;
    TriangleMesh mesh = load_model(obj_filename);
    
    REQUIRE_FALSE(mesh.empty());
    
    if (hollowingcfg.enabled) {
        auto inside = sla::generate_interior(mesh, hollowingcfg);
        REQUIRE(inside);
        mesh.merge(*inside);
        mesh.require_shared_vertices();
    }
    
    TriangleMeshSlicer slicer{&mesh};
    
    auto   bb      = mesh.bounding_box();
    double zmin    = bb.min.z();
    double zmax    = bb.max.z();
    double gnd     = zmin - supportcfg.object_elevation_mm;
    auto   layer_h = 0.05f;
    
    out.slicegrid = grid(float(gnd), float(zmax), layer_h);
    slicer.slice(out.slicegrid , CLOSING_RADIUS, &out.model_slices, []{});
    sla::cut_drainholes(out.model_slices, out.slicegrid, CLOSING_RADIUS, drainholes, []{});
    
    // Create the special index-triangle mesh with spatial indexing which
    // is the input of the support point and support mesh generators
    sla::EigenMesh3D emesh{mesh};
    if (hollowingcfg.enabled) 
        emesh.load_holes(drainholes);
    
    // Create the support point generator
    sla::SupportPointGenerator::Config autogencfg;
    autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
    sla::SupportPointGenerator point_gen{emesh, out.model_slices, out.slicegrid, 
                autogencfg, [] {}, [](int) {}};
    
    // Get the calculated support points.
    std::vector<sla::SupportPoint> support_points = point_gen.output();
    
    int validityflags = ASSUME_NO_REPAIR;
    
    // If there is no elevation, support points shall be removed from the
    // bottom of the object.
    if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
        sla::remove_bottom_points(support_points, zmin,
                                  supportcfg.base_height_mm);
    } else {
        // Should be support points at least on the bottom of the model
        REQUIRE_FALSE(support_points.empty());
        
        // Also the support mesh should not be empty.
        validityflags |= ASSUME_NO_EMPTY;
    }
    
    // Generate the actual support tree
    sla::SupportTreeBuilder treebuilder;
    treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg});
    
    check_support_tree_integrity(treebuilder, supportcfg);
    
    const TriangleMesh &output_mesh = treebuilder.retrieve_mesh();
    
    check_validity(output_mesh, validityflags);
    
    // Quick check if the dimensions and placement of supports are correct
    auto obb = output_mesh.bounding_box();
    
    double allowed_zmin = zmin - supportcfg.object_elevation_mm;
    
    if (std::abs(supportcfg.object_elevation_mm) < EPSILON)
        allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm;
    
    REQUIRE(obb.min.z() >= allowed_zmin);
    REQUIRE(obb.max.z() <= zmax);
    
    // Move out the support tree into the byproducts, we can examine it further
    // in various tests.
    out.obj_fname   = std::move(obj_filename);
    out.supporttree = std::move(treebuilder);
    out.input_mesh  = std::move(mesh);
}

void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, 
                                  const sla::SupportConfig &cfg)
{
    double gnd  = stree.ground_level;
    double H1   = cfg.max_solo_pillar_height_mm;
    double H2   = cfg.max_dual_pillar_height_mm;
    
    for (const sla::Head &head : stree.heads()) {
        REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET ||
                head.bridge_id != sla::ID_UNSET));
    }
    
    for (const sla::Pillar &pillar : stree.pillars()) {
        if (std::abs(pillar.endpoint().z() - gnd) < EPSILON) {
            double h = pillar.height;
            
            if (h > H1) REQUIRE(pillar.links >= 1);
            else if(h > H2) { REQUIRE(pillar.links >= 2); }
        }
        
        REQUIRE(pillar.links <= cfg.pillar_cascade_neighbors);
        REQUIRE(pillar.bridges <= cfg.max_bridges_on_pillar);
    }
    
    double max_bridgelen = 0.;
    auto chck_bridge = [&cfg](const sla::Bridge &bridge, double &max_brlen) {
        Vec3d n = bridge.endp - bridge.startp;
        double d = sla::distance(n);
        max_brlen = std::max(d, max_brlen);
        
        double z     = n.z();
        double polar = std::acos(z / d);
        double slope = -polar + PI / 2.;
        REQUIRE(std::abs(slope) >= cfg.bridge_slope - EPSILON);
    };
    
    for (auto &bridge : stree.bridges()) chck_bridge(bridge, max_bridgelen);
    REQUIRE(max_bridgelen <= cfg.max_bridge_length_mm);
    
    max_bridgelen = 0;
    for (auto &bridge : stree.crossbridges()) chck_bridge(bridge, max_bridgelen);
    
    double md = cfg.max_pillar_link_distance_mm / std::cos(-cfg.bridge_slope);
    REQUIRE(max_bridgelen <= md);
}

void test_pad(const std::string &obj_filename, const sla::PadConfig &padcfg, PadByproducts &out)
{
    REQUIRE(padcfg.validate().empty());
    
    TriangleMesh mesh = load_model(obj_filename);
    
    REQUIRE_FALSE(mesh.empty());
    
    // Create pad skeleton only from the model
    Slic3r::sla::pad_blueprint(mesh, out.model_contours);
    
    test_concave_hull(out.model_contours);
    
    REQUIRE_FALSE(out.model_contours.empty());
    
    // Create the pad geometry for the model contours only
    Slic3r::sla::create_pad({}, out.model_contours, out.mesh, padcfg);
    
    check_validity(out.mesh);
    
    auto bb = out.mesh.bounding_box();
    REQUIRE(bb.max.z() - bb.min.z() == Approx(padcfg.full_height()));
}

static void _test_concave_hull(const Polygons &hull, const ExPolygons &polys)
{
    REQUIRE(polys.size() >=hull.size());
    
    double polys_area = 0;
    for (const ExPolygon &p : polys) polys_area += p.area();
    
    double cchull_area = 0;
    for (const Slic3r::Polygon &p : hull) cchull_area += p.area();
    
    REQUIRE(cchull_area >= Approx(polys_area));
    
    size_t cchull_holes = 0;
    for (const Slic3r::Polygon &p : hull)
        cchull_holes += p.is_clockwise() ? 1 : 0;
    
    REQUIRE(cchull_holes == 0);
    
    Polygons intr = diff(to_polygons(polys), hull);
    REQUIRE(intr.empty());
}

void test_concave_hull(const ExPolygons &polys) {
    sla::PadConfig pcfg;
    
    Slic3r::sla::ConcaveHull cchull{polys, pcfg.max_merge_dist_mm, []{}};
    
    _test_concave_hull(cchull.polygons(), polys);
    
    coord_t delta = scaled(pcfg.brim_size_mm + pcfg.wing_distance());
    ExPolygons wafflex = sla::offset_waffle_style_ex(cchull, delta);
    Polygons waffl = sla::offset_waffle_style(cchull, delta);
    
    _test_concave_hull(to_polygons(wafflex), polys);
    _test_concave_hull(waffl, polys);
}

void check_validity(const TriangleMesh &input_mesh, int flags)
{
    TriangleMesh mesh{input_mesh};
    
    if (flags & ASSUME_NO_EMPTY) {
        REQUIRE_FALSE(mesh.empty());
    } else if (mesh.empty())
        return; // If it can be empty and it is, there is nothing left to do.
    
    REQUIRE(stl_validate(&mesh.stl));
    
    bool do_update_shared_vertices = false;
    mesh.repair(do_update_shared_vertices);
    
    if (flags & ASSUME_NO_REPAIR) {
        REQUIRE_FALSE(mesh.needed_repair());
    }
    
    if (flags & ASSUME_MANIFOLD) {
        mesh.require_shared_vertices();
        if (!mesh.is_manifold()) mesh.WriteOBJFile("non_manifold.obj");
        REQUIRE(mesh.is_manifold());
    }
}