Fixed conflicts after merge with master
This commit is contained in:
commit
aedb3892ba
@ -101,6 +101,9 @@ void AppConfig::set_defaults()
|
|||||||
if (get("use_inches").empty())
|
if (get("use_inches").empty())
|
||||||
set("use_inches", "0");
|
set("use_inches", "0");
|
||||||
|
|
||||||
|
if (get("show_splash_screen").empty())
|
||||||
|
set("show_splash_screen", "1");
|
||||||
|
|
||||||
// Remove legacy window positions/sizes
|
// Remove legacy window positions/sizes
|
||||||
erase("", "main_frame_maximized");
|
erase("", "main_frame_maximized");
|
||||||
erase("", "main_frame_pos");
|
erase("", "main_frame_pos");
|
||||||
|
@ -215,7 +215,9 @@ add_library(libslic3r STATIC
|
|||||||
SimplifyMeshImpl.hpp
|
SimplifyMeshImpl.hpp
|
||||||
SimplifyMesh.cpp
|
SimplifyMesh.cpp
|
||||||
MarchingSquares.hpp
|
MarchingSquares.hpp
|
||||||
Optimizer.hpp
|
Optimize/Optimizer.hpp
|
||||||
|
Optimize/NLoptOptimizer.hpp
|
||||||
|
Optimize/BruteforceOptimizer.hpp
|
||||||
${OpenVDBUtils_SOURCES}
|
${OpenVDBUtils_SOURCES}
|
||||||
SLA/Pad.hpp
|
SLA/Pad.hpp
|
||||||
SLA/Pad.cpp
|
SLA/Pad.cpp
|
||||||
|
@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// friend to Layer
|
// friend to Layer
|
||||||
void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree)
|
void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree)
|
||||||
{
|
{
|
||||||
for (LayerRegion *layerm : m_regions)
|
for (LayerRegion *layerm : m_regions)
|
||||||
layerm->fills.clear();
|
layerm->fills.clear();
|
||||||
@ -346,6 +346,7 @@ void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree)
|
|||||||
f->z = this->print_z;
|
f->z = this->print_z;
|
||||||
f->angle = surface_fill.params.angle;
|
f->angle = surface_fill.params.angle;
|
||||||
f->adapt_fill_octree = adaptive_fill_octree;
|
f->adapt_fill_octree = adaptive_fill_octree;
|
||||||
|
f->support_fill_octree = support_fill_octree;
|
||||||
|
|
||||||
// calculate flow spacing for infill pattern generation
|
// calculate flow spacing for infill pattern generation
|
||||||
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
|
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
|
||||||
|
@ -35,7 +35,7 @@ std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_ob
|
|||||||
const PrintRegionConfig &config = region->config();
|
const PrintRegionConfig &config = region->config();
|
||||||
bool nonempty = config.fill_density > 0;
|
bool nonempty = config.fill_density > 0;
|
||||||
bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
|
bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
|
||||||
bool has_support_infill = nonempty && false; // config.fill_pattern == icSupportCubic;
|
bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic;
|
||||||
region_fill_data.push_back(RegionFillData({
|
region_fill_data.push_back(RegionFillData({
|
||||||
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
|
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
|
||||||
has_support_infill ? Tristate::Maybe : Tristate::No,
|
has_support_infill ? Tristate::Maybe : Tristate::No,
|
||||||
@ -90,19 +90,32 @@ std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_ob
|
|||||||
return std::make_pair(adaptive_line_spacing, support_line_spacing);
|
return std::make_pair(adaptive_line_spacing, support_line_spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillAdaptive::_fill_surface_single(
|
void FillAdaptive::_fill_surface_single(const FillParams & params,
|
||||||
const FillParams ¶ms,
|
unsigned int thickness_layers,
|
||||||
unsigned int thickness_layers,
|
const std::pair<float, Point> &direction,
|
||||||
const std::pair<float, Point> &direction,
|
ExPolygon & expolygon,
|
||||||
ExPolygon &expolygon,
|
Polylines & polylines_out)
|
||||||
Polylines &polylines_out)
|
|
||||||
{
|
{
|
||||||
|
if(this->adapt_fill_octree != nullptr)
|
||||||
|
this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->adapt_fill_octree);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillAdaptive::generate_infill(const FillParams & params,
|
||||||
|
unsigned int thickness_layers,
|
||||||
|
const std::pair<float, Point> &direction,
|
||||||
|
ExPolygon & expolygon,
|
||||||
|
Polylines & polylines_out,
|
||||||
|
FillAdaptive_Internal::Octree *octree)
|
||||||
|
{
|
||||||
|
Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0);
|
||||||
|
Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones());
|
||||||
|
|
||||||
// Store grouped lines by its direction (multiple of 120°)
|
// Store grouped lines by its direction (multiple of 120°)
|
||||||
std::vector<Lines> infill_lines_dir(3);
|
std::vector<Lines> infill_lines_dir(3);
|
||||||
this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(),
|
this->generate_infill_lines(octree->root_cube.get(),
|
||||||
this->z, this->adapt_fill_octree->origin,infill_lines_dir,
|
this->z, octree->origin, rotation_matrix,
|
||||||
this->adapt_fill_octree->cubes_properties,
|
infill_lines_dir, octree->cubes_properties,
|
||||||
int(this->adapt_fill_octree->cubes_properties.size()) - 1);
|
int(octree->cubes_properties.size()) - 1);
|
||||||
|
|
||||||
Polylines all_polylines;
|
Polylines all_polylines;
|
||||||
all_polylines.reserve(infill_lines_dir[0].size() * 3);
|
all_polylines.reserve(infill_lines_dir[0].size() * 3);
|
||||||
@ -186,6 +199,7 @@ void FillAdaptive::generate_infill_lines(
|
|||||||
FillAdaptive_Internal::Cube *cube,
|
FillAdaptive_Internal::Cube *cube,
|
||||||
double z_position,
|
double z_position,
|
||||||
const Vec3d &origin,
|
const Vec3d &origin,
|
||||||
|
const Transform3d &rotation_matrix,
|
||||||
std::vector<Lines> &dir_lines_out,
|
std::vector<Lines> &dir_lines_out,
|
||||||
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
||||||
int depth)
|
int depth)
|
||||||
@ -197,7 +211,8 @@ void FillAdaptive::generate_infill_lines(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double z_diff = std::abs(z_position - cube->center.z());
|
Vec3d cube_center_tranformed = rotation_matrix * cube->center;
|
||||||
|
double z_diff = std::abs(z_position - cube_center_tranformed.z());
|
||||||
|
|
||||||
if (z_diff > cubes_properties[depth].height / 2)
|
if (z_diff > cubes_properties[depth].height / 2)
|
||||||
{
|
{
|
||||||
@ -208,14 +223,14 @@ void FillAdaptive::generate_infill_lines(
|
|||||||
{
|
{
|
||||||
Point from(
|
Point from(
|
||||||
scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance),
|
scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance),
|
||||||
scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube->center.z() - cubes_properties[depth].line_z_distance)) / sqrt(2))));
|
scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube_center_tranformed.z() - cubes_properties[depth].line_z_distance)) / sqrt(2))));
|
||||||
Point to(-from.x(), from.y());
|
Point to(-from.x(), from.y());
|
||||||
// Relative to cube center
|
// Relative to cube center
|
||||||
|
|
||||||
double rotation_angle = (2.0 * M_PI) / 3.0;
|
double rotation_angle = (2.0 * M_PI) / 3.0;
|
||||||
for (Lines &lines : dir_lines_out)
|
for (Lines &lines : dir_lines_out)
|
||||||
{
|
{
|
||||||
Vec3d offset = cube->center - origin;
|
Vec3d offset = cube_center_tranformed - (rotation_matrix * origin);
|
||||||
Point from_abs(from), to_abs(to);
|
Point from_abs(from), to_abs(to);
|
||||||
|
|
||||||
from_abs.x() += int(scale_(offset.x()));
|
from_abs.x() += int(scale_(offset.x()));
|
||||||
@ -235,7 +250,7 @@ void FillAdaptive::generate_infill_lines(
|
|||||||
{
|
{
|
||||||
if(child != nullptr)
|
if(child != nullptr)
|
||||||
{
|
{
|
||||||
generate_infill_lines(child.get(), z_position, origin, dir_lines_out, cubes_properties, depth - 1);
|
generate_infill_lines(child.get(), z_position, origin, rotation_matrix, dir_lines_out, cubes_properties, depth - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,14 +316,11 @@ std::unique_ptr<FillAdaptive_Internal::Octree> FillAdaptive::build_octree(
|
|||||||
triangle_mesh.require_shared_vertices();
|
triangle_mesh.require_shared_vertices();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0);
|
|
||||||
Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones());
|
|
||||||
|
|
||||||
AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||||
triangle_mesh.its.vertices, triangle_mesh.its.indices);
|
triangle_mesh.its.vertices, triangle_mesh.its.indices);
|
||||||
auto octree = std::make_unique<Octree>(std::make_unique<Cube>(cube_center), cube_center, cubes_properties);
|
auto octree = std::make_unique<Octree>(std::make_unique<Cube>(cube_center), cube_center, cubes_properties);
|
||||||
|
|
||||||
FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1);
|
FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1);
|
||||||
|
|
||||||
return octree;
|
return octree;
|
||||||
}
|
}
|
||||||
@ -316,7 +328,6 @@ std::unique_ptr<FillAdaptive_Internal::Octree> FillAdaptive::build_octree(
|
|||||||
void FillAdaptive::expand_cube(
|
void FillAdaptive::expand_cube(
|
||||||
FillAdaptive_Internal::Cube *cube,
|
FillAdaptive_Internal::Cube *cube,
|
||||||
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
||||||
const Transform3d &rotation_matrix,
|
|
||||||
const AABBTreeIndirect::Tree3f &distance_tree,
|
const AABBTreeIndirect::Tree3f &distance_tree,
|
||||||
const TriangleMesh &triangle_mesh, int depth)
|
const TriangleMesh &triangle_mesh, int depth)
|
||||||
{
|
{
|
||||||
@ -328,8 +339,8 @@ void FillAdaptive::expand_cube(
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Vec3d> child_centers = {
|
std::vector<Vec3d> child_centers = {
|
||||||
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d(-1, -1, 1),
|
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
|
||||||
Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1)
|
Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16;
|
double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16;
|
||||||
@ -337,15 +348,173 @@ void FillAdaptive::expand_cube(
|
|||||||
for (size_t i = 0; i < 8; ++i)
|
for (size_t i = 0; i < 8; ++i)
|
||||||
{
|
{
|
||||||
const Vec3d &child_center = child_centers[i];
|
const Vec3d &child_center = child_centers[i];
|
||||||
Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cubes_properties[depth].edge_length / 4));
|
Vec3d child_center_transformed = cube->center + (child_center * (cubes_properties[depth].edge_length / 4));
|
||||||
|
|
||||||
if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices,
|
if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices,
|
||||||
distance_tree, child_center_transformed, cube_radius_squared))
|
distance_tree, child_center_transformed, cube_radius_squared))
|
||||||
{
|
{
|
||||||
cube->children[i] = std::make_unique<Cube>(child_center_transformed);
|
cube->children[i] = std::make_unique<Cube>(child_center_transformed);
|
||||||
FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, rotation_matrix, distance_tree, triangle_mesh, depth - 1);
|
FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, distance_tree, triangle_mesh, depth - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FillAdaptive_Internal::Octree::propagate_point(
|
||||||
|
Vec3d point,
|
||||||
|
FillAdaptive_Internal::Cube * current,
|
||||||
|
int depth,
|
||||||
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties)
|
||||||
|
{
|
||||||
|
using namespace FillAdaptive_Internal;
|
||||||
|
|
||||||
|
if(depth <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t octant_idx = Octree::find_octant(point, current->center);
|
||||||
|
Cube * child = current->children[octant_idx].get();
|
||||||
|
|
||||||
|
// Octant not exists, then create it
|
||||||
|
if(child == nullptr) {
|
||||||
|
std::vector<Vec3d> child_centers = {
|
||||||
|
Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1),
|
||||||
|
Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
const Vec3d &child_center = child_centers[octant_idx];
|
||||||
|
Vec3d child_center_transformed = current->center + (child_center * (cubes_properties[depth].edge_length / 4));
|
||||||
|
|
||||||
|
current->children[octant_idx] = std::make_unique<Cube>(child_center_transformed);
|
||||||
|
child = current->children[octant_idx].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Octree::propagate_point(point, child, (depth - 1), cubes_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<FillAdaptive_Internal::Octree> FillSupportCubic::build_octree(
|
||||||
|
TriangleMesh & triangle_mesh,
|
||||||
|
coordf_t line_spacing,
|
||||||
|
const Vec3d & cube_center,
|
||||||
|
const Transform3d &rotation_matrix)
|
||||||
|
{
|
||||||
|
using namespace FillAdaptive_Internal;
|
||||||
|
|
||||||
|
if(line_spacing <= 0 || std::isnan(line_spacing))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d bb_size = triangle_mesh.bounding_box().size();
|
||||||
|
// The furthest point from the center of the bottom of the mesh bounding box.
|
||||||
|
double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) +
|
||||||
|
((bb_size.y() * bb_size.y()) / 4.0) +
|
||||||
|
(bb_size.z() * bb_size.z()));
|
||||||
|
double max_cube_edge_length = furthest_point * 2;
|
||||||
|
|
||||||
|
std::vector<CubeProperties> cubes_properties;
|
||||||
|
for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2)
|
||||||
|
{
|
||||||
|
CubeProperties props{};
|
||||||
|
props.edge_length = edge_length;
|
||||||
|
props.height = edge_length * sqrt(3);
|
||||||
|
props.diagonal_length = edge_length * sqrt(2);
|
||||||
|
props.line_z_distance = edge_length / sqrt(3);
|
||||||
|
props.line_xy_distance = edge_length / sqrt(6);
|
||||||
|
cubes_properties.push_back(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangle_mesh.its.vertices.empty())
|
||||||
|
{
|
||||||
|
triangle_mesh.require_shared_vertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||||
|
triangle_mesh.its.vertices, triangle_mesh.its.indices);
|
||||||
|
|
||||||
|
auto octree = std::make_unique<Octree>(std::make_unique<Cube>(cube_center), cube_center, cubes_properties);
|
||||||
|
|
||||||
|
double cube_edge_length = line_spacing / 2.0;
|
||||||
|
int max_depth = int(octree->cubes_properties.size()) - 1;
|
||||||
|
BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box();
|
||||||
|
Vec3f vertical(0, 0, 1);
|
||||||
|
|
||||||
|
for (size_t facet_idx = 0; facet_idx < triangle_mesh.stl.facet_start.size(); ++facet_idx)
|
||||||
|
{
|
||||||
|
if(triangle_mesh.stl.facet_start[facet_idx].normal.dot(vertical) <= 0.707)
|
||||||
|
{
|
||||||
|
// The angle is smaller than PI/4, than infill don't to be there
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stl_vertex v_1 = triangle_mesh.stl.facet_start[facet_idx].vertex[0];
|
||||||
|
stl_vertex v_2 = triangle_mesh.stl.facet_start[facet_idx].vertex[1];
|
||||||
|
stl_vertex v_3 = triangle_mesh.stl.facet_start[facet_idx].vertex[2];
|
||||||
|
|
||||||
|
std::vector<Vec3d> triangle_vertices =
|
||||||
|
{Vec3d(v_1.x(), v_1.y(), v_1.z()),
|
||||||
|
Vec3d(v_2.x(), v_2.y(), v_2.z()),
|
||||||
|
Vec3d(v_3.x(), v_3.y(), v_3.z())};
|
||||||
|
|
||||||
|
BoundingBoxf3 triangle_bb(triangle_vertices);
|
||||||
|
|
||||||
|
Vec3d triangle_start_relative = triangle_bb.min - mesh_bb.min;
|
||||||
|
Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min;
|
||||||
|
|
||||||
|
Vec3crd triangle_start_idx = Vec3crd(
|
||||||
|
int(std::floor(triangle_start_relative.x() / cube_edge_length)),
|
||||||
|
int(std::floor(triangle_start_relative.y() / cube_edge_length)),
|
||||||
|
int(std::floor(triangle_start_relative.z() / cube_edge_length)));
|
||||||
|
Vec3crd triangle_end_idx = Vec3crd(
|
||||||
|
int(std::floor(triangle_end_relative.x() / cube_edge_length)),
|
||||||
|
int(std::floor(triangle_end_relative.y() / cube_edge_length)),
|
||||||
|
int(std::floor(triangle_end_relative.z() / cube_edge_length)));
|
||||||
|
|
||||||
|
for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z)
|
||||||
|
{
|
||||||
|
for (int y = triangle_start_idx.y(); y <= triangle_end_idx.y(); ++y)
|
||||||
|
{
|
||||||
|
for (int x = triangle_start_idx.x(); x <= triangle_end_idx.x(); ++x)
|
||||||
|
{
|
||||||
|
Vec3d cube_center_relative(x * cube_edge_length + (cube_edge_length / 2.0), y * cube_edge_length + (cube_edge_length / 2.0), z * cube_edge_length);
|
||||||
|
Vec3d cube_center_absolute = cube_center_relative + mesh_bb.min;
|
||||||
|
|
||||||
|
double cube_center_absolute_arr[3] = {cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z()};
|
||||||
|
double distance = 0, cord_u = 0, cord_v = 0;
|
||||||
|
|
||||||
|
double dir[3] = {0.0, 0.0, 1.0};
|
||||||
|
|
||||||
|
double vert_0[3] = {triangle_vertices[0].x(),
|
||||||
|
triangle_vertices[0].y(),
|
||||||
|
triangle_vertices[0].z()};
|
||||||
|
double vert_1[3] = {triangle_vertices[1].x(),
|
||||||
|
triangle_vertices[1].y(),
|
||||||
|
triangle_vertices[1].z()};
|
||||||
|
double vert_2[3] = {triangle_vertices[2].x(),
|
||||||
|
triangle_vertices[2].y(),
|
||||||
|
triangle_vertices[2].z()};
|
||||||
|
|
||||||
|
if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length)
|
||||||
|
{
|
||||||
|
Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0));
|
||||||
|
Octree::propagate_point(rotation_matrix * cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return octree;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillSupportCubic::_fill_surface_single(const FillParams & params,
|
||||||
|
unsigned int thickness_layers,
|
||||||
|
const std::pair<float, Point> &direction,
|
||||||
|
ExPolygon & expolygon,
|
||||||
|
Polylines & polylines_out)
|
||||||
|
{
|
||||||
|
if (this->support_fill_octree != nullptr)
|
||||||
|
this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->support_fill_octree);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -35,6 +35,17 @@ namespace FillAdaptive_Internal
|
|||||||
|
|
||||||
Octree(std::unique_ptr<Cube> rootCube, const Vec3d &origin, const std::vector<CubeProperties> &cubes_properties)
|
Octree(std::unique_ptr<Cube> rootCube, const Vec3d &origin, const std::vector<CubeProperties> &cubes_properties)
|
||||||
: root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {}
|
: root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {}
|
||||||
|
|
||||||
|
inline static int find_octant(const Vec3d &i_cube, const Vec3d ¤t)
|
||||||
|
{
|
||||||
|
return (i_cube.z() > current.z()) * 4 + (i_cube.y() > current.y()) * 2 + (i_cube.x() > current.x());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void propagate_point(
|
||||||
|
Vec3d point,
|
||||||
|
FillAdaptive_Internal::Cube *current_cube,
|
||||||
|
int depth,
|
||||||
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties);
|
||||||
};
|
};
|
||||||
}; // namespace FillAdaptive_Internal
|
}; // namespace FillAdaptive_Internal
|
||||||
|
|
||||||
@ -63,12 +74,20 @@ protected:
|
|||||||
FillAdaptive_Internal::Cube *cube,
|
FillAdaptive_Internal::Cube *cube,
|
||||||
double z_position,
|
double z_position,
|
||||||
const Vec3d & origin,
|
const Vec3d & origin,
|
||||||
|
const Transform3d & rotation_matrix,
|
||||||
std::vector<Lines> & dir_lines_out,
|
std::vector<Lines> & dir_lines_out,
|
||||||
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
||||||
int depth);
|
int depth);
|
||||||
|
|
||||||
static void connect_lines(Lines &lines, Line new_line);
|
static void connect_lines(Lines &lines, Line new_line);
|
||||||
|
|
||||||
|
void generate_infill(const FillParams & params,
|
||||||
|
unsigned int thickness_layers,
|
||||||
|
const std::pair<float, Point> &direction,
|
||||||
|
ExPolygon & expolygon,
|
||||||
|
Polylines & polylines_out,
|
||||||
|
FillAdaptive_Internal::Octree *octree);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<FillAdaptive_Internal::Octree> build_octree(
|
static std::unique_ptr<FillAdaptive_Internal::Octree> build_octree(
|
||||||
TriangleMesh &triangle_mesh,
|
TriangleMesh &triangle_mesh,
|
||||||
@ -78,12 +97,36 @@ public:
|
|||||||
static void expand_cube(
|
static void expand_cube(
|
||||||
FillAdaptive_Internal::Cube *cube,
|
FillAdaptive_Internal::Cube *cube,
|
||||||
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
const std::vector<FillAdaptive_Internal::CubeProperties> &cubes_properties,
|
||||||
const Transform3d & rotation_matrix,
|
|
||||||
const AABBTreeIndirect::Tree3f &distance_tree,
|
const AABBTreeIndirect::Tree3f &distance_tree,
|
||||||
const TriangleMesh & triangle_mesh,
|
const TriangleMesh & triangle_mesh,
|
||||||
int depth);
|
int depth);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FillSupportCubic : public FillAdaptive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FillSupportCubic() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual Fill* clone() const { return new FillSupportCubic(*this); };
|
||||||
|
|
||||||
|
virtual bool no_sort() const { return true; }
|
||||||
|
|
||||||
|
virtual void _fill_surface_single(
|
||||||
|
const FillParams ¶ms,
|
||||||
|
unsigned int thickness_layers,
|
||||||
|
const std::pair<float, Point> &direction,
|
||||||
|
ExPolygon &expolygon,
|
||||||
|
Polylines &polylines_out);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<FillAdaptive_Internal::Octree> build_octree(
|
||||||
|
TriangleMesh & triangle_mesh,
|
||||||
|
coordf_t line_spacing,
|
||||||
|
const Vec3d & cube_center,
|
||||||
|
const Transform3d &rotation_matrix);
|
||||||
|
};
|
||||||
|
|
||||||
// Calculate line spacing for
|
// Calculate line spacing for
|
||||||
// 1) adaptive cubic infill
|
// 1) adaptive cubic infill
|
||||||
// 2) adaptive internal support cubic infill
|
// 2) adaptive internal support cubic infill
|
||||||
|
@ -39,6 +39,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
|||||||
case ipHilbertCurve: return new FillHilbertCurve();
|
case ipHilbertCurve: return new FillHilbertCurve();
|
||||||
case ipOctagramSpiral: return new FillOctagramSpiral();
|
case ipOctagramSpiral: return new FillOctagramSpiral();
|
||||||
case ipAdaptiveCubic: return new FillAdaptive();
|
case ipAdaptiveCubic: return new FillAdaptive();
|
||||||
|
case ipSupportCubic: return new FillSupportCubic();
|
||||||
default: throw std::invalid_argument("unknown type");
|
default: throw std::invalid_argument("unknown type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,10 @@ public:
|
|||||||
// In scaled coordinates. Bounding box of the 2D projection of the object.
|
// In scaled coordinates. Bounding box of the 2D projection of the object.
|
||||||
BoundingBox bounding_box;
|
BoundingBox bounding_box;
|
||||||
|
|
||||||
|
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||||
FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr;
|
FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr;
|
||||||
|
// Octree builds on mesh for usage in the support cubic infill
|
||||||
|
FillAdaptive_Internal::Octree* support_fill_octree = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~Fill() {}
|
virtual ~Fill() {}
|
||||||
|
@ -138,8 +138,8 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void make_perimeters();
|
void make_perimeters();
|
||||||
void make_fills() { this->make_fills(nullptr); };
|
void make_fills() { this->make_fills(nullptr, nullptr); };
|
||||||
void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree);
|
void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree);
|
||||||
void make_ironing();
|
void make_ironing();
|
||||||
|
|
||||||
void export_region_slices_to_svg(const char *path) const;
|
void export_region_slices_to_svg(const char *path) const;
|
||||||
|
140
src/libslic3r/Optimize/BruteforceOptimizer.hpp
Normal file
140
src/libslic3r/Optimize/BruteforceOptimizer.hpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#ifndef BRUTEFORCEOPTIMIZER_HPP
|
||||||
|
#define BRUTEFORCEOPTIMIZER_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace opt {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
// Implementing a bruteforce optimizer
|
||||||
|
|
||||||
|
// Return the number of iterations needed to reach a specific grid position (idx)
|
||||||
|
template<size_t N>
|
||||||
|
long num_iter(const std::array<size_t, N> &idx, size_t gridsz)
|
||||||
|
{
|
||||||
|
long ret = 0;
|
||||||
|
for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of a grid search where the search interval is sampled in
|
||||||
|
// equidistant points for each dimension. Grid size determines the number of
|
||||||
|
// samples for one dimension so the number of function calls is gridsize ^ dimension.
|
||||||
|
struct AlgBurteForce {
|
||||||
|
bool to_min;
|
||||||
|
StopCriteria stc;
|
||||||
|
size_t gridsz;
|
||||||
|
|
||||||
|
AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {}
|
||||||
|
|
||||||
|
// This function is called recursively for each dimension and generates
|
||||||
|
// the grid values for the particular dimension. If D is less than zero,
|
||||||
|
// the object function input values are generated for each dimension and it
|
||||||
|
// can be evaluated. The current best score is compared with the newly
|
||||||
|
// returned score and changed appropriately.
|
||||||
|
template<int D, size_t N, class Fn, class Cmp>
|
||||||
|
bool run(std::array<size_t, N> &idx,
|
||||||
|
Result<N> &result,
|
||||||
|
const Bounds<N> &bounds,
|
||||||
|
Fn &&fn,
|
||||||
|
Cmp &&cmp)
|
||||||
|
{
|
||||||
|
if (stc.stop_condition()) return false;
|
||||||
|
|
||||||
|
if constexpr (D < 0) { // Let's evaluate fn
|
||||||
|
Input<N> inp;
|
||||||
|
|
||||||
|
auto max_iter = stc.max_iterations();
|
||||||
|
if (max_iter && num_iter(idx, gridsz) >= max_iter)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t d = 0; d < N; ++d) {
|
||||||
|
const Bound &b = bounds[d];
|
||||||
|
double step = (b.max() - b.min()) / (gridsz - 1);
|
||||||
|
inp[d] = b.min() + idx[d] * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto score = fn(inp);
|
||||||
|
if (cmp(score, result.score)) { // Change current score to the new
|
||||||
|
double absdiff = std::abs(score - result.score);
|
||||||
|
|
||||||
|
result.score = score;
|
||||||
|
result.optimum = inp;
|
||||||
|
|
||||||
|
// Check if the required precision is reached.
|
||||||
|
if (absdiff < stc.abs_score_diff() ||
|
||||||
|
absdiff < stc.rel_score_diff() * std::abs(score))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < gridsz; ++i) {
|
||||||
|
idx[D] = i; // Mark the current grid position and dig down
|
||||||
|
if (!run<D - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||||
|
std::forward<Cmp>(cmp)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Fn, size_t N>
|
||||||
|
Result<N> optimize(Fn&& fn,
|
||||||
|
const Input<N> &/*initvals*/,
|
||||||
|
const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
std::array<size_t, N> idx = {};
|
||||||
|
Result<N> result;
|
||||||
|
|
||||||
|
if (to_min) {
|
||||||
|
result.score = std::numeric_limits<double>::max();
|
||||||
|
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||||
|
std::less<double>{});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.score = std::numeric_limits<double>::lowest();
|
||||||
|
run<int(N) - 1>(idx, result, bounds, std::forward<Fn>(fn),
|
||||||
|
std::greater<double>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
using AlgBruteForce = detail::AlgBurteForce;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class Optimizer<AlgBruteForce> {
|
||||||
|
AlgBruteForce m_alg;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100)
|
||||||
|
: m_alg{cr, gridsz}
|
||||||
|
{}
|
||||||
|
|
||||||
|
Optimizer& to_max() { m_alg.to_min = false; return *this; }
|
||||||
|
Optimizer& to_min() { m_alg.to_min = true; return *this; }
|
||||||
|
|
||||||
|
template<class Func, size_t N>
|
||||||
|
Result<N> optimize(Func&& func,
|
||||||
|
const Input<N> &initvals,
|
||||||
|
const Bounds<N>& bounds)
|
||||||
|
{
|
||||||
|
return m_alg.optimize(std::forward<Func>(func), initvals, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optimizer &set_criteria(const StopCriteria &cr)
|
||||||
|
{
|
||||||
|
m_alg.stc = cr; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StopCriteria &get_criteria() const { return m_alg.stc; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::opt
|
||||||
|
|
||||||
|
#endif // BRUTEFORCEOPTIMIZER_HPP
|
@ -12,134 +12,11 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <tuple>
|
|
||||||
#include <array>
|
#include <libslic3r/Optimize/Optimizer.hpp>
|
||||||
#include <cmath>
|
|
||||||
#include <functional>
|
|
||||||
#include <limits>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace Slic3r { namespace opt {
|
namespace Slic3r { namespace opt {
|
||||||
|
|
||||||
// A type to hold the complete result of the optimization.
|
|
||||||
template<size_t N> struct Result {
|
|
||||||
int resultcode;
|
|
||||||
std::array<double, N> optimum;
|
|
||||||
double score;
|
|
||||||
};
|
|
||||||
|
|
||||||
// An interval of possible input values for optimization
|
|
||||||
class Bound {
|
|
||||||
double m_min, m_max;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Bound(double min = std::numeric_limits<double>::min(),
|
|
||||||
double max = std::numeric_limits<double>::max())
|
|
||||||
: m_min(min), m_max(max)
|
|
||||||
{}
|
|
||||||
|
|
||||||
double min() const noexcept { return m_min; }
|
|
||||||
double max() const noexcept { return m_max; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper types for optimization function input and bounds
|
|
||||||
template<size_t N> using Input = std::array<double, N>;
|
|
||||||
template<size_t N> using Bounds = std::array<Bound, N>;
|
|
||||||
|
|
||||||
// A type for specifying the stop criteria. Setter methods can be concatenated
|
|
||||||
class StopCriteria {
|
|
||||||
|
|
||||||
// If the absolute value difference between two scores.
|
|
||||||
double m_abs_score_diff = std::nan("");
|
|
||||||
|
|
||||||
// If the relative value difference between two scores.
|
|
||||||
double m_rel_score_diff = std::nan("");
|
|
||||||
|
|
||||||
// Stop if this value or better is found.
|
|
||||||
double m_stop_score = std::nan("");
|
|
||||||
|
|
||||||
// A predicate that if evaluates to true, the optimization should terminate
|
|
||||||
// and the best result found prior to termination should be returned.
|
|
||||||
std::function<bool()> m_stop_condition = [] { return false; };
|
|
||||||
|
|
||||||
// The max allowed number of iterations.
|
|
||||||
unsigned m_max_iterations = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
StopCriteria & abs_score_diff(double val)
|
|
||||||
{
|
|
||||||
m_abs_score_diff = val; return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
double abs_score_diff() const { return m_abs_score_diff; }
|
|
||||||
|
|
||||||
StopCriteria & rel_score_diff(double val)
|
|
||||||
{
|
|
||||||
m_rel_score_diff = val; return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
double rel_score_diff() const { return m_rel_score_diff; }
|
|
||||||
|
|
||||||
StopCriteria & stop_score(double val)
|
|
||||||
{
|
|
||||||
m_stop_score = val; return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
double stop_score() const { return m_stop_score; }
|
|
||||||
|
|
||||||
StopCriteria & max_iterations(double val)
|
|
||||||
{
|
|
||||||
m_max_iterations = val; return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
double max_iterations() const { return m_max_iterations; }
|
|
||||||
|
|
||||||
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
|
||||||
{
|
|
||||||
m_stop_condition = cond; return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool stop_condition() { return m_stop_condition(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper class to use optimization methods involving gradient.
|
|
||||||
template<size_t N> struct ScoreGradient {
|
|
||||||
double score;
|
|
||||||
std::optional<std::array<double, N>> gradient;
|
|
||||||
|
|
||||||
ScoreGradient(double s, const std::array<double, N> &grad)
|
|
||||||
: score{s}, gradient{grad}
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper to be used in static_assert.
|
|
||||||
template<class T> struct always_false { enum { value = false }; };
|
|
||||||
|
|
||||||
// Basic interface to optimizer object
|
|
||||||
template<class Method, class Enable = void> class Optimizer {
|
|
||||||
public:
|
|
||||||
|
|
||||||
Optimizer(const StopCriteria &)
|
|
||||||
{
|
|
||||||
static_assert (always_false<Method>::value,
|
|
||||||
"Optimizer unimplemented for given method!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Optimizer<Method> &to_min() { return *this; }
|
|
||||||
Optimizer<Method> &to_max() { return *this; }
|
|
||||||
Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
|
|
||||||
StopCriteria get_criteria() const { return {}; };
|
|
||||||
|
|
||||||
template<class Func, size_t N>
|
|
||||||
Result<N> optimize(Func&& func,
|
|
||||||
const Input<N> &initvals,
|
|
||||||
const Bounds<N>& bounds) { return {}; }
|
|
||||||
|
|
||||||
// optional for randomized methods:
|
|
||||||
void seed(long /*s*/) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Helper types for NLopt algorithm selection in template contexts
|
// Helper types for NLopt algorithm selection in template contexts
|
||||||
@ -166,19 +43,6 @@ struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
|||||||
template<class M, class T = void>
|
template<class M, class T = void>
|
||||||
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
||||||
|
|
||||||
// Helper to convert C style array to std::array. The copy should be optimized
|
|
||||||
// away with modern compilers.
|
|
||||||
template<size_t N, class T> auto to_arr(const T *a)
|
|
||||||
{
|
|
||||||
std::array<T, N> r;
|
|
||||||
std::copy(a, a + N, std::begin(r));
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
|
||||||
{
|
|
||||||
return to_arr<N>(static_cast<const T *>(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class OptDir { MIN, MAX }; // Where to optimize
|
enum class OptDir { MIN, MAX }; // Where to optimize
|
||||||
|
|
||||||
@ -357,23 +221,12 @@ public:
|
|||||||
void seed(long s) { m_opt.seed(s); }
|
void seed(long s) { m_opt.seed(s); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
// Predefinded NLopt algorithms
|
||||||
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
|
||||||
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
|
||||||
{
|
|
||||||
return ScoreGradient<N>(s, detail::to_arr(grad));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Predefinded NLopt algorithms that are used in the codebase
|
|
||||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||||
|
using AlgNLoptDIRECT = detail::NLoptAlg<NLOPT_GN_DIRECT>;
|
||||||
// TODO: define others if needed...
|
using AlgNLoptMLSL = detail::NLoptAlg<NLOPT_GN_MLSL>;
|
||||||
|
|
||||||
// Helper defs for pre-crafted global and local optimizers that work well.
|
|
||||||
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
|
|
||||||
using DefaultLocalOptimizer = Optimizer<AlgNLoptSubplex>;
|
|
||||||
|
|
||||||
}} // namespace Slic3r::opt
|
}} // namespace Slic3r::opt
|
||||||
|
|
182
src/libslic3r/Optimize/Optimizer.hpp
Normal file
182
src/libslic3r/Optimize/Optimizer.hpp
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
#ifndef OPTIMIZER_HPP
|
||||||
|
#define OPTIMIZER_HPP
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace opt {
|
||||||
|
|
||||||
|
// A type to hold the complete result of the optimization.
|
||||||
|
template<size_t N> struct Result {
|
||||||
|
int resultcode; // Method dependent
|
||||||
|
std::array<double, N> optimum;
|
||||||
|
double score;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An interval of possible input values for optimization
|
||||||
|
class Bound {
|
||||||
|
double m_min, m_max;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Bound(double min = std::numeric_limits<double>::min(),
|
||||||
|
double max = std::numeric_limits<double>::max())
|
||||||
|
: m_min(min), m_max(max)
|
||||||
|
{}
|
||||||
|
|
||||||
|
double min() const noexcept { return m_min; }
|
||||||
|
double max() const noexcept { return m_max; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper types for optimization function input and bounds
|
||||||
|
template<size_t N> using Input = std::array<double, N>;
|
||||||
|
template<size_t N> using Bounds = std::array<Bound, N>;
|
||||||
|
|
||||||
|
// A type for specifying the stop criteria. Setter methods can be concatenated
|
||||||
|
class StopCriteria {
|
||||||
|
|
||||||
|
// If the absolute value difference between two scores.
|
||||||
|
double m_abs_score_diff = std::nan("");
|
||||||
|
|
||||||
|
// If the relative value difference between two scores.
|
||||||
|
double m_rel_score_diff = std::nan("");
|
||||||
|
|
||||||
|
// Stop if this value or better is found.
|
||||||
|
double m_stop_score = std::nan("");
|
||||||
|
|
||||||
|
// A predicate that if evaluates to true, the optimization should terminate
|
||||||
|
// and the best result found prior to termination should be returned.
|
||||||
|
std::function<bool()> m_stop_condition = [] { return false; };
|
||||||
|
|
||||||
|
// The max allowed number of iterations.
|
||||||
|
unsigned m_max_iterations = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
StopCriteria & abs_score_diff(double val)
|
||||||
|
{
|
||||||
|
m_abs_score_diff = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double abs_score_diff() const { return m_abs_score_diff; }
|
||||||
|
|
||||||
|
StopCriteria & rel_score_diff(double val)
|
||||||
|
{
|
||||||
|
m_rel_score_diff = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rel_score_diff() const { return m_rel_score_diff; }
|
||||||
|
|
||||||
|
StopCriteria & stop_score(double val)
|
||||||
|
{
|
||||||
|
m_stop_score = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double stop_score() const { return m_stop_score; }
|
||||||
|
|
||||||
|
StopCriteria & max_iterations(double val)
|
||||||
|
{
|
||||||
|
m_max_iterations = val; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
double max_iterations() const { return m_max_iterations; }
|
||||||
|
|
||||||
|
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
||||||
|
{
|
||||||
|
m_stop_condition = cond; return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stop_condition() { return m_stop_condition(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper class to use optimization methods involving gradient.
|
||||||
|
template<size_t N> struct ScoreGradient {
|
||||||
|
double score;
|
||||||
|
std::optional<std::array<double, N>> gradient;
|
||||||
|
|
||||||
|
ScoreGradient(double s, const std::array<double, N> &grad)
|
||||||
|
: score{s}, gradient{grad}
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to be used in static_assert.
|
||||||
|
template<class T> struct always_false { enum { value = false }; };
|
||||||
|
|
||||||
|
// Basic interface to optimizer object
|
||||||
|
template<class Method, class Enable = void> class Optimizer {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Optimizer(const StopCriteria &)
|
||||||
|
{
|
||||||
|
static_assert (always_false<Method>::value,
|
||||||
|
"Optimizer unimplemented for given method!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch optimization towards function minimum
|
||||||
|
Optimizer &to_min() { return *this; }
|
||||||
|
|
||||||
|
// Switch optimization towards function maximum
|
||||||
|
Optimizer &to_max() { return *this; }
|
||||||
|
|
||||||
|
// Set criteria for successive optimizations
|
||||||
|
Optimizer &set_criteria(const StopCriteria &) { return *this; }
|
||||||
|
|
||||||
|
// Get current criteria
|
||||||
|
StopCriteria get_criteria() const { return {}; };
|
||||||
|
|
||||||
|
// Find function minimum or maximum for Func which has has signature:
|
||||||
|
// double(const Input<N> &input) and input with dimension N
|
||||||
|
//
|
||||||
|
// Initial starting point can be given as the second parameter.
|
||||||
|
//
|
||||||
|
// For each dimension an interval (Bound) has to be given marking the bounds
|
||||||
|
// for that dimension.
|
||||||
|
//
|
||||||
|
// initvals have to be within the specified bounds, otherwise its undefined
|
||||||
|
// behavior.
|
||||||
|
//
|
||||||
|
// Func can return a score of type double or optionally a ScoreGradient
|
||||||
|
// class to indicate the function gradient for a optimization methods that
|
||||||
|
// make use of the gradient.
|
||||||
|
template<class Func, size_t N>
|
||||||
|
Result<N> optimize(Func&& /*func*/,
|
||||||
|
const Input<N> &/*initvals*/,
|
||||||
|
const Bounds<N>& /*bounds*/) { return {}; }
|
||||||
|
|
||||||
|
// optional for randomized methods:
|
||||||
|
void seed(long /*s*/) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Helper to convert C style array to std::array. The copy should be optimized
|
||||||
|
// away with modern compilers.
|
||||||
|
template<size_t N, class T> auto to_arr(const T *a)
|
||||||
|
{
|
||||||
|
std::array<T, N> r;
|
||||||
|
std::copy(a, a + N, std::begin(r));
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
||||||
|
{
|
||||||
|
return to_arr<N>(static_cast<const T *>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Helper functions to create bounds, initial value
|
||||||
|
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
||||||
|
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
||||||
|
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
||||||
|
{
|
||||||
|
return ScoreGradient<N>(s, detail::to_arr(grad));
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::opt
|
||||||
|
|
||||||
|
#endif // OPTIMIZER_HPP
|
@ -239,7 +239,7 @@ private:
|
|||||||
void discover_horizontal_shells();
|
void discover_horizontal_shells();
|
||||||
void combine_infill();
|
void combine_infill();
|
||||||
void _generate_support_material();
|
void _generate_support_material();
|
||||||
std::unique_ptr<FillAdaptive_Internal::Octree> prepare_adaptive_infill_data();
|
std::pair<std::unique_ptr<FillAdaptive_Internal::Octree>, std::unique_ptr<FillAdaptive_Internal::Octree>> prepare_adaptive_infill_data();
|
||||||
|
|
||||||
// XYZ in scaled coordinates
|
// XYZ in scaled coordinates
|
||||||
Vec3crd m_size;
|
Vec3crd m_size;
|
||||||
|
@ -882,6 +882,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->enum_values.push_back("archimedeanchords");
|
def->enum_values.push_back("archimedeanchords");
|
||||||
def->enum_values.push_back("octagramspiral");
|
def->enum_values.push_back("octagramspiral");
|
||||||
def->enum_values.push_back("adaptivecubic");
|
def->enum_values.push_back("adaptivecubic");
|
||||||
|
def->enum_values.push_back("supportcubic");
|
||||||
def->enum_labels.push_back(L("Rectilinear"));
|
def->enum_labels.push_back(L("Rectilinear"));
|
||||||
def->enum_labels.push_back(L("Grid"));
|
def->enum_labels.push_back(L("Grid"));
|
||||||
def->enum_labels.push_back(L("Triangles"));
|
def->enum_labels.push_back(L("Triangles"));
|
||||||
@ -896,6 +897,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->enum_labels.push_back(L("Archimedean Chords"));
|
def->enum_labels.push_back(L("Archimedean Chords"));
|
||||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||||
def->enum_labels.push_back(L("Adaptive Cubic"));
|
def->enum_labels.push_back(L("Adaptive Cubic"));
|
||||||
|
def->enum_labels.push_back(L("Support Cubic"));
|
||||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
|
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
|
||||||
|
|
||||||
def = this->add("first_layer_acceleration", coFloat);
|
def = this->add("first_layer_acceleration", coFloat);
|
||||||
|
@ -39,7 +39,7 @@ enum AuthorizationType {
|
|||||||
|
|
||||||
enum InfillPattern : int {
|
enum InfillPattern : int {
|
||||||
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipCount,
|
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class IroningType {
|
enum class IroningType {
|
||||||
@ -140,6 +140,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::g
|
|||||||
keys_map["archimedeanchords"] = ipArchimedeanChords;
|
keys_map["archimedeanchords"] = ipArchimedeanChords;
|
||||||
keys_map["octagramspiral"] = ipOctagramSpiral;
|
keys_map["octagramspiral"] = ipOctagramSpiral;
|
||||||
keys_map["adaptivecubic"] = ipAdaptiveCubic;
|
keys_map["adaptivecubic"] = ipAdaptiveCubic;
|
||||||
|
keys_map["supportcubic"] = ipSupportCubic;
|
||||||
}
|
}
|
||||||
return keys_map;
|
return keys_map;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "AABBTreeIndirect.hpp"
|
#include "AABBTreeIndirect.hpp"
|
||||||
#include "Fill/FillAdaptive.hpp"
|
#include "Fill/FillAdaptive.hpp"
|
||||||
|
#include "Format/STL.hpp"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
@ -371,15 +372,15 @@ void PrintObject::infill()
|
|||||||
this->prepare_infill();
|
this->prepare_infill();
|
||||||
|
|
||||||
if (this->set_started(posInfill)) {
|
if (this->set_started(posInfill)) {
|
||||||
std::unique_ptr<FillAdaptive_Internal::Octree> octree = this->prepare_adaptive_infill_data();
|
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
||||||
tbb::parallel_for(
|
tbb::parallel_for(
|
||||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||||
[this, &octree](const tbb::blocked_range<size_t>& range) {
|
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
m_layers[layer_idx]->make_fills(octree.get());
|
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -432,11 +433,18 @@ void PrintObject::generate_support_material()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<FillAdaptive_Internal::Octree> PrintObject::prepare_adaptive_infill_data()
|
//#define ADAPTIVE_SUPPORT_SIMPLE
|
||||||
|
|
||||||
|
std::pair<std::unique_ptr<FillAdaptive_Internal::Octree>, std::unique_ptr<FillAdaptive_Internal::Octree>> PrintObject::prepare_adaptive_infill_data()
|
||||||
{
|
{
|
||||||
|
using namespace FillAdaptive_Internal;
|
||||||
|
|
||||||
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
|
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
|
||||||
if (adaptive_line_spacing == 0.)
|
|
||||||
return std::unique_ptr<FillAdaptive_Internal::Octree>{};
|
std::unique_ptr<Octree> adaptive_fill_octree = {}, support_fill_octree = {};
|
||||||
|
|
||||||
|
if (adaptive_line_spacing == 0. && support_line_spacing == 0.)
|
||||||
|
return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree));
|
||||||
|
|
||||||
TriangleMesh mesh = this->model_object()->raw_mesh();
|
TriangleMesh mesh = this->model_object()->raw_mesh();
|
||||||
mesh.transform(m_trafo, true);
|
mesh.transform(m_trafo, true);
|
||||||
@ -444,7 +452,55 @@ std::unique_ptr<FillAdaptive_Internal::Octree> PrintObject::prepare_adaptive_inf
|
|||||||
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
|
mesh.translate(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0);
|
||||||
// Center of the first cube in octree
|
// Center of the first cube in octree
|
||||||
Vec3d mesh_origin = mesh.bounding_box().center();
|
Vec3d mesh_origin = mesh.bounding_box().center();
|
||||||
return FillAdaptive::build_octree(mesh, adaptive_line_spacing, mesh_origin);
|
|
||||||
|
#ifdef ADAPTIVE_SUPPORT_SIMPLE
|
||||||
|
if (mesh.its.vertices.empty())
|
||||||
|
{
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3f vertical(0, 0, 1);
|
||||||
|
|
||||||
|
indexed_triangle_set its_set;
|
||||||
|
its_set.vertices = mesh.its.vertices;
|
||||||
|
|
||||||
|
// Filter out non overhanging faces
|
||||||
|
for (size_t i = 0; i < mesh.its.indices.size(); ++i) {
|
||||||
|
stl_triangle_vertex_indices vertex_idx = mesh.its.indices[i];
|
||||||
|
|
||||||
|
auto its_calculate_normal = [](const stl_triangle_vertex_indices &index, const std::vector<stl_vertex> &vertices) {
|
||||||
|
stl_normal normal = (vertices[index.y()] - vertices[index.x()]).cross(vertices[index.z()] - vertices[index.x()]);
|
||||||
|
return normal;
|
||||||
|
};
|
||||||
|
|
||||||
|
stl_normal normal = its_calculate_normal(vertex_idx, mesh.its.vertices);
|
||||||
|
stl_normalize_vector(normal);
|
||||||
|
|
||||||
|
if(normal.dot(vertical) >= 0.707) {
|
||||||
|
its_set.indices.push_back(vertex_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh = TriangleMesh(its_set);
|
||||||
|
|
||||||
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
|
Slic3r::store_stl(debug_out_path("overhangs.stl").c_str(), &mesh, false);
|
||||||
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
#endif /* ADAPTIVE_SUPPORT_SIMPLE */
|
||||||
|
|
||||||
|
Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0);
|
||||||
|
Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse();
|
||||||
|
|
||||||
|
if (adaptive_line_spacing != 0.) {
|
||||||
|
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes
|
||||||
|
mesh.transform(rotation_matrix);
|
||||||
|
adaptive_fill_octree = FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (support_line_spacing != 0.)
|
||||||
|
support_fill_octree = FillSupportCubic::build_octree(mesh, support_line_spacing, rotation_matrix * mesh_origin, rotation_matrix);
|
||||||
|
|
||||||
|
return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintObject::clear_layers()
|
void PrintObject::clear_layers()
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
#include <tbb/spin_mutex.h>
|
#include <tbb/spin_mutex.h>
|
||||||
#include <tbb/mutex.h>
|
#include <tbb/mutex.h>
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
|
#include <tbb/parallel_reduce.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include <libslic3r/libslic3r.h>
|
#include <libslic3r/libslic3r.h>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@ -21,28 +25,56 @@ template<> struct _ccr<true>
|
|||||||
using SpinningMutex = tbb::spin_mutex;
|
using SpinningMutex = tbb::spin_mutex;
|
||||||
using BlockingMutex = tbb::mutex;
|
using BlockingMutex = tbb::mutex;
|
||||||
|
|
||||||
|
template<class Fn, class It>
|
||||||
|
static IteratorOnly<It, void> loop_(const tbb::blocked_range<It> &range, Fn &&fn)
|
||||||
|
{
|
||||||
|
for (auto &el : range) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Fn, class I>
|
||||||
|
static IntegerOnly<I, void> loop_(const tbb::blocked_range<I> &range, Fn &&fn)
|
||||||
|
{
|
||||||
|
for (I i = range.begin(); i < range.end(); ++i) fn(i);
|
||||||
|
}
|
||||||
|
|
||||||
template<class It, class Fn>
|
template<class It, class Fn>
|
||||||
static IteratorOnly<It, void> for_each(It from,
|
static void for_each(It from, It to, Fn &&fn, size_t granularity = 1)
|
||||||
It to,
|
|
||||||
Fn && fn,
|
|
||||||
size_t granularity = 1)
|
|
||||||
{
|
{
|
||||||
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
||||||
[&fn, from](const auto &range) {
|
[&fn, from](const auto &range) {
|
||||||
for (auto &el : range) fn(el);
|
loop_(range, std::forward<Fn>(fn));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class I, class Fn>
|
template<class I, class MergeFn, class T, class AccessFn>
|
||||||
static IntegerOnly<I, void> for_each(I from,
|
static T reduce(I from,
|
||||||
I to,
|
I to,
|
||||||
Fn && fn,
|
const T &init,
|
||||||
size_t granularity = 1)
|
MergeFn &&mergefn,
|
||||||
|
AccessFn &&access,
|
||||||
|
size_t granularity = 1
|
||||||
|
)
|
||||||
{
|
{
|
||||||
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
return tbb::parallel_reduce(
|
||||||
[&fn](const auto &range) {
|
tbb::blocked_range{from, to, granularity}, init,
|
||||||
for (I i = range.begin(); i < range.end(); ++i) fn(i);
|
[&](const auto &range, T subinit) {
|
||||||
});
|
T acc = subinit;
|
||||||
|
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
std::forward<MergeFn>(mergefn));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class I, class MergeFn, class T>
|
||||||
|
static IteratorOnly<I, T> reduce(I from,
|
||||||
|
I to,
|
||||||
|
const T & init,
|
||||||
|
MergeFn &&mergefn,
|
||||||
|
size_t granularity = 1)
|
||||||
|
{
|
||||||
|
return reduce(
|
||||||
|
from, to, init, std::forward<MergeFn>(mergefn),
|
||||||
|
[](typename I::value_type &i) { return i; }, granularity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,23 +87,52 @@ public:
|
|||||||
using SpinningMutex = _Mtx;
|
using SpinningMutex = _Mtx;
|
||||||
using BlockingMutex = _Mtx;
|
using BlockingMutex = _Mtx;
|
||||||
|
|
||||||
template<class It, class Fn>
|
template<class Fn, class It>
|
||||||
static IteratorOnly<It, void> for_each(It from,
|
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
|
||||||
It to,
|
|
||||||
Fn &&fn,
|
|
||||||
size_t /* ignore granularity */ = 1)
|
|
||||||
{
|
{
|
||||||
for (auto it = from; it != to; ++it) fn(*it);
|
for (auto it = from; it != to; ++it) fn(*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class I, class Fn>
|
template<class Fn, class I>
|
||||||
static IntegerOnly<I, void> for_each(I from,
|
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
|
||||||
I to,
|
|
||||||
Fn &&fn,
|
|
||||||
size_t /* ignore granularity */ = 1)
|
|
||||||
{
|
{
|
||||||
for (I i = from; i < to; ++i) fn(i);
|
for (I i = from; i < to; ++i) fn(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class It, class Fn>
|
||||||
|
static void for_each(It from,
|
||||||
|
It to,
|
||||||
|
Fn &&fn,
|
||||||
|
size_t /* ignore granularity */ = 1)
|
||||||
|
{
|
||||||
|
loop_(from, to, std::forward<Fn>(fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class I, class MergeFn, class T, class AccessFn>
|
||||||
|
static T reduce(I from,
|
||||||
|
I to,
|
||||||
|
const T & init,
|
||||||
|
MergeFn &&mergefn,
|
||||||
|
AccessFn &&access,
|
||||||
|
size_t /*granularity*/ = 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
T acc = init;
|
||||||
|
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class I, class MergeFn, class T>
|
||||||
|
static IteratorOnly<I, T> reduce(I from,
|
||||||
|
I to,
|
||||||
|
const T &init,
|
||||||
|
MergeFn &&mergefn,
|
||||||
|
size_t /*granularity*/ = 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
|
||||||
|
[](typename I::value_type &i) { return i; });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using ccr = _ccr<USE_FULL_CONCURRENCY>;
|
using ccr = _ccr<USE_FULL_CONCURRENCY>;
|
||||||
|
@ -1,35 +1,259 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
|
||||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||||
#include <libslic3r/SLA/SupportTree.hpp>
|
#include <libslic3r/SLA/Concurrency.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||||
|
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
#include <libslic3r/Geometry.hpp>
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
#include <thread>
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
namespace Slic3r { namespace sla {
|
||||||
float accuracy,
|
|
||||||
std::function<void(unsigned)> statuscb,
|
inline bool is_on_floor(const SLAPrintObject &mo)
|
||||||
std::function<bool()> stopcond)
|
|
||||||
{
|
{
|
||||||
using libnest2d::opt::Method;
|
auto opt_elevation = mo.config().support_object_elevation.getFloat();
|
||||||
using libnest2d::opt::bound;
|
auto opt_padaround = mo.config().pad_around_object.getBool();
|
||||||
using libnest2d::opt::Optimizer;
|
|
||||||
using libnest2d::opt::TOptimizer;
|
|
||||||
using libnest2d::opt::StopCriteria;
|
|
||||||
|
|
||||||
static const unsigned MAX_TRIES = 100000;
|
return opt_elevation < EPSILON || opt_padaround;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find transformed mesh ground level without copy and with parallel reduce.
|
||||||
|
double find_ground_level(const TriangleMesh &mesh,
|
||||||
|
const Transform3d & tr,
|
||||||
|
size_t threads)
|
||||||
|
{
|
||||||
|
size_t vsize = mesh.its.vertices.size();
|
||||||
|
|
||||||
|
auto minfn = [](double a, double b) { return std::min(a, b); };
|
||||||
|
|
||||||
|
auto accessfn = [&mesh, &tr] (size_t vi) {
|
||||||
|
return (tr * mesh.its.vertices[vi].template cast<double>()).z();
|
||||||
|
};
|
||||||
|
|
||||||
|
double zmin = std::numeric_limits<double>::max();
|
||||||
|
size_t granularity = vsize / threads;
|
||||||
|
return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the vertices of a triangle directly in an array of 3 points
|
||||||
|
std::array<Vec3d, 3> get_triangle_vertices(const TriangleMesh &mesh,
|
||||||
|
size_t faceidx)
|
||||||
|
{
|
||||||
|
const auto &face = mesh.its.indices[faceidx];
|
||||||
|
return {Vec3d{mesh.its.vertices[face(0)].cast<double>()},
|
||||||
|
Vec3d{mesh.its.vertices[face(1)].cast<double>()},
|
||||||
|
Vec3d{mesh.its.vertices[face(2)].cast<double>()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<Vec3d, 3> get_transformed_triangle(const TriangleMesh &mesh,
|
||||||
|
const Transform3d & tr,
|
||||||
|
size_t faceidx)
|
||||||
|
{
|
||||||
|
const auto &tri = get_triangle_vertices(mesh, faceidx);
|
||||||
|
return {tr * tri[0], tr * tri[1], tr * tri[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get area and normal of a triangle
|
||||||
|
struct Facestats {
|
||||||
|
Vec3d normal;
|
||||||
|
double area;
|
||||||
|
|
||||||
|
explicit Facestats(const std::array<Vec3d, 3> &triangle)
|
||||||
|
{
|
||||||
|
Vec3d U = triangle[1] - triangle[0];
|
||||||
|
Vec3d V = triangle[2] - triangle[0];
|
||||||
|
Vec3d C = U.cross(V);
|
||||||
|
normal = C.normalized();
|
||||||
|
area = 0.5 * C.norm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const Vec3d DOWN = {0., 0., -1.};
|
||||||
|
constexpr double POINTS_PER_UNIT_AREA = 1.;
|
||||||
|
|
||||||
|
// The score function for a particular face
|
||||||
|
inline double get_score(const Facestats &fc)
|
||||||
|
{
|
||||||
|
// Simply get the angle (acos of dot product) between the face normal and
|
||||||
|
// the DOWN vector.
|
||||||
|
double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI;
|
||||||
|
|
||||||
|
// Only consider faces that have have slopes below 90 deg:
|
||||||
|
phi = phi * (phi > 0.5);
|
||||||
|
|
||||||
|
// Make the huge slopes more significant than the smaller slopes
|
||||||
|
phi = phi * phi * phi;
|
||||||
|
|
||||||
|
// Multiply with the area of the current face
|
||||||
|
return fc.area * POINTS_PER_UNIT_AREA * phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class AccessFn>
|
||||||
|
double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads)
|
||||||
|
{
|
||||||
|
double initv = 0.;
|
||||||
|
auto mergefn = std::plus<double>{};
|
||||||
|
size_t grainsize = facecount / Nthreads;
|
||||||
|
size_t from = 0, to = facecount;
|
||||||
|
|
||||||
|
return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to guess the number of support points needed to support a mesh
|
||||||
|
double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr)
|
||||||
|
{
|
||||||
|
if (mesh.its.vertices.empty()) return std::nan("");
|
||||||
|
|
||||||
|
auto accessfn = [&mesh, &tr](size_t fi) {
|
||||||
|
Facestats fc{get_transformed_triangle(mesh, tr, fi)};
|
||||||
|
return get_score(fc);
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t facecount = mesh.its.indices.size();
|
||||||
|
size_t Nthreads = std::thread::hardware_concurrency();
|
||||||
|
return sum_score(accessfn, facecount, Nthreads) / facecount;
|
||||||
|
}
|
||||||
|
|
||||||
|
double get_model_supportedness_onfloor(const TriangleMesh &mesh,
|
||||||
|
const Transform3d & tr)
|
||||||
|
{
|
||||||
|
if (mesh.its.vertices.empty()) return std::nan("");
|
||||||
|
|
||||||
|
size_t Nthreads = std::thread::hardware_concurrency();
|
||||||
|
|
||||||
|
double zmin = find_ground_level(mesh, tr, Nthreads);
|
||||||
|
double zlvl = zmin + 0.1; // Set up a slight tolerance from z level
|
||||||
|
|
||||||
|
auto accessfn = [&mesh, &tr, zlvl](size_t fi) {
|
||||||
|
std::array<Vec3d, 3> tri = get_transformed_triangle(mesh, tr, fi);
|
||||||
|
Facestats fc{tri};
|
||||||
|
|
||||||
|
if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl)
|
||||||
|
return -fc.area * POINTS_PER_UNIT_AREA;
|
||||||
|
|
||||||
|
return get_score(fc);
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t facecount = mesh.its.indices.size();
|
||||||
|
return sum_score(accessfn, facecount, Nthreads) / facecount;
|
||||||
|
}
|
||||||
|
|
||||||
|
using XYRotation = std::array<double, 2>;
|
||||||
|
|
||||||
|
// prepare the rotation transformation
|
||||||
|
Transform3d to_transform3d(const XYRotation &rot)
|
||||||
|
{
|
||||||
|
Transform3d rt = Transform3d::Identity();
|
||||||
|
rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY()));
|
||||||
|
rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX()));
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
XYRotation from_transform3d(const Transform3d &tr)
|
||||||
|
{
|
||||||
|
Vec3d rot3d = Geometry::Transformation {tr}.get_rotation();
|
||||||
|
return {rot3d.x(), rot3d.y()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the best score from a set of function inputs. Evaluate for every point.
|
||||||
|
template<size_t N, class Fn, class It, class StopCond>
|
||||||
|
std::array<double, N> find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn)
|
||||||
|
{
|
||||||
|
std::array<double, N> ret;
|
||||||
|
|
||||||
|
double score = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
size_t Nthreads = std::thread::hardware_concurrency();
|
||||||
|
size_t dist = std::distance(from, to);
|
||||||
|
std::vector<double> scores(dist, score);
|
||||||
|
|
||||||
|
ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) {
|
||||||
|
if (stopfn()) return;
|
||||||
|
|
||||||
|
scores[i] = fn(*(from + i));
|
||||||
|
}, dist / Nthreads);
|
||||||
|
|
||||||
|
auto it = std::min_element(scores.begin(), scores.end());
|
||||||
|
|
||||||
|
if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect the rotations for each face of the convex hull
|
||||||
|
std::vector<XYRotation> get_chull_rotations(const TriangleMesh &mesh, size_t max_count)
|
||||||
|
{
|
||||||
|
TriangleMesh chull = mesh.convex_hull_3d();
|
||||||
|
chull.require_shared_vertices();
|
||||||
|
double chull2d_area = chull.convex_hull().area();
|
||||||
|
double area_threshold = chull2d_area / (scaled<double>(1e3) * scaled(1.));
|
||||||
|
|
||||||
|
size_t facecount = chull.its.indices.size();
|
||||||
|
|
||||||
|
struct RotArea { XYRotation rot; double area; };
|
||||||
|
|
||||||
|
auto inputs = reserve_vector<RotArea>(facecount);
|
||||||
|
|
||||||
|
auto rotcmp = [](const RotArea &r1, const RotArea &r2) {
|
||||||
|
double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y];
|
||||||
|
return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||||
|
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||||
|
return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t fi = 0; fi < facecount; ++fi) {
|
||||||
|
Facestats fc{get_triangle_vertices(chull, fi)};
|
||||||
|
|
||||||
|
if (fc.area > area_threshold) {
|
||||||
|
auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN);
|
||||||
|
XYRotation rot = from_transform3d(Transform3d::Identity() * q);
|
||||||
|
RotArea ra = {rot, fc.area};
|
||||||
|
|
||||||
|
auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp);
|
||||||
|
|
||||||
|
if (it == inputs.end() || !eqcmp(it->rot, rot))
|
||||||
|
inputs.insert(it, ra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.shrink_to_fit();
|
||||||
|
if (!max_count) max_count = inputs.size();
|
||||||
|
std::sort(inputs.begin(), inputs.end(),
|
||||||
|
[](const RotArea &ra, const RotArea &rb) {
|
||||||
|
return ra.area > rb.area;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto ret = reserve_vector<XYRotation>(std::min(max_count, inputs.size()));
|
||||||
|
for (const RotArea &ra : inputs) ret.emplace_back(ra.rot);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2d find_best_rotation(const SLAPrintObject & po,
|
||||||
|
float accuracy,
|
||||||
|
std::function<void(unsigned)> statuscb,
|
||||||
|
std::function<bool()> stopcond)
|
||||||
|
{
|
||||||
|
static const unsigned MAX_TRIES = 1000;
|
||||||
|
|
||||||
// return value
|
// return value
|
||||||
std::array<double, 3> rot;
|
XYRotation rot;
|
||||||
|
|
||||||
// We will use only one instance of this converted mesh to examine different
|
// We will use only one instance of this converted mesh to examine different
|
||||||
// rotations
|
// rotations
|
||||||
const TriangleMesh& mesh = modelobj.raw_mesh();
|
TriangleMesh mesh = po.model_object()->raw_mesh();
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
|
||||||
// For current iteration number
|
// To keep track of the number of iterations
|
||||||
unsigned status = 0;
|
unsigned status = 0;
|
||||||
|
|
||||||
// The maximum number of iterations
|
// The maximum number of iterations
|
||||||
@ -38,77 +262,61 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
|||||||
// call status callback with zero, because we are at the start
|
// call status callback with zero, because we are at the start
|
||||||
statuscb(status);
|
statuscb(status);
|
||||||
|
|
||||||
// So this is the object function which is called by the solver many times
|
auto statusfn = [&statuscb, &status, &max_tries] {
|
||||||
// It has to yield a single value representing the current score. We will
|
|
||||||
// call the status callback in each iteration but the actual value may be
|
|
||||||
// the same for subsequent iterations (status goes from 0 to 100 but
|
|
||||||
// iterations can be many more)
|
|
||||||
auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries]
|
|
||||||
(double rx, double ry, double rz)
|
|
||||||
{
|
|
||||||
const TriangleMesh& m = mesh;
|
|
||||||
|
|
||||||
// prepare the rotation transformation
|
|
||||||
Transform3d rt = Transform3d::Identity();
|
|
||||||
|
|
||||||
rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ()));
|
|
||||||
rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY()));
|
|
||||||
rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX()));
|
|
||||||
|
|
||||||
double score = 0;
|
|
||||||
|
|
||||||
// For all triangles we calculate the normal and sum up the dot product
|
|
||||||
// (a scalar indicating how much are two vectors aligned) with each axis
|
|
||||||
// this will result in a value that is greater if a normal is aligned
|
|
||||||
// with all axes. If the normal is aligned than the triangle itself is
|
|
||||||
// orthogonal to the axes and that is good for print quality.
|
|
||||||
|
|
||||||
// TODO: some applications optimize for minimum z-axis cross section
|
|
||||||
// area. The current function is only an example of how to optimize.
|
|
||||||
|
|
||||||
// Later we can add more criteria like the number of overhangs, etc...
|
|
||||||
for(size_t i = 0; i < m.stl.facet_start.size(); i++) {
|
|
||||||
Vec3d n = m.stl.facet_start[i].normal.cast<double>();
|
|
||||||
|
|
||||||
// rotate the normal with the current rotation given by the solver
|
|
||||||
n = rt * n;
|
|
||||||
|
|
||||||
// We should score against the alignment with the reference planes
|
|
||||||
score += std::abs(n.dot(Vec3d::UnitX()));
|
|
||||||
score += std::abs(n.dot(Vec3d::UnitY()));
|
|
||||||
score += std::abs(n.dot(Vec3d::UnitZ()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// report status
|
// report status
|
||||||
if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) );
|
statuscb(unsigned(++status * 100.0/max_tries) );
|
||||||
|
|
||||||
return score;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Firing up the genetic optimizer. For now it uses the nlopt library.
|
// Different search methods have to be used depending on the model elevation
|
||||||
StopCriteria stc;
|
if (is_on_floor(po)) {
|
||||||
stc.max_iterations = max_tries;
|
|
||||||
stc.relative_score_difference = 1e-3;
|
|
||||||
stc.stop_condition = stopcond; // stop when stopcond returns true
|
|
||||||
TOptimizer<Method::G_GENETIC> solver(stc);
|
|
||||||
|
|
||||||
// We are searching rotations around the three axes x, y, z. Thus the
|
std::vector<XYRotation> inputs = get_chull_rotations(mesh, max_tries);
|
||||||
// problem becomes a 3 dimensional optimization task.
|
max_tries = inputs.size();
|
||||||
// We can specify the bounds for a dimension in the following way:
|
|
||||||
auto b = bound(-PI/2, PI/2);
|
|
||||||
|
|
||||||
// Now we start the optimization process with initial angles (0, 0, 0)
|
// If the model can be placed on the bed directly, we only need to
|
||||||
auto result = solver.optimize_max(objfunc,
|
// check the 3D convex hull face rotations.
|
||||||
libnest2d::opt::initvals(0.0, 0.0, 0.0),
|
|
||||||
b, b, b);
|
|
||||||
|
|
||||||
// Save the result and fck off
|
auto objfn = [&mesh, &statusfn](const XYRotation &rot) {
|
||||||
rot[0] = std::get<0>(result.optimum);
|
statusfn();
|
||||||
rot[1] = std::get<1>(result.optimum);
|
Transform3d tr = to_transform3d(rot);
|
||||||
rot[2] = std::get<2>(result.optimum);
|
return get_model_supportedness_onfloor(mesh, tr);
|
||||||
|
};
|
||||||
|
|
||||||
return rot;
|
rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond);
|
||||||
|
} else {
|
||||||
|
// Preparing the optimizer.
|
||||||
|
size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls
|
||||||
|
opt::Optimizer<opt::AlgBruteForce> solver(opt::StopCriteria{}
|
||||||
|
.max_iterations(max_tries)
|
||||||
|
.stop_condition(stopcond),
|
||||||
|
gridsize);
|
||||||
|
|
||||||
|
// We are searching rotations around only two axes x, y. Thus the
|
||||||
|
// problem becomes a 2 dimensional optimization task.
|
||||||
|
// We can specify the bounds for a dimension in the following way:
|
||||||
|
auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} });
|
||||||
|
|
||||||
|
auto result = solver.to_min().optimize(
|
||||||
|
[&mesh, &statusfn] (const XYRotation &rot)
|
||||||
|
{
|
||||||
|
statusfn();
|
||||||
|
return get_model_supportedness(mesh, to_transform3d(rot));
|
||||||
|
}, opt::initvals({0., 0.}), bounds);
|
||||||
|
|
||||||
|
// Save the result and fck off
|
||||||
|
rot = result.optimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {rot[0], rot[1]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr)
|
||||||
|
{
|
||||||
|
TriangleMesh mesh = po.model_object()->raw_mesh();
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
|
||||||
|
return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) :
|
||||||
|
get_model_supportedness(mesh, tr);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
@ -4,9 +4,11 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
|
#include <libslic3r/Point.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ModelObject;
|
class SLAPrintObject;
|
||||||
|
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
@ -25,14 +27,17 @@ namespace sla {
|
|||||||
*
|
*
|
||||||
* @return Returns the rotations around each axis (x, y, z)
|
* @return Returns the rotations around each axis (x, y, z)
|
||||||
*/
|
*/
|
||||||
std::array<double, 3> find_best_rotation(
|
Vec2d find_best_rotation(
|
||||||
const ModelObject& modelobj,
|
const SLAPrintObject& modelobj,
|
||||||
float accuracy = 1.0f,
|
float accuracy = 1.0f,
|
||||||
std::function<void(unsigned)> statuscb = [] (unsigned) {},
|
std::function<void(unsigned)> statuscb = [] (unsigned) {},
|
||||||
std::function<bool()> stopcond = [] () { return false; }
|
std::function<bool()> stopcond = [] () { return false; }
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
double get_model_supportedness(const SLAPrintObject &mesh,
|
||||||
}
|
const Transform3d & tr);
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // SLAROTFINDER_HPP
|
#endif // SLAROTFINDER_HPP
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||||
|
|
||||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
#include <libslic3r/Optimizer.hpp>
|
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
@ -70,7 +70,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3i> &fac
|
|||||||
stl_get_size(&stl);
|
stl_get_size(&stl);
|
||||||
}
|
}
|
||||||
|
|
||||||
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
|
TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false)
|
||||||
{
|
{
|
||||||
stl.stats.type = inmemory;
|
stl.stats.type = inmemory;
|
||||||
|
|
||||||
|
@ -78,172 +78,229 @@ namespace GUI {
|
|||||||
|
|
||||||
class MainFrame;
|
class MainFrame;
|
||||||
|
|
||||||
static float get_scale_for_main_display()
|
|
||||||
{
|
|
||||||
// ysFIXME : Workaround :
|
|
||||||
// wxFrame is created on the main monitor, so we can take a scale factor from this one
|
|
||||||
// before The Application and the Mainframe are created
|
|
||||||
wxFrame fr(nullptr, wxID_ANY, wxEmptyString);
|
|
||||||
|
|
||||||
#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
|
|
||||||
int dpi = get_dpi_for_window(&fr);
|
|
||||||
float sf = dpi != DPI_DEFAULT ? sf = (float)dpi / DPI_DEFAULT : 1.0;
|
|
||||||
#else
|
|
||||||
printf("dpi = %d\n", get_dpi_for_window(&fr));
|
|
||||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
|
||||||
float sf = 0.1 * std::max<size_t>(10, fr.GetTextExtent("m").x - 1);
|
|
||||||
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
|
|
||||||
|
|
||||||
printf("scale factor = %f\n", sf);
|
|
||||||
return sf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale input bitmap and return scale factor
|
|
||||||
static float scale_bitmap(wxBitmap& bmp)
|
|
||||||
{
|
|
||||||
float sf = get_scale_for_main_display();
|
|
||||||
|
|
||||||
// scale bitmap if needed
|
|
||||||
if (sf > 1.0) {
|
|
||||||
wxImage image = bmp.ConvertToImage();
|
|
||||||
if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0)
|
|
||||||
{
|
|
||||||
int width = int(sf * image.GetWidth());
|
|
||||||
int height = int(sf * image.GetHeight());
|
|
||||||
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
|
||||||
|
|
||||||
bmp = wxBitmap(std::move(image));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void word_wrap_string(wxString& input, int line_px_len, float scalef)
|
|
||||||
{
|
|
||||||
// calculate count od symbols in one line according to the scale
|
|
||||||
int line_len = std::roundf( (float)line_px_len / (scalef * 10)) + 10;
|
|
||||||
|
|
||||||
int idx = -1;
|
|
||||||
int cur_len = 0;
|
|
||||||
for (size_t i = 0; i < input.Len(); i++)
|
|
||||||
{
|
|
||||||
cur_len++;
|
|
||||||
if (input[i] == ' ')
|
|
||||||
idx = i;
|
|
||||||
if (input[i] == '\n')
|
|
||||||
{
|
|
||||||
idx = -1;
|
|
||||||
cur_len = 0;
|
|
||||||
}
|
|
||||||
if (cur_len >= line_len && idx >= 0)
|
|
||||||
{
|
|
||||||
input[idx] = '\n';
|
|
||||||
cur_len = static_cast<int>(i) - idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DecorateSplashScreen(wxBitmap& bmp)
|
|
||||||
{
|
|
||||||
wxASSERT(bmp.IsOk());
|
|
||||||
float scale_factor = scale_bitmap(bmp);
|
|
||||||
|
|
||||||
// use a memory DC to draw directly onto the bitmap
|
|
||||||
wxMemoryDC memDc(bmp);
|
|
||||||
|
|
||||||
// draw an dark grey box at the left of the splashscreen.
|
|
||||||
// this box will be 2/5 of the weight of the bitmap, and be at the left.
|
|
||||||
int banner_width = (bmp.GetWidth() / 5) * 2 - 2;
|
|
||||||
const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight()));
|
|
||||||
wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51)));
|
|
||||||
wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51)));
|
|
||||||
memDc.DrawRectangle(banner_rect);
|
|
||||||
|
|
||||||
// title
|
|
||||||
#if ENABLE_GCODE_VIEWER
|
|
||||||
wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME;
|
|
||||||
#else
|
|
||||||
wxString title_string = SLIC3R_APP_NAME;
|
|
||||||
#endif // ENABLE_GCODE_VIEWER
|
|
||||||
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
||||||
title_font.SetPointSize(24);
|
|
||||||
|
|
||||||
// dynamically get the version to display
|
|
||||||
wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION);
|
|
||||||
wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger().Larger();
|
|
||||||
|
|
||||||
// create a copyright notice that uses the year that this file was compiled
|
|
||||||
wxString year(__DATE__);
|
|
||||||
wxString cr_symbol = wxString::FromUTF8("\xc2\xa9");
|
|
||||||
wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n"
|
|
||||||
"%s 2011-2018 Alessandro Ranellucci.",
|
|
||||||
cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n";
|
|
||||||
wxFont copyright_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger();
|
|
||||||
|
|
||||||
copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" +
|
|
||||||
_L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" +
|
|
||||||
_L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.");
|
|
||||||
|
|
||||||
word_wrap_string(copyright_string, banner_width, scale_factor);
|
|
||||||
|
|
||||||
wxCoord margin = int(scale_factor * 20);
|
|
||||||
|
|
||||||
// draw the (orange) labels inside of our black box (at the left of the splashscreen)
|
|
||||||
memDc.SetTextForeground(wxColour(237, 107, 33));
|
|
||||||
|
|
||||||
memDc.SetFont(title_font);
|
|
||||||
memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT);
|
|
||||||
|
|
||||||
memDc.SetFont(version_font);
|
|
||||||
memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT);
|
|
||||||
|
|
||||||
memDc.SetFont(copyright_font);
|
|
||||||
memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SplashScreen : public wxSplashScreen
|
class SplashScreen : public wxSplashScreen
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxWindow* parent)
|
SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, bool is_decorated = false)
|
||||||
: wxSplashScreen(bitmap, splashStyle, milliseconds, parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
: wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY,
|
||||||
wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR)
|
wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR )
|
||||||
{
|
{
|
||||||
wxASSERT(bitmap.IsOk());
|
wxASSERT(bitmap.IsOk());
|
||||||
m_main_bitmap = bitmap;
|
m_main_bitmap = bitmap;
|
||||||
|
|
||||||
m_scale_factor = get_scale_for_main_display();
|
if (!is_decorated)
|
||||||
|
Decorate(m_main_bitmap, pos, true);
|
||||||
|
|
||||||
|
m_scale = get_display_scale(pos);
|
||||||
|
m_font = get_scaled_sys_font(get_splashscreen_display_scale_factor(pos)).Bold().Larger();
|
||||||
|
|
||||||
|
if (pos != wxDefaultPosition) {
|
||||||
|
this->SetPosition(pos);
|
||||||
|
this->CenterOnScreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetText(const wxString& text)
|
void SetText(const wxString& text)
|
||||||
{
|
{
|
||||||
SetBmp(m_main_bitmap);
|
set_bitmap(m_main_bitmap);
|
||||||
if (!text.empty()) {
|
if (!text.empty()) {
|
||||||
wxBitmap bitmap(m_main_bitmap);
|
wxBitmap bitmap(m_main_bitmap);
|
||||||
|
|
||||||
wxMemoryDC memDC;
|
wxMemoryDC memDC;
|
||||||
memDC.SelectObject(bitmap);
|
memDC.SelectObject(bitmap);
|
||||||
|
|
||||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold().Larger();
|
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||||
memDC.SetFont(font);
|
memDC.SetFont(m_font);
|
||||||
memDC.SetTextForeground(wxColour(237, 107, 33));
|
memDC.SetTextForeground(wxColour(237, 107, 33));
|
||||||
memDC.DrawText(text, int(m_scale_factor * 45), int(m_scale_factor * 215));
|
memDC.DrawText(text, int(m_scale * 45), int(m_scale * 200));
|
||||||
|
|
||||||
memDC.SelectObject(wxNullBitmap);
|
memDC.SelectObject(wxNullBitmap);
|
||||||
SetBmp(bitmap);
|
set_bitmap(bitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetBmp(wxBitmap& bmp)
|
static bool Decorate(wxBitmap& bmp, wxPoint screen_pos = wxDefaultPosition, bool force_decor = false)
|
||||||
|
{
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float screen_sf = get_splashscreen_display_scale_factor(screen_pos);
|
||||||
|
float screen_scale = get_display_scale(screen_pos);
|
||||||
|
|
||||||
|
if (screen_sf == 1.0) {
|
||||||
|
// it means that we have just one display or all displays have a same scale
|
||||||
|
// Scale bitmap for this display and continue the decoration
|
||||||
|
scale_bitmap(bmp, screen_scale);
|
||||||
|
}
|
||||||
|
else if (force_decor) {
|
||||||
|
// if we are here, it means that bitmap is already scaled for the main display
|
||||||
|
// and now we should just scale it th the secondary monitor and continue the decoration
|
||||||
|
scale_bitmap(bmp, screen_sf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if screens have different scale and this function is called with force_decor == false
|
||||||
|
// then just rescale the bitmap for the main display scale
|
||||||
|
scale_bitmap(bmp, get_display_scale());
|
||||||
|
return false;
|
||||||
|
// Decoration will be continued later, from the SplashScreen constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
// use a memory DC to draw directly onto the bitmap
|
||||||
|
wxMemoryDC memDc(bmp);
|
||||||
|
|
||||||
|
// draw an dark grey box at the left of the splashscreen.
|
||||||
|
// this box will be 2/5 of the weight of the bitmap, and be at the left.
|
||||||
|
int banner_width = (bmp.GetWidth() / 5) * 2 - 2;
|
||||||
|
const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight()));
|
||||||
|
wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51)));
|
||||||
|
wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51)));
|
||||||
|
memDc.DrawRectangle(banner_rect);
|
||||||
|
|
||||||
|
wxFont sys_font = get_scaled_sys_font(screen_sf);
|
||||||
|
|
||||||
|
// title
|
||||||
|
#if ENABLE_GCODE_VIEWER
|
||||||
|
wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME;
|
||||||
|
#else
|
||||||
|
wxString title_string = SLIC3R_APP_NAME;
|
||||||
|
#endif // ENABLE_GCODE_VIEWER
|
||||||
|
|
||||||
|
wxFont title_font = sys_font;
|
||||||
|
title_font.SetPointSize(3 * sys_font.GetPointSize());
|
||||||
|
|
||||||
|
// dynamically get the version to display
|
||||||
|
wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION);
|
||||||
|
wxFont version_font = sys_font.Larger().Larger();
|
||||||
|
|
||||||
|
// create a copyright notice that uses the year that this file was compiled
|
||||||
|
wxString year(__DATE__);
|
||||||
|
wxString cr_symbol = wxString::FromUTF8("\xc2\xa9");
|
||||||
|
wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n"
|
||||||
|
"%s 2011-2018 Alessandro Ranellucci.",
|
||||||
|
cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n";
|
||||||
|
wxFont copyright_font = sys_font.Larger();
|
||||||
|
|
||||||
|
copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" +
|
||||||
|
_L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" +
|
||||||
|
_L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" +
|
||||||
|
_L("Splash screen could be desabled from the \"Preferences\"");
|
||||||
|
|
||||||
|
word_wrap_string(copyright_string, banner_width, screen_scale);
|
||||||
|
|
||||||
|
wxCoord margin = int(screen_scale * 20);
|
||||||
|
|
||||||
|
// draw the (orange) labels inside of our black box (at the left of the splashscreen)
|
||||||
|
memDc.SetTextForeground(wxColour(237, 107, 33));
|
||||||
|
|
||||||
|
memDc.SetFont(title_font);
|
||||||
|
memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT);
|
||||||
|
|
||||||
|
memDc.SetFont(version_font);
|
||||||
|
memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT);
|
||||||
|
|
||||||
|
memDc.SetFont(copyright_font);
|
||||||
|
memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, margin), wxALIGN_BOTTOM | wxALIGN_LEFT);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
wxBitmap m_main_bitmap;
|
||||||
|
wxFont m_font;
|
||||||
|
float m_scale {1.0};
|
||||||
|
|
||||||
|
void set_bitmap(wxBitmap& bmp)
|
||||||
{
|
{
|
||||||
m_window->SetBitmap(bmp);
|
m_window->SetBitmap(bmp);
|
||||||
m_window->Refresh();
|
m_window->Refresh();
|
||||||
m_window->Update();
|
m_window->Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
static float get_splashscreen_display_scale_factor(wxPoint pos = wxDefaultPosition)
|
||||||
wxBitmap m_main_bitmap;
|
{
|
||||||
float m_scale_factor {1.0};
|
if (wxDisplay::GetCount() == 1)
|
||||||
|
return 1.0;
|
||||||
|
|
||||||
|
wxFrame main_screen_fr(nullptr, wxID_ANY, wxEmptyString);
|
||||||
|
wxFrame splash_screen_fr(nullptr, wxID_ANY, wxEmptyString, pos);
|
||||||
|
|
||||||
|
#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
|
||||||
|
int main_dpi = get_dpi_for_window(&main_screen_fr);
|
||||||
|
int splash_dpi = get_dpi_for_window(&splash_screen_fr);
|
||||||
|
float sf = (float)splash_dpi / (float)main_dpi;
|
||||||
|
#else
|
||||||
|
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||||
|
float sf = (float)splash_screen_fr.GetTextExtent("m").x / (float)main_screen_fr.GetTextExtent("m").x;
|
||||||
|
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
|
||||||
|
|
||||||
|
return sf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float get_display_scale(wxPoint pos = wxDefaultPosition)
|
||||||
|
{
|
||||||
|
// pos equals to wxDefaultPosition, when display is main
|
||||||
|
wxFrame fr(nullptr, wxID_ANY, wxEmptyString, pos);
|
||||||
|
|
||||||
|
#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__)
|
||||||
|
int dpi = get_dpi_for_window(&fr);
|
||||||
|
float scale = dpi != DPI_DEFAULT ? (float)dpi / DPI_DEFAULT : 1.0;
|
||||||
|
#else
|
||||||
|
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||||
|
float scale = 0.1 * std::max<size_t>(10, fr.GetTextExtent("m").x - 1);
|
||||||
|
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
|
||||||
|
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scale_bitmap(wxBitmap& bmp, float scale)
|
||||||
|
{
|
||||||
|
if (scale == 1.0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wxImage image = bmp.ConvertToImage();
|
||||||
|
if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int width = int(scale * image.GetWidth());
|
||||||
|
int height = int(scale * image.GetHeight());
|
||||||
|
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
||||||
|
|
||||||
|
bmp = wxBitmap(std::move(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
static wxFont get_scaled_sys_font(float screen_sf)
|
||||||
|
{
|
||||||
|
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||||
|
if (screen_sf != 1.0)
|
||||||
|
font.SetPointSize(int(screen_sf * (float)font.GetPointSize()));
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void word_wrap_string(wxString& input, int line_px_len, float scalef)
|
||||||
|
{
|
||||||
|
// calculate count od symbols in one line according to the scale
|
||||||
|
int line_len = int((float)line_px_len / (scalef * 10) + 0.5) + 10;
|
||||||
|
|
||||||
|
int idx = -1;
|
||||||
|
int cur_len = 0;
|
||||||
|
for (size_t i = 0; i < input.Len(); i++)
|
||||||
|
{
|
||||||
|
cur_len++;
|
||||||
|
if (input[i] == ' ')
|
||||||
|
idx = i;
|
||||||
|
if (input[i] == '\n')
|
||||||
|
{
|
||||||
|
idx = -1;
|
||||||
|
cur_len = 0;
|
||||||
|
}
|
||||||
|
if (cur_len >= line_len && idx >= 0)
|
||||||
|
{
|
||||||
|
input[idx] = '\n';
|
||||||
|
cur_len = static_cast<int>(i) - idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
||||||
@ -580,17 +637,32 @@ bool GUI_App::on_init_inner()
|
|||||||
*/
|
*/
|
||||||
wxInitAllImageHandlers();
|
wxInitAllImageHandlers();
|
||||||
|
|
||||||
wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400);
|
SplashScreen* scrn = nullptr;
|
||||||
|
if (app_config->get("show_splash_screen") == "1")
|
||||||
|
{
|
||||||
#if ENABLE_GCODE_VIEWER
|
#if ENABLE_GCODE_VIEWER
|
||||||
wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG);
|
wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG);
|
||||||
#else
|
#else
|
||||||
wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG);
|
wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG);
|
||||||
#endif // ENABLE_GCODE_VIEWER
|
#endif // ENABLE_GCODE_VIEWER
|
||||||
|
|
||||||
DecorateSplashScreen(bmp);
|
// Detect position (display) to show the splash screen
|
||||||
|
// Now this position is equal to the mainframe position
|
||||||
|
wxPoint splashscreen_pos = wxDefaultPosition;
|
||||||
|
if (app_config->has("window_mainframe")) {
|
||||||
|
auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
|
||||||
|
if (metrics)
|
||||||
|
splashscreen_pos = metrics->get_rect().GetPosition();
|
||||||
|
}
|
||||||
|
|
||||||
SplashScreen* scrn = new SplashScreen(bmp.IsOk() ? bmp : bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr);
|
// try to decorate and/or scale the bitmap before splash screen creation
|
||||||
scrn->SetText(_L("Loading configuration..."));
|
bool is_decorated = SplashScreen::Decorate(bmp, splashscreen_pos);
|
||||||
|
|
||||||
|
// create splash screen with updated bmp
|
||||||
|
scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400),
|
||||||
|
wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated);
|
||||||
|
scrn->SetText(_L("Loading configuration..."));
|
||||||
|
}
|
||||||
|
|
||||||
preset_bundle = new PresetBundle();
|
preset_bundle = new PresetBundle();
|
||||||
|
|
||||||
@ -646,7 +718,9 @@ bool GUI_App::on_init_inner()
|
|||||||
|
|
||||||
// application frame
|
// application frame
|
||||||
#if ENABLE_GCODE_VIEWER
|
#if ENABLE_GCODE_VIEWER
|
||||||
if (is_editor())
|
if (scrn && is_editor())
|
||||||
|
#else
|
||||||
|
if (scrn)
|
||||||
#endif // ENABLE_GCODE_VIEWER
|
#endif // ENABLE_GCODE_VIEWER
|
||||||
scrn->SetText(_L("Creating settings tabs..."));
|
scrn->SetText(_L("Creating settings tabs..."));
|
||||||
|
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
|
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
|
||||||
#include "GLGizmoRotate.hpp"
|
#include "GLGizmoRotate.hpp"
|
||||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
|
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||||
|
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
@ -194,6 +200,64 @@ void GLGizmoRotate::on_render_for_picking() const
|
|||||||
glsafe(::glPopMatrix());
|
glsafe(::glPopMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||||
|
State & state,
|
||||||
|
const Alignment &alignment)
|
||||||
|
: m_imgui{imgui}
|
||||||
|
{
|
||||||
|
imgui->begin(_L("Rotation"), ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize |
|
||||||
|
ImGuiWindowFlags_NoCollapse);
|
||||||
|
|
||||||
|
// adjust window position to avoid overlap the view toolbar
|
||||||
|
float win_h = ImGui::GetWindowHeight();
|
||||||
|
float x = alignment.x, y = alignment.y;
|
||||||
|
y = std::min(y, alignment.bottom_limit - win_h);
|
||||||
|
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||||
|
|
||||||
|
static constexpr const char * button_txt = L("Optimize orientation");
|
||||||
|
static constexpr const char * slider_txt = L("Accuracy");
|
||||||
|
|
||||||
|
float button_width = imgui->calc_text_size(_(button_txt)).x;
|
||||||
|
ImGui::PushItemWidth(100.);
|
||||||
|
//if (imgui->button(_(button_txt))) {
|
||||||
|
if (ImGui::ArrowButton(_(button_txt).c_str(), ImGuiDir_Down)){
|
||||||
|
std::cout << "Blip" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SliderFloat(_(slider_txt).c_str(), &state.accuracy, 0.01f, 1.f, "%.1f");
|
||||||
|
|
||||||
|
static const std::vector<std::string> options = {
|
||||||
|
_L("Least supports").ToStdString(),
|
||||||
|
_L("Suface quality").ToStdString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (imgui->combo(_L("Choose method"), options, state.method) ) {
|
||||||
|
// std::cout << "method: " << state.method << std::endl;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow()
|
||||||
|
{
|
||||||
|
m_imgui->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
|
||||||
|
{
|
||||||
|
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
|
||||||
|
// m_rotoptimizewin_state.mobj = ?;
|
||||||
|
// RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GLGizmoRotate::render_circle() const
|
void GLGizmoRotate::render_circle() const
|
||||||
{
|
{
|
||||||
::glBegin(GL_LINE_LOOP);
|
::glBegin(GL_LINE_LOOP);
|
||||||
|
@ -52,12 +52,12 @@ public:
|
|||||||
std::string get_tooltip() const override;
|
std::string get_tooltip() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool on_init();
|
bool on_init() override;
|
||||||
virtual std::string on_get_name() const { return ""; }
|
std::string on_get_name() const override { return ""; }
|
||||||
virtual void on_start_dragging();
|
void on_start_dragging() override;
|
||||||
virtual void on_update(const UpdateData& data);
|
void on_update(const UpdateData& data) override;
|
||||||
virtual void on_render() const;
|
void on_render() const override;
|
||||||
virtual void on_render_for_picking() const;
|
void on_render_for_picking() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void render_circle() const;
|
void render_circle() const;
|
||||||
@ -94,46 +94,79 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool on_init();
|
bool on_init() override;
|
||||||
virtual std::string on_get_name() const;
|
std::string on_get_name() const override;
|
||||||
virtual void on_set_state()
|
void on_set_state() override
|
||||||
{
|
{
|
||||||
for (GLGizmoRotate& g : m_gizmos)
|
for (GLGizmoRotate& g : m_gizmos)
|
||||||
g.set_state(m_state);
|
g.set_state(m_state);
|
||||||
}
|
}
|
||||||
virtual void on_set_hover_id()
|
void on_set_hover_id() override
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 3; ++i)
|
for (int i = 0; i < 3; ++i)
|
||||||
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
||||||
}
|
}
|
||||||
virtual void on_enable_grabber(unsigned int id)
|
void on_enable_grabber(unsigned int id) override
|
||||||
{
|
{
|
||||||
if (id < 3)
|
if (id < 3)
|
||||||
m_gizmos[id].enable_grabber(0);
|
m_gizmos[id].enable_grabber(0);
|
||||||
}
|
}
|
||||||
virtual void on_disable_grabber(unsigned int id)
|
void on_disable_grabber(unsigned int id) override
|
||||||
{
|
{
|
||||||
if (id < 3)
|
if (id < 3)
|
||||||
m_gizmos[id].disable_grabber(0);
|
m_gizmos[id].disable_grabber(0);
|
||||||
}
|
}
|
||||||
virtual bool on_is_activable() const;
|
bool on_is_activable() const override;
|
||||||
virtual void on_start_dragging();
|
void on_start_dragging() override;
|
||||||
virtual void on_stop_dragging();
|
void on_stop_dragging() override;
|
||||||
virtual void on_update(const UpdateData& data)
|
void on_update(const UpdateData& data) override
|
||||||
{
|
{
|
||||||
for (GLGizmoRotate& g : m_gizmos)
|
for (GLGizmoRotate& g : m_gizmos)
|
||||||
{
|
{
|
||||||
g.update(data);
|
g.update(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtual void on_render() const;
|
void on_render() const override;
|
||||||
virtual void on_render_for_picking() const
|
void on_render_for_picking() const override
|
||||||
{
|
{
|
||||||
for (const GLGizmoRotate& g : m_gizmos)
|
for (const GLGizmoRotate& g : m_gizmos)
|
||||||
{
|
{
|
||||||
g.render_for_picking();
|
g.render_for_picking();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
class RotoptimzeWindow {
|
||||||
|
ImGuiWrapper *m_imgui = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
enum Metods { mMinSupportPoints, mLegacy };
|
||||||
|
|
||||||
|
float accuracy = 1.f;
|
||||||
|
int method = mMinSupportPoints;
|
||||||
|
ModelObject *mobj = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Alignment { float x, y, bottom_limit; };
|
||||||
|
|
||||||
|
RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||||
|
State & state,
|
||||||
|
const Alignment &bottom_limit);
|
||||||
|
|
||||||
|
~RotoptimzeWindow();
|
||||||
|
|
||||||
|
RotoptimzeWindow(const RotoptimzeWindow&) = delete;
|
||||||
|
RotoptimzeWindow(RotoptimzeWindow &&) = delete;
|
||||||
|
RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete;
|
||||||
|
RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
RotoptimzeWindow::State m_rotoptimizewin_state = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
|
@ -425,10 +425,10 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>&
|
|||||||
text(label);
|
text(label);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
int selection_out = -1;
|
int selection_out = selection;
|
||||||
bool res = false;
|
bool res = false;
|
||||||
|
|
||||||
const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : "";
|
const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : "";
|
||||||
if (ImGui::BeginCombo("", selection_str)) {
|
if (ImGui::BeginCombo("", selection_str)) {
|
||||||
for (int i = 0; i < (int)options.size(); i++) {
|
for (int i = 0; i < (int)options.size(); i++) {
|
||||||
if (ImGui::Selectable(options[i].c_str(), i == selection)) {
|
if (ImGui::Selectable(options[i].c_str(), i == selection)) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||||
#include "libslic3r/MinAreaBoundingBox.hpp"
|
#include "libslic3r/MinAreaBoundingBox.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
|
|
||||||
@ -12,26 +13,41 @@ namespace Slic3r { namespace GUI {
|
|||||||
void RotoptimizeJob::process()
|
void RotoptimizeJob::process()
|
||||||
{
|
{
|
||||||
int obj_idx = m_plater->get_selected_object_idx();
|
int obj_idx = m_plater->get_selected_object_idx();
|
||||||
if (obj_idx < 0) { return; }
|
if (obj_idx < 0 || int(m_plater->sla_print().objects().size()) <= obj_idx)
|
||||||
|
return;
|
||||||
|
|
||||||
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
|
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
|
||||||
|
const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)];
|
||||||
|
|
||||||
auto r = sla::find_best_rotation(
|
if (!o || !po) return;
|
||||||
*o,
|
|
||||||
.005f,
|
TriangleMesh mesh = o->raw_mesh();
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
|
||||||
|
// for (auto inst : o->instances) {
|
||||||
|
// Transform3d tr = Transform3d::Identity();
|
||||||
|
// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ()));
|
||||||
|
// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY()));
|
||||||
|
// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX()));
|
||||||
|
|
||||||
|
// double score = sla::get_model_supportedness(*po, tr);
|
||||||
|
|
||||||
|
// std::cout << "Model supportedness before: " << score << std::endl;
|
||||||
|
// }
|
||||||
|
|
||||||
|
Vec2d r = sla::find_best_rotation(*po, 0.75f,
|
||||||
[this](unsigned s) {
|
[this](unsigned s) {
|
||||||
if (s < 100)
|
if (s < 100)
|
||||||
update_status(int(s),
|
update_status(int(s), _(L("Searching for optimal orientation")));
|
||||||
_(L("Searching for optimal orientation")));
|
|
||||||
},
|
},
|
||||||
[this]() { return was_canceled(); });
|
[this] () { return was_canceled(); });
|
||||||
|
|
||||||
|
|
||||||
double mindist = 6.0; // FIXME
|
double mindist = 6.0; // FIXME
|
||||||
|
|
||||||
if (!was_canceled()) {
|
if (!was_canceled()) {
|
||||||
for(ModelInstance * oi : o->instances) {
|
for(ModelInstance * oi : o->instances) {
|
||||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
oi->set_rotation({r[X], r[Y], 0.});
|
||||||
|
|
||||||
auto trmatrix = oi->get_transformation().get_matrix();
|
auto trmatrix = oi->get_transformation().get_matrix();
|
||||||
Polygon trchull = o->convex_hull_2d(trmatrix);
|
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||||
|
@ -131,6 +131,15 @@ void PreferencesDialog::build()
|
|||||||
option = Option(def, "use_inches");
|
option = Option(def, "use_inches");
|
||||||
m_optgroup_general->append_single_option_line(option);
|
m_optgroup_general->append_single_option_line(option);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Show/Hide splash screen
|
||||||
|
def.label = L("Show splash screen");
|
||||||
|
def.type = coBool;
|
||||||
|
def.tooltip = L("Show splash screen");
|
||||||
|
def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" });
|
||||||
|
option = Option(def, "show_splash_screen");
|
||||||
|
m_optgroup_general->append_single_option_line(option);
|
||||||
|
|
||||||
m_optgroup_camera = std::make_shared<ConfigOptionsGroup>(this, _(L("Camera")));
|
m_optgroup_camera = std::make_shared<ConfigOptionsGroup>(this, _(L("Camera")));
|
||||||
m_optgroup_camera->label_width = 40;
|
m_optgroup_camera->label_width = 40;
|
||||||
m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||||
|
@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_marchingsquares.cpp
|
test_marchingsquares.cpp
|
||||||
test_timeutils.cpp
|
test_timeutils.cpp
|
||||||
test_voronoi.cpp
|
test_voronoi.cpp
|
||||||
|
test_optimizers.cpp
|
||||||
test_png_io.cpp
|
test_png_io.cpp
|
||||||
test_timeutils.cpp
|
test_timeutils.cpp
|
||||||
)
|
)
|
||||||
|
59
tests/libslic3r/test_optimizers.cpp
Normal file
59
tests/libslic3r/test_optimizers.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <test_utils.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Optimize/BruteforceOptimizer.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Optimize/NLoptOptimizer.hpp>
|
||||||
|
|
||||||
|
void check_opt_result(double score, double ref, double abs_err, double rel_err)
|
||||||
|
{
|
||||||
|
double abs_diff = std::abs(score - ref);
|
||||||
|
double rel_diff = std::abs(abs_diff / std::abs(ref));
|
||||||
|
|
||||||
|
bool abs_reached = abs_diff < abs_err;
|
||||||
|
bool rel_reached = rel_diff < rel_err;
|
||||||
|
bool precision_reached = abs_reached || rel_reached;
|
||||||
|
REQUIRE(precision_reached);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Opt> void test_sin(Opt &&opt)
|
||||||
|
{
|
||||||
|
using namespace Slic3r::opt;
|
||||||
|
|
||||||
|
auto optfunc = [](const auto &in) {
|
||||||
|
auto [phi] = in;
|
||||||
|
|
||||||
|
return std::sin(phi);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto init = initvals({PI});
|
||||||
|
auto optbounds = bounds({ {0., 2 * PI}});
|
||||||
|
|
||||||
|
Result result_min = opt.to_min().optimize(optfunc, init, optbounds);
|
||||||
|
Result result_max = opt.to_max().optimize(optfunc, init, optbounds);
|
||||||
|
|
||||||
|
check_opt_result(result_min.score, -1., 1e-2, 1e-4);
|
||||||
|
check_opt_result(result_max.score, 1., 1e-2, 1e-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Opt> void test_sphere_func(Opt &&opt)
|
||||||
|
{
|
||||||
|
using namespace Slic3r::opt;
|
||||||
|
|
||||||
|
Result result = opt.to_min().optimize([](const auto &in) {
|
||||||
|
auto [x, y] = in;
|
||||||
|
|
||||||
|
return x * x + y * y + 1.;
|
||||||
|
}, initvals({.6, -0.2}), bounds({{-1., 1.}, {-1., 1.}}));
|
||||||
|
|
||||||
|
check_opt_result(result.score, 1., 1e-2, 1e-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test brute force optimzer for basic 1D and 2D functions", "[Opt]") {
|
||||||
|
using namespace Slic3r::opt;
|
||||||
|
|
||||||
|
Optimizer<AlgBruteForce> opt;
|
||||||
|
|
||||||
|
test_sin(opt);
|
||||||
|
test_sphere_func(opt);
|
||||||
|
}
|
@ -5,6 +5,7 @@
|
|||||||
#include "sla_test_utils.hpp"
|
#include "sla_test_utils.hpp"
|
||||||
|
|
||||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||||
|
#include <libslic3r/SLA/Concurrency.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -239,3 +240,14 @@ TEST_CASE("halfcone test", "[halfcone]") {
|
|||||||
m.require_shared_vertices();
|
m.require_shared_vertices();
|
||||||
m.WriteOBJFile("Halfcone.obj");
|
m.WriteOBJFile("Halfcone.obj");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test concurrency")
|
||||||
|
{
|
||||||
|
std::vector<double> vals = grid(0., 100., 10.);
|
||||||
|
|
||||||
|
double ref = std::accumulate(vals.begin(), vals.end(), 0.);
|
||||||
|
|
||||||
|
double s = sla::ccr_par::reduce(vals.begin(), vals.end(), 0., std::plus<double>{});
|
||||||
|
|
||||||
|
REQUIRE(s == Approx(ref));
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user