Merge branch 'master' into fs_emboss
This commit is contained in:
commit
f331bb5edf
4
deps/wxWidgets/wxWidgets.cmake
vendored
4
deps/wxWidgets/wxWidgets.cmake
vendored
@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
|
||||
endif()
|
||||
|
||||
prusaslicer_add_cmake_project(wxWidgets
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/4fd2120c913c20c3bb66ee9d01d8ff5087a8b90a.zip
|
||||
URL_HASH SHA256=5b59e8b4dccf73e109c6588f6a69bcfe4e02e930af53c43d5d1329c1f3d83ec9
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/0b49beaacce17d90f0c370ecd73221abd089667a.zip
|
||||
URL_HASH SHA256=8fa978a76d6bd811b30eecc5124186b9ad54290b820f3a354e85bfa9dae6a5ce
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
@ -871,7 +871,7 @@ template<class P> auto rcend(const P& p) -> decltype(_backward(cbegin(p)))
|
||||
|
||||
template<class P> TPoint<P> front(const P& p) { return *shapelike::cbegin(p); }
|
||||
template<class P> TPoint<P> back (const P& p) {
|
||||
return *backward(shapelike::cend(p));
|
||||
return *std::prev(shapelike::cend(p));
|
||||
}
|
||||
|
||||
// Optional, does nothing by default
|
||||
|
@ -157,26 +157,34 @@ template<class RawShape> class EdgeCache {
|
||||
|
||||
void createCache(const RawShape& sh) {
|
||||
{ // For the contour
|
||||
auto first = shapelike::cbegin(sh);
|
||||
auto next = std::next(first);
|
||||
auto endit = shapelike::cend(sh);
|
||||
auto first = sl::cbegin(sh);
|
||||
auto endit = sl::cend(sh);
|
||||
auto next = first == endit ? endit : std::next(first);
|
||||
|
||||
contour_.distances.reserve(shapelike::contourVertexCount(sh));
|
||||
contour_.distances.reserve(sl::contourVertexCount(sh));
|
||||
|
||||
while(next != endit) {
|
||||
contour_.emap.emplace_back(*(first++), *(next++));
|
||||
contour_.full_distance += length(contour_.emap.back());
|
||||
contour_.distances.emplace_back(contour_.full_distance);
|
||||
}
|
||||
|
||||
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN) {
|
||||
if (sl::contourVertexCount(sh) > 0) {
|
||||
contour_.emap.emplace_back(sl::back(sh), sl::front(sh));
|
||||
contour_.full_distance += length(contour_.emap.back());
|
||||
contour_.distances.emplace_back(contour_.full_distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& h : shapelike::holes(sh)) { // For the holes
|
||||
auto first = h.begin();
|
||||
auto next = std::next(first);
|
||||
auto endit = h.end();
|
||||
auto first = sl::cbegin(h);
|
||||
auto endit = sl::cend(h);
|
||||
auto next = first == endit ? endit :std::next(first);
|
||||
|
||||
ContourCache hc;
|
||||
hc.distances.reserve(endit - first);
|
||||
hc.distances.reserve(sl::contourVertexCount(h));
|
||||
|
||||
while(next != endit) {
|
||||
hc.emap.emplace_back(*(first++), *(next++));
|
||||
@ -184,6 +192,14 @@ template<class RawShape> class EdgeCache {
|
||||
hc.distances.emplace_back(hc.full_distance);
|
||||
}
|
||||
|
||||
if constexpr (ClosureTypeV<RawShape> == Closure::OPEN) {
|
||||
if (sl::contourVertexCount(h) > 0) {
|
||||
hc.emap.emplace_back(sl::back(sh), sl::front(sh));
|
||||
hc.full_distance += length(hc.emap.back());
|
||||
hc.distances.emplace_back(hc.full_distance);
|
||||
}
|
||||
}
|
||||
|
||||
holes_.emplace_back(std::move(hc));
|
||||
}
|
||||
}
|
||||
@ -206,7 +222,6 @@ template<class RawShape> class EdgeCache {
|
||||
contour_.corners.reserve(N / S + 1);
|
||||
contour_.corners.emplace_back(0.0);
|
||||
auto N_1 = N-1;
|
||||
contour_.corners.emplace_back(0.0);
|
||||
for(size_t i = 0; i < N_1; i += S) {
|
||||
contour_.corners.emplace_back(
|
||||
contour_.distances.at(i) / contour_.full_distance);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillConcentric.hpp"
|
||||
#include "FillEnsuring.hpp"
|
||||
#include "Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -486,14 +487,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
|
||||
size_t first_object_layer_id = this->object()->get_layer(0)->id();
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
//skip patterns for which additional input is nullptr
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: if (lightning_generator == nullptr) continue; break;
|
||||
case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break;
|
||||
case ipSupportCubic: if (support_fill_octree == nullptr) continue; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
@ -647,7 +640,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
#endif
|
||||
}
|
||||
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
|
||||
{
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this);
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
@ -656,14 +649,17 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
|
||||
Polylines sparse_infill_polylines{};
|
||||
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
// skip patterns for which additional input is nullptr
|
||||
if (surface_fill.surface.surface_type != stInternal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: continue; break;
|
||||
case ipAdaptiveCubic: continue; break;
|
||||
case ipSupportCubic: continue; break;
|
||||
case ipCount: continue; break;
|
||||
case ipSupportBase: continue; break;
|
||||
case ipEnsuring: continue; break;
|
||||
case ipLightning:
|
||||
case ipAdaptiveCubic:
|
||||
case ipSupportCubic:
|
||||
case ipRectilinear:
|
||||
case ipMonotonic:
|
||||
case ipMonotonicLines:
|
||||
@ -688,10 +684,16 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
// f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
if (surface_fill.params.pattern == ipLightning) {
|
||||
auto *lf = dynamic_cast<FillLightning::Filler *>(f.get());
|
||||
lf->generator = lightning_generator;
|
||||
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
|
||||
}
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
double link_max_length = 0.;
|
||||
if (!surface_fill.params.bridge) {
|
||||
|
@ -368,8 +368,12 @@ public:
|
||||
void make_perimeters();
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring() const;
|
||||
void make_fills(FillAdaptive::Octree *adaptive_fill_octree,
|
||||
FillAdaptive::Octree *support_fill_octree,
|
||||
FillLightning::Generator *lightning_generator);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree,
|
||||
FillAdaptive::Octree *support_fill_octree,
|
||||
FillLightning::Generator* lightning_generator) const;
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
|
@ -378,7 +378,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) :
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, Geometry::deg2rad(custom_angle)) :
|
||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#if 0
|
||||
|
@ -1500,7 +1500,7 @@ void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d&
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
@ -234,6 +234,7 @@ namespace client
|
||||
delete m_data.s;
|
||||
m_type = TYPE_EMPTY;
|
||||
}
|
||||
~expr() { reset(); }
|
||||
|
||||
enum Type {
|
||||
TYPE_EMPTY = 0,
|
||||
|
@ -267,13 +267,17 @@ ThickLines ThickPolyline::thicklines() const
|
||||
// Removes the given distance from the end of the ThickPolyline
|
||||
void ThickPolyline::clip_end(double distance)
|
||||
{
|
||||
if (! this->empty()) {
|
||||
assert(this->width.size() == (this->points.size() - 1) * 2);
|
||||
while (distance > 0) {
|
||||
Vec2d last_point = this->last_point().cast<double>();
|
||||
coordf_t last_width = this->width.back();
|
||||
this->points.pop_back();
|
||||
this->width.pop_back();
|
||||
if (this->points.empty())
|
||||
if (this->points.empty()) {
|
||||
assert(this->width.empty());
|
||||
break;
|
||||
}
|
||||
coordf_t last_width = this->width.back();
|
||||
this->width.pop_back();
|
||||
|
||||
Vec2d vec = this->last_point().cast<double>() - last_point;
|
||||
coordf_t width_diff = this->width.back() - last_width;
|
||||
@ -289,7 +293,8 @@ void ThickPolyline::clip_end(double distance)
|
||||
|
||||
distance -= std::sqrt(vec_length_sqr);
|
||||
}
|
||||
assert(this->width.size() == (this->points.size() - 1) * 2);
|
||||
}
|
||||
assert(this->points.empty() ? this->width.empty() : this->width.size() == (this->points.size() - 1) * 2);
|
||||
}
|
||||
|
||||
void ThickPolyline::start_at_index(int index)
|
||||
|
@ -187,6 +187,7 @@ struct ThickPolyline {
|
||||
const Point& last_point() const { return this->points.back(); }
|
||||
size_t size() const { return this->points.size(); }
|
||||
bool is_valid() const { return this->points.size() >= 2; }
|
||||
bool empty() const { return this->points.empty(); }
|
||||
double length() const { return Slic3r::length(this->points); }
|
||||
|
||||
void clear() { this->points.clear(); this->width.clear(); }
|
||||
|
@ -452,7 +452,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance",
|
||||
"support_material_buildplate_only",
|
||||
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_tip_diameter",
|
||||
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
|
||||
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
|
||||
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
|
||||
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
|
||||
|
@ -1185,7 +1185,7 @@ void Print::alert_when_supports_needed()
|
||||
case SupportSpotsGenerator::SupportPointCause::WeakObjectPart: message = L("thin fragile section"); break;
|
||||
}
|
||||
|
||||
return (critical ? "!" : "") + message;
|
||||
return message;
|
||||
};
|
||||
|
||||
// vector of pairs of object and its issues, where each issue is a pair of type and critical flag
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef slic3r_Print_hpp_
|
||||
#define slic3r_Print_hpp_
|
||||
|
||||
#include "Fill/FillAdaptive.hpp"
|
||||
#include "Fill/FillLightning.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
@ -385,7 +387,8 @@ private:
|
||||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data();
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface*, float>>& surfaces_w_bottom_z) const;
|
||||
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
||||
|
||||
// XYZ in scaled coordinates
|
||||
@ -410,6 +413,9 @@ private:
|
||||
// this is set to true when LayerRegion->slices is split in top/internal/bottom
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool m_typed_slices = false;
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> m_adaptive_fill_octrees;
|
||||
FillLightning::GeneratorPtr m_lightning_generator;
|
||||
};
|
||||
|
||||
struct WipeTowerData
|
||||
|
@ -2910,6 +2910,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(5));
|
||||
|
||||
// Tree Support Branch Distance
|
||||
// How far apart the branches need to be when they touch the model. Making this distance small will cause
|
||||
// the tree support to touch the model at more points, causing better overhang but making support harder to remove.
|
||||
def = this->add("support_tree_branch_distance", coFloat);
|
||||
def->label = L("Branch Distance");
|
||||
def->category = L("Support material");
|
||||
def->tooltip = L("How far apart the branches need to be when they touch the model. "
|
||||
"Making this distance small will cause the tree support to touch the model at more points, "
|
||||
"causing better overhang but making support harder to remove.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1.));
|
||||
|
||||
def = this->add("support_tree_top_rate", coPercent);
|
||||
def->label = L("Branch Density");
|
||||
def->category = L("Support material");
|
||||
@ -2921,7 +2933,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->min = 5;
|
||||
def->max_literal = 35;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionPercent(30));
|
||||
def->set_default_value(new ConfigOptionPercent(15));
|
||||
|
||||
def = this->add("temperature", coInts);
|
||||
def->label = L("Other layers");
|
||||
|
@ -555,6 +555,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, support_tree_branch_diameter))
|
||||
((ConfigOptionFloat, support_tree_branch_diameter_angle))
|
||||
((ConfigOptionPercent, support_tree_top_rate))
|
||||
((ConfigOptionFloat, support_tree_branch_distance))
|
||||
((ConfigOptionFloat, support_tree_tip_diameter))
|
||||
// The rest
|
||||
((ConfigOptionBool, thick_bridges))
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "Layer.hpp"
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "TreeSupport.hpp"
|
||||
#include "Surface.hpp"
|
||||
@ -37,10 +38,13 @@
|
||||
#include <cstddef>
|
||||
#include <float.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <oneapi/tbb/blocked_range.h>
|
||||
#include <oneapi/tbb/concurrent_vector.h>
|
||||
#include <oneapi/tbb/parallel_for.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@ -400,16 +404,16 @@ void PrintObject::infill()
|
||||
|
||||
if (this->set_started(posInfill)) {
|
||||
m_print->set_status(45, L("making infill"));
|
||||
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
|
||||
auto lightning_generator = this->prepare_lightning_infill_data();
|
||||
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
|
||||
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](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) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get());
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -509,7 +513,8 @@ void PrintObject::estimate_curled_extrusions()
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data()
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
|
||||
{
|
||||
using namespace FillAdaptive;
|
||||
|
||||
@ -523,21 +528,17 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
|
||||
its_transform(mesh, to_octree * this->trafo_centered(), true);
|
||||
|
||||
// Triangulate internal bridging surfaces.
|
||||
std::vector<std::vector<Vec3d>> overhangs(this->layers().size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0, int(m_layers.size()) - 1),
|
||||
[this, &to_octree, &overhangs](const tbb::blocked_range<int> &range) {
|
||||
std::vector<Vec3d> &out = overhangs[range.begin()];
|
||||
for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
std::vector<std::vector<Vec3d>> overhangs(surfaces_w_bottom_z.size());
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, surfaces_w_bottom_z.size()),
|
||||
[this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range<int> &range) {
|
||||
for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
|
||||
std::vector<Vec3d> &out = overhangs[surface_idx];
|
||||
m_print->throw_if_canceled();
|
||||
const Layer *layer = this->layers()[idx_layer];
|
||||
for (const LayerRegion *layerm : layer->regions())
|
||||
for (const Surface &surface : layerm->fill_surfaces())
|
||||
if (surface.surface_type == stInternalBridge)
|
||||
append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z()));
|
||||
}
|
||||
append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon,
|
||||
surfaces_w_bottom_z[surface_idx].second));
|
||||
for (Vec3d &p : out)
|
||||
p = (to_octree * p).eval();
|
||||
}
|
||||
});
|
||||
// and gather them.
|
||||
for (size_t i = 1; i < overhangs.size(); ++ i)
|
||||
@ -693,6 +694,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "support_tree_branch_diameter"
|
||||
|| opt_key == "support_tree_branch_diameter_angle"
|
||||
|| opt_key == "support_tree_top_rate"
|
||||
|| opt_key == "support_tree_branch_distance"
|
||||
|| opt_key == "support_tree_tip_diameter"
|
||||
|| opt_key == "raft_expansion"
|
||||
|| opt_key == "raft_first_layer_density"
|
||||
@ -1581,259 +1583,219 @@ void PrintObject::bridge_over_infill()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info();
|
||||
|
||||
struct ModifiedSurface
|
||||
struct CandidateSurface
|
||||
{
|
||||
ModifiedSurface(const Surface *original_surface, Polygons new_polys, const LayerRegion *region, double bridge_angle)
|
||||
: original_surface(original_surface), new_polys(new_polys), region(region), bridge_angle(bridge_angle)
|
||||
CandidateSurface(const Surface *original_surface,
|
||||
int layer_index,
|
||||
Polygons new_polys,
|
||||
const LayerRegion *region,
|
||||
double bridge_angle,
|
||||
bool supported_by_lightning)
|
||||
: original_surface(original_surface)
|
||||
, layer_index(layer_index)
|
||||
, new_polys(new_polys)
|
||||
, region(region)
|
||||
, bridge_angle(bridge_angle)
|
||||
, supported_by_lightning(supported_by_lightning)
|
||||
{}
|
||||
const Surface *original_surface;
|
||||
int layer_index;
|
||||
Polygons new_polys;
|
||||
const LayerRegion *region;
|
||||
double bridge_angle;
|
||||
bool supported_by_lightning;
|
||||
};
|
||||
|
||||
std::unordered_map<const LayerSlice *, std::vector<ModifiedSurface>> bridging_surfaces;
|
||||
std::map<size_t, std::vector<CandidateSurface>> surfaces_by_layer;
|
||||
|
||||
// SECTION to gather and filter surfaces for expanding, and then cluster them by layer
|
||||
{
|
||||
tbb::concurrent_vector<CandidateSurface> candidate_surfaces;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = static_cast<const PrintObject *>(this),
|
||||
&bridging_surfaces](tbb::blocked_range<size_t> r) {
|
||||
&candidate_surfaces](tbb::blocked_range<size_t> r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
|
||||
// gather also sparse infill surfaces on this layer, to which we can expand the bridges for anchoring
|
||||
// gather potential internal bridging surfaces for the current layer
|
||||
// pair of LayerSlice idx and surfaces. The LayerSlice idx simplifies the processing, since we cannot expand beyond it
|
||||
std::unordered_map<const LayerSlice *, SurfacesPtr> bridging_surface_candidates;
|
||||
std::unordered_map<const LayerSlice *, SurfacesPtr> expansion_space;
|
||||
std::unordered_map<const LayerSlice *, float> max_bridge_flow_height;
|
||||
std::unordered_map<const Surface *, const LayerRegion *> surface_to_region;
|
||||
for (const LayerSlice &slice : layer->lslices_ex) {
|
||||
AABBTreeLines::LinesDistancer<Line> slice_island_tree{to_lines(layer->lslices[int(&slice - layer->lslices_ex.data())])};
|
||||
std::unordered_set<const LayerRegion *> regions_to_check;
|
||||
|
||||
// If there is composite island we have to check all regions on the layer. otherwise, only some regions are needed to be checked
|
||||
for (const LayerIsland &island : slice.islands) {
|
||||
regions_to_check.insert(layer->regions()[island.perimeters.region()]);
|
||||
if (!island.fill_expolygons_composite()) {
|
||||
regions_to_check.insert(layer->regions()[island.fill_region_id]);
|
||||
} else {
|
||||
for (const auto& r : layer->regions()) {
|
||||
regions_to_check.insert(r);
|
||||
if (layer->lower_layer == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing();
|
||||
Polygons unsupported_area;
|
||||
Polygons lower_layer_solids;
|
||||
bool contains_only_lightning = true;
|
||||
for (const LayerRegion *region : layer->lower_layer->regions()) {
|
||||
if (region->region().config().fill_pattern.value != ipLightning) {
|
||||
contains_only_lightning = false;
|
||||
}
|
||||
Polygons fill_polys = to_polygons(region->fill_expolygons());
|
||||
unsupported_area = union_(unsupported_area, expand(fill_polys, spacing));
|
||||
for (const Surface &surface : region->fill_surfaces()) {
|
||||
if (surface.surface_type != stInternal || region->region().config().fill_density.value == 100) {
|
||||
Polygons p = to_polygons(surface.expolygon);
|
||||
lower_layer_solids.insert(lower_layer_solids.end(), p.begin(), p.end());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const LayerRegion *region : regions_to_check) {
|
||||
lower_layer_solids = expand(lower_layer_solids, 4 * spacing);
|
||||
unsupported_area = shrink(unsupported_area, 5 * spacing);
|
||||
unsupported_area = diff(unsupported_area, lower_layer_solids);
|
||||
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
SurfacesPtr region_internal_solids = region->fill_surfaces().filter_by_type(stInternalSolid);
|
||||
|
||||
// filter out surfaces not from this island... TODO sotre this info in the Z-Graph, so that this filtering is not needed
|
||||
// NOTE: we are keeping even very small internal ensuring overhangs here. The aim is to later differentiate between expanding wall ensuring regions
|
||||
// where briding them would be conterproductive, and small ensuring islands that expand into large ones, where bridging is quite necessary
|
||||
region_internal_solids.erase(std::remove_if(region_internal_solids.begin(), region_internal_solids.end(),
|
||||
[slice_island_tree](const Surface *s) {
|
||||
if (slice_island_tree.outside(s->expolygon.contour.first_point()) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
region_internal_solids.end());
|
||||
if (!region_internal_solids.empty()) {
|
||||
max_bridge_flow_height[&slice] = std::max(max_bridge_flow_height[&slice],
|
||||
region->bridging_flow(frSolidInfill, true).height());
|
||||
}
|
||||
for (const Surface *s : region_internal_solids) {
|
||||
surface_to_region[s] = region;
|
||||
Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area);
|
||||
bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON;
|
||||
if (!unsupported.empty() && (!partially_supported || area(unsupported) > 5 * 5 * spacing * spacing)) {
|
||||
Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing));
|
||||
for (Polygon p : diff(to_polygons(s->expolygon), worth_bridging)) {
|
||||
if (p.area() < region->flow(frSolidInfill, true).scaled_spacing() * scale_(12.0)) {
|
||||
worth_bridging.push_back(p);
|
||||
}
|
||||
bridging_surface_candidates[&slice].insert(bridging_surface_candidates[&slice].end(), region_internal_solids.begin(),
|
||||
region_internal_solids.end());
|
||||
auto region_sparse_infill = region->fill_surfaces().filter_by_type(stInternal);
|
||||
expansion_space[&slice].insert(expansion_space[&slice].end(), region_sparse_infill.begin(), region_sparse_infill.end());
|
||||
}
|
||||
worth_bridging = intersection(closing(worth_bridging, 3 * region->flow(frSolidInfill, true).scaled_spacing()), s->expolygon);
|
||||
candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0, contains_only_lightning));
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)),
|
||||
to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging),
|
||||
to_lines(unsupported_area));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const CandidateSurface &c : candidate_surfaces) {
|
||||
surfaces_by_layer[c.layer_index].push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
// if there are none briding candidates, exit now, before making infill for the previous layer
|
||||
if (std::all_of(bridging_surface_candidates.begin(), bridging_surface_candidates.end(),
|
||||
[](const std::pair<const LayerSlice *, SurfacesPtr> &candidates) { return candidates.second.empty(); })) {
|
||||
continue;
|
||||
std::map<size_t, Polylines> infill_lines;
|
||||
// SECTION to generate infill polylines
|
||||
{
|
||||
std::vector<std::pair<const Surface *, float>> surfaces_w_bottom_z;
|
||||
for (const auto &pair : surfaces_by_layer) {
|
||||
for (const CandidateSurface &c : pair.second) {
|
||||
surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z());
|
||||
}
|
||||
}
|
||||
|
||||
// generate sparse infill polylines from lower layers to get anchorable polylines
|
||||
Polylines lower_layer_polylines = po->get_layer(lidx)->lower_layer->generate_sparse_infill_polylines_for_anchoring();
|
||||
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z);
|
||||
this->m_lightning_generator = this->prepare_lightning_infill_data();
|
||||
|
||||
for (std::pair<const LayerSlice *, SurfacesPtr> candidates : bridging_surface_candidates) {
|
||||
if (candidates.second.empty()) {
|
||||
continue;
|
||||
};
|
||||
|
||||
auto region_has_special_infill = [](const LayerRegion *layer_region) {
|
||||
switch (layer_region->region().config().fill_pattern.value) {
|
||||
case ipAdaptiveCubic: return true;
|
||||
case ipSupportCubic: return true;
|
||||
case ipLightning: return true;
|
||||
default: return false;
|
||||
std::vector<size_t> layers_to_generate_infill;
|
||||
for (const auto &pair : surfaces_by_layer) {
|
||||
assert(pair.first > 0);
|
||||
infill_lines[pair.first - 1] = {};
|
||||
layers_to_generate_infill.push_back(pair.first - 1);
|
||||
}
|
||||
};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, layers_to_generate_infill.size()), [po = static_cast<const PrintObject *>(this),
|
||||
&layers_to_generate_infill,
|
||||
&infill_lines](tbb::blocked_range<size_t> r) {
|
||||
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
|
||||
size_t lidx = layers_to_generate_infill[job_idx];
|
||||
infill_lines.at(
|
||||
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->m_adaptive_fill_octrees.first.get(),
|
||||
po->m_adaptive_fill_octrees.second.get(),
|
||||
po->m_lightning_generator.get());
|
||||
}
|
||||
});
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
for (const auto &il : infill_lines) {
|
||||
debug_draw(std::to_string(il.first) + "_infill_lines", to_lines(get_layer(il.first)->lslices), to_lines(il.second), {}, {});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another
|
||||
std::vector<std::vector<size_t>> clustered_layers_for_threads;
|
||||
float target_flow_height_factor = 0.5;
|
||||
{
|
||||
std::vector<size_t> layers_with_candidates;
|
||||
std::map<size_t, Polygons> layer_area_covered_by_candidates;
|
||||
for (const auto& pair : surfaces_by_layer) {
|
||||
layers_with_candidates.push_back(pair.first);
|
||||
layer_area_covered_by_candidates[pair.first] = {};
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer,
|
||||
&layer_area_covered_by_candidates](
|
||||
tbb::blocked_range<size_t> r) {
|
||||
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
|
||||
size_t lidx = layers_with_candidates[job_idx];
|
||||
for (const auto &candidate : surfaces_by_layer.at(lidx)) {
|
||||
Polygon candiate_inflated_aabb = get_extents(candidate.new_polys)
|
||||
.inflated(candidate.region->flow(frSolidInfill, true).scaled_spacing() * 5)
|
||||
.polygon();
|
||||
layer_area_covered_by_candidates.at(lidx) = union_(layer_area_covered_by_candidates.at(lidx),
|
||||
Polygons{candiate_inflated_aabb});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// note: surfaces_by_layer is ordered map
|
||||
for (auto pair : surfaces_by_layer) {
|
||||
if (clustered_layers_for_threads.empty() ||
|
||||
this->get_layer(clustered_layers_for_threads.back().back())->print_z <
|
||||
this->get_layer(pair.first)->print_z -
|
||||
this->get_layer(pair.first)->regions()[0]->flow(frSolidInfill, true).height() * target_flow_height_factor -
|
||||
EPSILON ||
|
||||
intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()],
|
||||
layer_area_covered_by_candidates[pair.first])
|
||||
.empty()) {
|
||||
clustered_layers_for_threads.push_back({pair.first});
|
||||
} else {
|
||||
clustered_layers_for_threads.back().push_back(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
std::cout << "BRIDGE OVER INFILL CLUSTERED LAYERS FOR SINGLE THREAD" << std::endl;
|
||||
for (auto cluster : clustered_layers_for_threads) {
|
||||
std::cout << "CLUSTER: ";
|
||||
for (auto l : cluster) {
|
||||
std::cout << l << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there.
|
||||
auto gather_areas_w_depth = [target_flow_height_factor](const PrintObject *po, int lidx, float target_flow_height) {
|
||||
// Gather lower layers sparse infill areas, to depth defined by used bridge flow
|
||||
Polygons lower_layers_sparse_infill{};
|
||||
Polygons special_infill{};
|
||||
Polygons not_sparse_infill{};
|
||||
{
|
||||
double bottom_z = layer->print_z - max_bridge_flow_height[candidates.first] - EPSILON;
|
||||
std::vector<LayerSlice::Link> current_links{};
|
||||
current_links.insert(current_links.end(), candidates.first->overlaps_below.begin(),
|
||||
candidates.first->overlaps_below.end());
|
||||
std::vector<LayerSlice::Link> next_links{};
|
||||
double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON;
|
||||
for (int i = int(lidx) - 1; i >= 0; --i) {
|
||||
// Stop iterating if layer is lower than bottom_z.
|
||||
if (po->get_layer(i)->print_z < bottom_z)
|
||||
const Layer *layer = po->get_layer(i);
|
||||
if (layer->print_z < bottom_z)
|
||||
break;
|
||||
for (const auto &link : current_links) {
|
||||
const LayerSlice &slice_below = po->get_layer(i)->lslices_ex[link.slice_idx];
|
||||
next_links.insert(next_links.end(), slice_below.overlaps_below.begin(), slice_below.overlaps_below.end());
|
||||
std::unordered_set<const LayerRegion *> regions_under_to_check;
|
||||
for (const LayerIsland &island : slice_below.islands) {
|
||||
regions_under_to_check.insert(po->get_layer(i)->regions()[island.perimeters.region()]);
|
||||
if (!island.fill_expolygons_composite()) {
|
||||
regions_under_to_check.insert(po->get_layer(i)->regions()[island.fill_region_id]);
|
||||
} else {
|
||||
for (const auto &r : po->get_layer(i)->regions()) {
|
||||
regions_under_to_check.insert(r);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const LayerRegion *region : regions_under_to_check) {
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
bool has_low_density = region->region().config().fill_density.value < 100;
|
||||
bool has_special_infill = region_has_special_infill(region);
|
||||
for (const Surface &surface : region->fill_surfaces()) {
|
||||
if (surface.surface_type == stInternal && has_low_density && !has_special_infill) {
|
||||
if (surface.surface_type == stInternal && has_low_density) {
|
||||
Polygons p = to_polygons(surface.expolygon);
|
||||
lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), p.begin(), p.end());
|
||||
} else if (surface.surface_type == stInternal && has_low_density && has_special_infill) {
|
||||
Polygons p = to_polygons(surface.expolygon);
|
||||
special_infill.insert(special_infill.end(), p.begin(), p.end());
|
||||
} else {
|
||||
Polygons p = to_polygons(surface.expolygon);
|
||||
not_sparse_infill.insert(not_sparse_infill.end(), p.begin(), p.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
current_links = next_links;
|
||||
next_links.clear();
|
||||
lower_layers_sparse_infill = union_(lower_layers_sparse_infill);
|
||||
}
|
||||
|
||||
lower_layers_sparse_infill = intersection(lower_layers_sparse_infill,
|
||||
layer->lslices[int(candidates.first - layer->lslices_ex.data())]);
|
||||
lower_layers_sparse_infill = diff(lower_layers_sparse_infill, not_sparse_infill);
|
||||
special_infill = intersection(special_infill, layer->lslices[int(candidates.first - layer->lslices_ex.data())]);
|
||||
special_infill = diff(special_infill, not_sparse_infill);
|
||||
|
||||
lower_layers_sparse_infill.insert(lower_layers_sparse_infill.end(), special_infill.begin(), special_infill.end());
|
||||
|
||||
if (shrink(lower_layers_sparse_infill, 3.0 * scale_(max_bridge_flow_height[candidates.first])).empty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (expansion_space[candidates.first].empty() && special_infill.empty()) {
|
||||
// there is no expansion space to which can anchors expand on this island, add back original polygons and skip the island
|
||||
for (const Surface *candidate : candidates.second) {
|
||||
bridging_surfaces[candidates.first].emplace_back(candidate, to_polygons(candidate->expolygon),
|
||||
surface_to_region[candidate], 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Polygons expand_area;
|
||||
for (const Surface *sparse_infill : expansion_space[candidates.first]) {
|
||||
assert(sparse_infill->surface_type == stInternal);
|
||||
Polygons a = to_polygons(sparse_infill->expolygon);
|
||||
expand_area.insert(expand_area.end(), a.begin(), a.end());
|
||||
}
|
||||
|
||||
// Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that would otherwise
|
||||
// compete over anchoring sparse infill lines, leaving one area unachored
|
||||
std::sort(candidates.second.begin(), candidates.second.end(), [](const Surface* left, const Surface* right){
|
||||
auto a = get_extents(left->expolygon);
|
||||
auto b = get_extents(right->expolygon);
|
||||
|
||||
if (a.min.x() == b.min.x()) {
|
||||
return a.min.y() < b.min.y();
|
||||
return diff(lower_layers_sparse_infill, not_sparse_infill);
|
||||
};
|
||||
return a.min.x() < b.min.x();
|
||||
});
|
||||
|
||||
std::unordered_map<const LayerRegion *, std::pair<Polygons, Polygons>> infill_and_deep_infill_polygons_per_region;
|
||||
for (const auto &surface_region : surface_to_region) {
|
||||
const LayerRegion *r = surface_region.second;
|
||||
if (infill_and_deep_infill_polygons_per_region.find(r) == infill_and_deep_infill_polygons_per_region.end()) {
|
||||
const Flow &flow = r->bridging_flow(frSolidInfill, true);
|
||||
Polygons infill_region = to_polygons(r->fill_expolygons());
|
||||
Polygons deep_infill_area = closing(infill_region, scale_(0.01), scale_(0.01) + 4.0 * flow.scaled_spacing());
|
||||
Polygons solid_supported_area = expand(not_sparse_infill, 4.0 * flow.scaled_spacing());
|
||||
infill_and_deep_infill_polygons_per_region[r] = {closing(infill_region, float(scale_(0.1))),
|
||||
intersection(lower_layers_sparse_infill,
|
||||
diff(deep_infill_area, solid_supported_area))};
|
||||
}
|
||||
}
|
||||
|
||||
// Lower layers sparse infill sections gathered
|
||||
// now we can intersected them with bridging surface candidates to get actual areas that need and can accumulate
|
||||
// bridging. These areas we then expand (within the surrounding sparse infill only!)
|
||||
// to touch the infill polylines on previous layer.
|
||||
for (const Surface *candidate : candidates.second) {
|
||||
const Flow &flow = surface_to_region[candidate]->bridging_flow(frSolidInfill, true);
|
||||
assert(candidate->surface_type == stInternalSolid);
|
||||
|
||||
Polygons bridged_area = intersection(expand(to_polygons(candidate->expolygon), flow.scaled_spacing()),
|
||||
infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].first);
|
||||
// cut off parts which are not over sparse infill - material overflow
|
||||
Polygons worth_bridging = intersection(bridged_area,
|
||||
infill_and_deep_infill_polygons_per_region[surface_to_region[candidate]].second);
|
||||
if (worth_bridging.empty()) {
|
||||
continue;
|
||||
}
|
||||
bridged_area = intersection(bridged_area, expand(worth_bridging, 5.0 * flow.scaled_spacing()));
|
||||
|
||||
Polygons max_area = expand_area;
|
||||
max_area.insert(max_area.end(), bridged_area.begin(), bridged_area.end());
|
||||
max_area = closing(max_area, flow.scaled_spacing());
|
||||
|
||||
Polylines anchors = intersection_pl(lower_layer_polylines, max_area);
|
||||
if (!special_infill.empty()) {
|
||||
auto part_over_special_infill = intersection(special_infill, bridged_area);
|
||||
auto artificial_boundary = to_polylines(expand(part_over_special_infill, 0.5 * flow.scaled_width()));
|
||||
anchors.insert(anchors.end(), artificial_boundary.begin(), artificial_boundary.end());
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "special", to_lines(part_over_special_infill), to_lines(artificial_boundary),
|
||||
to_lines(anchors), to_lines(expand_area));
|
||||
#endif
|
||||
}
|
||||
anchors = diff_pl(anchors, bridged_area);
|
||||
|
||||
Lines anchors_and_walls = to_lines(anchors);
|
||||
Lines tmp = to_lines(max_area);
|
||||
anchors_and_walls.insert(anchors_and_walls.end(), tmp.begin(), tmp.end());
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "candidate", to_lines(candidate->expolygon), to_lines(bridged_area),
|
||||
to_lines(max_area), (anchors_and_walls));
|
||||
#endif
|
||||
|
||||
double bridging_angle = 0;
|
||||
Polygons tmp_expanded_area = expand(bridged_area, 3.0 * flow.scaled_spacing());
|
||||
for (const ModifiedSurface& s : bridging_surfaces[candidates.first]) {
|
||||
if (!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
||||
bridging_angle = s.bridge_angle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bridging_angle == 0) {
|
||||
AABBTreeLines::LinesDistancer<Line> lines_tree{anchors.empty() ? anchors_and_walls : to_lines(anchors)};
|
||||
// LAMBDA do determine optimal bridging angle
|
||||
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern) {
|
||||
AABBTreeLines::LinesDistancer<Line> lines_tree(anchors);
|
||||
|
||||
std::map<double, int> counted_directions;
|
||||
for (const Polygon &p : bridged_area) {
|
||||
@ -1891,17 +1853,22 @@ void PrintObject::bridge_over_infill()
|
||||
best_dir = {dir_acc / score_acc, score_acc};
|
||||
}
|
||||
}
|
||||
bridging_angle = best_dir.first;
|
||||
double bridging_angle = best_dir.first;
|
||||
if (bridging_angle == 0) {
|
||||
bridging_angle = 0.001;
|
||||
}
|
||||
switch (surface_to_region[candidate]->region().config().fill_pattern.value) {
|
||||
switch (dominant_pattern) {
|
||||
case ipHilbertCurve: bridging_angle += 0.25 * PI; break;
|
||||
case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return bridging_angle;
|
||||
};
|
||||
|
||||
// LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly
|
||||
// generated lines
|
||||
auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) {
|
||||
auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) {
|
||||
for (Line &l : lines) {
|
||||
double ax = double(l.a.x());
|
||||
@ -1924,28 +1891,23 @@ void PrintObject::bridge_over_infill()
|
||||
double aligning_angle = -bridging_angle + PI * 0.5;
|
||||
{
|
||||
polygons_rotate(bridged_area, aligning_angle);
|
||||
lines_rotate(anchors_and_walls, cos(aligning_angle), sin(aligning_angle));
|
||||
lines_rotate(anchors, cos(aligning_angle), sin(aligning_angle));
|
||||
BoundingBox bb_x = get_extents(bridged_area);
|
||||
BoundingBox bb_y = get_extents(anchors_and_walls);
|
||||
BoundingBox bb_y = get_extents(anchors);
|
||||
|
||||
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + flow.scaled_spacing() - 1) / flow.scaled_spacing();
|
||||
const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing();
|
||||
std::vector<Line> vertical_lines(n_vlines);
|
||||
for (size_t i = 0; i < n_vlines; i++) {
|
||||
coord_t x = bb_x.min.x() + i * flow.scaled_spacing();
|
||||
coord_t y_min = bb_y.min.y() - flow.scaled_spacing();
|
||||
coord_t y_max = bb_y.max.y() + flow.scaled_spacing();
|
||||
coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing();
|
||||
coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing();
|
||||
coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing();
|
||||
vertical_lines[i].a = Point{x, y_min};
|
||||
vertical_lines[i].b = Point{x, y_max};
|
||||
}
|
||||
|
||||
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors_and_walls)};
|
||||
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
|
||||
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "sliced", to_lines(bridged_area), anchors_and_walls,
|
||||
vertical_lines, {});
|
||||
#endif
|
||||
|
||||
std::vector<std::vector<Line>> polygon_sections(n_vlines);
|
||||
for (size_t i = 0; i < n_vlines; i++) {
|
||||
auto area_intersections = bridged_area_tree.intersections_with_line<true>(vertical_lines[i]);
|
||||
@ -1959,24 +1921,22 @@ void PrintObject::bridge_over_infill()
|
||||
auto anchors_intersections = anchors_and_walls_tree.intersections_with_line<true>(vertical_lines[i]);
|
||||
|
||||
for (Line §ion : polygon_sections[i]) {
|
||||
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(),
|
||||
section.a,
|
||||
auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a,
|
||||
[](const Point &a, const std::pair<Point, size_t> &b) {
|
||||
return a.y() > b.first.y();
|
||||
});
|
||||
if (maybe_below_anchor != anchors_intersections.rend()) {
|
||||
section.a = maybe_below_anchor->first;
|
||||
section.a.y() -= flow.scaled_width() * (0.5 + 1.0);
|
||||
section.a.y() -= bridging_flow.scaled_width() * (0.5 + 1.0);
|
||||
}
|
||||
|
||||
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(),
|
||||
section.b,
|
||||
auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b,
|
||||
[](const Point &a, const std::pair<Point, size_t> &b) {
|
||||
return a.y() < b.first.y();
|
||||
});
|
||||
if (maybe_upper_anchor != anchors_intersections.end()) {
|
||||
section.b = maybe_upper_anchor->first;
|
||||
section.b.y() += flow.scaled_width() * (0.5 + 1.0);
|
||||
section.b.y() += bridging_flow.scaled_width() * (0.5 + 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2006,10 +1966,8 @@ void PrintObject::bridge_over_infill()
|
||||
for (const auto &polygon_slice : polygon_sections) {
|
||||
std::unordered_set<const Line *> used_segments;
|
||||
for (TracedPoly &traced_poly : current_traced_polys) {
|
||||
auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(),
|
||||
traced_poly.lows.back(), [](const Point &low, const Line &seg) {
|
||||
return seg.b.y() > low.y();
|
||||
});
|
||||
auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(),
|
||||
[](const Point &low, const Line &seg) { return seg.b.y() > low.y(); });
|
||||
|
||||
if (maybe_first_overlap != polygon_slice.end() && // segment exists
|
||||
segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(),
|
||||
@ -2018,28 +1976,28 @@ void PrintObject::bridge_over_infill()
|
||||
// Overlapping segment. In that case, add it
|
||||
// to the traced polygon and add segment to used segments
|
||||
if ((traced_poly.lows.back() - maybe_first_overlap->a).cast<double>().squaredNorm() <
|
||||
36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) {
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a);
|
||||
} else {
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a - Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(maybe_first_overlap->a);
|
||||
}
|
||||
|
||||
if ((traced_poly.highs.back() - maybe_first_overlap->b).cast<double>().squaredNorm() <
|
||||
36.0 * double(flow.scaled_spacing()) * flow.scaled_spacing()) {
|
||||
36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) {
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b);
|
||||
} else {
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b - Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(maybe_first_overlap->b);
|
||||
}
|
||||
used_segments.insert(&(*maybe_first_overlap));
|
||||
} else {
|
||||
// Zero or multiple overlapping segments. Resolving this is nontrivial,
|
||||
// so we just close this polygon and maybe open several new. This will hopefully happen much less often
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
|
||||
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
|
||||
traced_poly.lows.clear();
|
||||
@ -2054,9 +2012,9 @@ void PrintObject::bridge_over_infill()
|
||||
for (const auto &segment : polygon_slice) {
|
||||
if (used_segments.find(&segment) == used_segments.end()) {
|
||||
TracedPoly &new_tp = current_traced_polys.emplace_back();
|
||||
new_tp.lows.push_back(segment.a - Point{flow.scaled_spacing() / 2, 0});
|
||||
new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
new_tp.lows.push_back(segment.a);
|
||||
new_tp.highs.push_back(segment.b - Point{flow.scaled_spacing() / 2, 0});
|
||||
new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0});
|
||||
new_tp.highs.push_back(segment.b);
|
||||
}
|
||||
}
|
||||
@ -2067,95 +2025,179 @@ void PrintObject::bridge_over_infill()
|
||||
Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows));
|
||||
new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend());
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
Lines l{};
|
||||
for (const auto &s : polygon_sections) {
|
||||
l.insert(l.end(), s.begin(), s.end());
|
||||
}
|
||||
debug_draw(std::to_string(lidx) + "reconstructed", l, anchors_and_walls_tree.get_lines(),
|
||||
to_lines(expanded_bridged_area), bridged_area_tree.get_lines());
|
||||
#endif
|
||||
}
|
||||
|
||||
polygons_rotate(expanded_bridged_area, -aligning_angle);
|
||||
expanded_bridged_area = intersection(expanded_bridged_area, max_area);
|
||||
expanded_bridged_area = opening(expanded_bridged_area, flow.scaled_spacing());
|
||||
expand_area = diff(expand_area, expanded_bridged_area);
|
||||
return expanded_bridged_area;
|
||||
};
|
||||
|
||||
bridging_surfaces[candidates.first].emplace_back(candidate, expanded_bridged_area, surface_to_region[candidate],
|
||||
bridging_angle);
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "cadidate_added", to_lines(expanded_bridged_area), to_lines(bridged_area),
|
||||
to_lines(max_area), to_lines(expand_area));
|
||||
#endif
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, clustered_layers_for_threads.size()), [po = static_cast<const PrintObject *>(this),
|
||||
&surfaces_by_layer, &clustered_layers_for_threads,
|
||||
gather_areas_w_depth, &infill_lines,
|
||||
determine_bridging_angle,
|
||||
construct_anchored_polygon](
|
||||
tbb::blocked_range<size_t> r) {
|
||||
for (size_t cluster_idx = r.begin(); cluster_idx < r.end(); cluster_idx++) {
|
||||
for (size_t job_idx = 0; job_idx < clustered_layers_for_threads[cluster_idx].size(); job_idx++) {
|
||||
size_t lidx = clustered_layers_for_threads[cluster_idx][job_idx];
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
// this thread has exclusive access to all surfaces in layers enumerated in
|
||||
// clustered_layers_for_threads[cluster_idx]
|
||||
|
||||
// Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that
|
||||
// would otherwise compete over anchoring sparse infill lines, leaving one area unachored
|
||||
std::sort(surfaces_by_layer[lidx].begin(), surfaces_by_layer[lidx].end(),
|
||||
[](const CandidateSurface &left, const CandidateSurface &right) {
|
||||
auto a = get_extents(left.new_polys);
|
||||
auto b = get_extents(right.new_polys);
|
||||
|
||||
if (a.min.x() == b.min.x()) {
|
||||
return a.min.y() < b.min.y();
|
||||
};
|
||||
return a.min.x() < b.min.x();
|
||||
});
|
||||
|
||||
// Gather deep infill areas, where thick bridges fit
|
||||
coordf_t thick_bridges_depth = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).height();
|
||||
Polygons deep_infill_area = gather_areas_w_depth(po, lidx, thick_bridges_depth * 0.5);
|
||||
|
||||
// Now also remove area that has been already filled on lower layers by bridging expansion - For this
|
||||
// reason we did the clustering of layers per thread.
|
||||
double bottom_z = layer->print_z - thick_bridges_depth - EPSILON;
|
||||
if (job_idx > 0) {
|
||||
for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) {
|
||||
size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx];
|
||||
const Layer *lower_layer = po->get_layer(lower_layer_idx);
|
||||
if (lower_layer->print_z >= bottom_z) {
|
||||
for (const auto &c : surfaces_by_layer[lower_layer_idx]) {
|
||||
deep_infill_area = diff(deep_infill_area, c.new_polys);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors
|
||||
Polygons expansion_area;
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
auto polys = to_polygons(region->fill_surfaces().filter_by_type(stInternal));
|
||||
expansion_area.insert(expansion_area.end(), polys.begin(), polys.end());
|
||||
}
|
||||
expansion_area = closing(expansion_area, SCALED_EPSILON);
|
||||
expansion_area = intersection(expansion_area, deep_infill_area);
|
||||
Polylines anchors = intersection_pl(infill_lines[lidx - 1], expansion_area);
|
||||
|
||||
std::vector<CandidateSurface> expanded_surfaces;
|
||||
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
|
||||
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
|
||||
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
|
||||
Polygons area_to_be_bridge = intersection(candidate.new_polys, deep_infill_area);
|
||||
|
||||
if (area_to_be_bridge.empty())
|
||||
continue;
|
||||
|
||||
area_to_be_bridge = expand(area_to_be_bridge, flow.scaled_spacing());
|
||||
Polygons boundary_area = union_(expansion_area, area_to_be_bridge);
|
||||
Polylines boundary_plines = to_polylines(boundary_area);
|
||||
|
||||
double bridging_angle = 0;
|
||||
if (!anchors.empty()) {
|
||||
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors),
|
||||
candidate.region->region().config().fill_pattern.value);
|
||||
} else {
|
||||
// use expansion boundaries as anchors.
|
||||
// Also, use Infill pattern that is neutral for angle determination, since there are no infill lines.
|
||||
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine);
|
||||
}
|
||||
|
||||
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
|
||||
if (candidate.supported_by_lightning) {
|
||||
boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10)));
|
||||
}
|
||||
Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
|
||||
|
||||
// Check collision with other expanded surfaces
|
||||
{
|
||||
bool reconstruct = false;
|
||||
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
|
||||
for (const CandidateSurface &s : expanded_surfaces) {
|
||||
if (!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
||||
bridging_angle = s.bridge_angle;
|
||||
reconstruct = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reconstruct) {
|
||||
bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle);
|
||||
}
|
||||
}
|
||||
|
||||
bridging_area = intersection(bridging_area, boundary_area);
|
||||
bridging_area = opening(bridging_area, flow.scaled_spacing());
|
||||
expansion_area = diff(expansion_area, bridging_area);
|
||||
|
||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) +
|
||||
"_expanded_bridging",
|
||||
to_lines(layer->lslices), to_lines(boundary_plines), to_lines(candidate.new_polys),
|
||||
to_lines(bridging_area));
|
||||
#endif
|
||||
|
||||
expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, candidate.layer_index, bridging_area,
|
||||
candidate.region, bridging_angle, candidate.supported_by_lightning));
|
||||
}
|
||||
surfaces_by_layer[lidx].swap(expanded_surfaces);
|
||||
expanded_surfaces.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info();
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this,
|
||||
&bridging_surfaces](tbb::blocked_range<size_t> r) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
continue;
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
std::unordered_map<const LayerRegion*, Surfaces> new_surfaces;
|
||||
|
||||
for (const LayerSlice &slice : layer->lslices_ex) {
|
||||
if (const auto &modified_surfaces = bridging_surfaces.find(&slice);
|
||||
modified_surfaces != bridging_surfaces.end()) {
|
||||
std::unordered_set<LayerRegion *> regions_to_check;
|
||||
for (const LayerIsland &island : slice.islands) {
|
||||
regions_to_check.insert(layer->regions()[island.perimeters.region()]);
|
||||
if (!island.fill_expolygons_composite()) {
|
||||
regions_to_check.insert(layer->regions()[island.fill_region_id]);
|
||||
} else {
|
||||
for (LayerRegion *r : layer->regions()) {
|
||||
regions_to_check.insert(r);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Polygons cut_from_infill{};
|
||||
for (const auto &surface : modified_surfaces->second) {
|
||||
for (const auto &surface : surfaces_by_layer.at(lidx)) {
|
||||
cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end());
|
||||
}
|
||||
|
||||
for (const LayerRegion *region : regions_to_check) {
|
||||
for (const ModifiedSurface &s : modified_surfaces->second) {
|
||||
for (const Surface &surface : region->m_fill_surfaces.surfaces) {
|
||||
if (s.original_surface == &surface) {
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
Surfaces new_surfaces;
|
||||
|
||||
for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) {
|
||||
for (Surface &surface : region->m_fill_surfaces.surfaces) {
|
||||
if (cs.original_surface == &surface) {
|
||||
Surface tmp(surface, {});
|
||||
for (const ExPolygon &expoly : diff_ex(surface.expolygon, s.new_polys)) {
|
||||
for (const ExPolygon &expoly : diff_ex(surface.expolygon, cs.new_polys)) {
|
||||
if (expoly.area() > region->flow(frSolidInfill).scaled_width() * scale_(4.0)) {
|
||||
new_surfaces[region].emplace_back(tmp, expoly);
|
||||
new_surfaces.emplace_back(tmp, expoly);
|
||||
}
|
||||
}
|
||||
tmp.surface_type = stInternalBridge;
|
||||
tmp.bridge_angle = s.bridge_angle;
|
||||
for (const ExPolygon &expoly : union_ex(s.new_polys)) {
|
||||
new_surfaces[region].emplace_back(tmp, expoly);
|
||||
tmp.bridge_angle = cs.bridge_angle;
|
||||
for (const ExPolygon &expoly : union_ex(cs.new_polys)) {
|
||||
new_surfaces.emplace_back(tmp, expoly);
|
||||
}
|
||||
surface.clear();
|
||||
} else if (surface.surface_type == stInternal) {
|
||||
Surface tmp(surface, {});
|
||||
for (const ExPolygon &expoly : diff_ex(surface.expolygon, cut_from_infill)) {
|
||||
new_surfaces[region].emplace_back(tmp, expoly);
|
||||
new_surfaces.emplace_back(tmp, expoly);
|
||||
}
|
||||
} else {
|
||||
new_surfaces[region].push_back(surface);
|
||||
surface.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
if (new_surfaces.find(region) != new_surfaces.end()) {
|
||||
region->m_fill_surfaces = new_surfaces[region];
|
||||
}
|
||||
region->m_fill_surfaces.surfaces.insert(region->m_fill_surfaces.surfaces.end(), new_surfaces.begin(), new_surfaces.end());
|
||||
region->m_fill_surfaces.surfaces.erase(std::remove_if(region->m_fill_surfaces.surfaces.begin(),
|
||||
region->m_fill_surfaces.surfaces.end(),
|
||||
[](const Surface &s) { return s.empty(); }),
|
||||
region->m_fill_surfaces.surfaces.end());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &pr
|
||||
// this->minimum_support_area =
|
||||
// this->minimum_bottom_area =
|
||||
// this->support_offset =
|
||||
// this->support_tree_branch_distance = 2.5 * line_width ??
|
||||
this->support_tree_branch_distance = scaled<coord_t>(config.support_tree_branch_distance.value);
|
||||
this->support_tree_angle = std::clamp<double>(config.support_tree_angle * M_PI / 180., 0., 0.5 * M_PI - EPSILON);
|
||||
this->support_tree_angle_slow = std::clamp<double>(config.support_tree_angle_slow * M_PI / 180., 0., this->support_tree_angle - EPSILON);
|
||||
this->support_tree_branch_diameter = scaled<coord_t>(config.support_tree_branch_diameter.value);
|
||||
@ -329,6 +329,7 @@ void TreeModelVolumes::precalculate(const PrintObject& print_object, const coord
|
||||
for (int k = int(j - 1); k >= int(i); -- k) {
|
||||
std::string legend = format("radius-%1%", unscaled<float>(sorted[k].first.first));
|
||||
expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) });
|
||||
SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d-%s.svg", name.data(), sorted[i].first.second, legend.c_str()), { expolygons_with_attributes.back() });
|
||||
}
|
||||
// Render the range of per radius collision polygons into a common SVG.
|
||||
SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes);
|
||||
@ -416,9 +417,6 @@ const Polygons& TreeModelVolumes::getAvoidance(const coord_t orig_radius, LayerI
|
||||
|
||||
const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, LayerIndex layer_idx, std::function<void()> throw_on_cancel) const
|
||||
{
|
||||
if (orig_radius == 0)
|
||||
return this->getCollision(0, layer_idx, true);
|
||||
|
||||
const coord_t radius = ceilRadius(orig_radius);
|
||||
if (std::optional<std::reference_wrapper<const Polygons>> result = m_placeable_areas_cache.getArea({ radius, layer_idx }); result)
|
||||
return (*result).get();
|
||||
@ -426,6 +424,10 @@ const Polygons& TreeModelVolumes::getPlaceableAreas(const coord_t orig_radius, L
|
||||
BOOST_LOG_TRIVIAL(error_level_not_in_cache) << "Had to calculate Placeable Areas at radius " << radius << " and layer " << layer_idx << ", but precalculate was called. Performance may suffer!";
|
||||
tree_supports_show_error("Not precalculated Placeable areas requested."sv, false);
|
||||
}
|
||||
if (orig_radius == 0)
|
||||
// Placable areas for radius 0 are calculated in the general collision code.
|
||||
return this->getCollision(0, layer_idx, true);
|
||||
else
|
||||
const_cast<TreeModelVolumes*>(this)->calculatePlaceables(radius, layer_idx, throw_on_cancel);
|
||||
return getPlaceableAreas(orig_radius, layer_idx, throw_on_cancel);
|
||||
}
|
||||
@ -470,6 +472,7 @@ void TreeModelVolumes::calculateCollision(const std::vector<RadiusLayerPair> &ke
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate collisions and placable areas for radius and for layer 0 to max_layer_idx inclusive.
|
||||
void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex max_layer_idx, std::function<void()> throw_on_cancel)
|
||||
{
|
||||
// assert(radius == this->ceilRadius(radius));
|
||||
@ -480,12 +483,14 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
std::sort(layer_outline_indices.begin(), layer_outline_indices.end(),
|
||||
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
|
||||
|
||||
const LayerIndex min_layer_last = m_collision_cache.getMaxCalculatedLayer(radius);
|
||||
std::vector<Polygons> data(max_layer_idx + 1 - min_layer_last, Polygons{});
|
||||
// Layer range for which the collisions will be calculated.
|
||||
LayerPolygonCache data;
|
||||
data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1);
|
||||
|
||||
const bool calculate_placable = m_support_rests_on_model && radius == 0;
|
||||
std::vector<Polygons> data_placeable;
|
||||
LayerPolygonCache data_placeable;
|
||||
if (calculate_placable)
|
||||
data_placeable = std::vector<Polygons>(max_layer_idx + 1 - min_layer_last, Polygons{});
|
||||
data_placeable.allocate(data.idx_begin, data.idx_end);
|
||||
|
||||
for (size_t outline_idx : layer_outline_indices)
|
||||
if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) {
|
||||
@ -493,8 +498,6 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
const coord_t layer_height = settings.layer_height;
|
||||
const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height)));
|
||||
const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height)));
|
||||
const LayerIndex max_required_layer = std::min<LayerIndex>(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers));
|
||||
const LayerIndex min_layer_bottom = std::max<LayerIndex>(0, min_layer_last - int(z_distance_bottom_layers));
|
||||
const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist :
|
||||
// technically this causes collision for the normal xy_distance to be larger by m_current_min_xy_dist_delta for all
|
||||
// not currently processing meshes as this delta will be added at request time.
|
||||
@ -505,35 +508,40 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
settings.support_xy_distance;
|
||||
|
||||
// 1) Calculate offsets of collision areas in parallel.
|
||||
std::vector<Polygons> collision_areas_offsetted(max_required_layer + 1 - min_layer_bottom);
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_bottom, max_required_layer + 1),
|
||||
[&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, min_layer_bottom, &collision_areas_offsetted, &throw_on_cancel]
|
||||
LayerPolygonCache collision_areas_offsetted;
|
||||
collision_areas_offsetted.allocate(
|
||||
std::max<LayerIndex>(0, data.idx_begin - z_distance_bottom_layers),
|
||||
std::min<LayerIndex>(outlines.size(), data.idx_end + z_distance_top_layers));
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(collision_areas_offsetted.idx_begin, collision_areas_offsetted.idx_end),
|
||||
[&outlines, &machine_border = std::as_const(m_machine_border), offset_value = radius + xy_distance, &collision_areas_offsetted, &throw_on_cancel]
|
||||
(const tbb::blocked_range<LayerIndex> &range) {
|
||||
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
|
||||
Polygons collision_areas = machine_border;
|
||||
append(collision_areas, outlines[layer_idx]);
|
||||
// jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model.
|
||||
// if a key does not exist when it is accessed it is added!
|
||||
collision_areas_offsetted[layer_idx - min_layer_bottom] = offset_value == 0 ? union_(collision_areas) : offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2);
|
||||
collision_areas_offsetted[layer_idx] = offset_value == 0 ?
|
||||
union_(collision_areas) :
|
||||
offset(union_ex(collision_areas), offset_value, ClipperLib::jtMiter, 1.2);
|
||||
throw_on_cancel();
|
||||
}
|
||||
});
|
||||
|
||||
// 2) Sum over top / bottom ranges.
|
||||
const bool last = outline_idx == layer_outline_indices.size();
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(min_layer_last + 1, max_layer_idx + 1),
|
||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, min_layer_bottom, radius,
|
||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, min_layer_last, last, &throw_on_cancel]
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.idx_begin, data.idx_end),
|
||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
||||
(const tbb::blocked_range<LayerIndex>& range) {
|
||||
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
|
||||
Polygons collisions;
|
||||
for (int i = -z_distance_bottom_layers; i <= z_distance_top_layers; ++ i) {
|
||||
int j = layer_idx + i - min_layer_bottom;
|
||||
if (j >= 0 && j < int(collision_areas_offsetted.size()) && i <= 0)
|
||||
for (int i = - z_distance_bottom_layers; i <= 0; ++ i)
|
||||
if (int j = layer_idx + i; collision_areas_offsetted.has(j))
|
||||
append(collisions, collision_areas_offsetted[j]);
|
||||
else if (j >= 0 && layer_idx + i < int(outlines.size()) && i > 0) {
|
||||
for (int i = 1; i <= z_distance_top_layers; ++ i)
|
||||
if (int j = layer_idx + i; j < int(outlines.size())) {
|
||||
Polygons collision_areas_original = machine_border;
|
||||
append(collision_areas_original, outlines[layer_idx + i]);
|
||||
append(collision_areas_original, outlines[j]);
|
||||
|
||||
// If just the collision (including the xy distance) of the layers above is accumulated, it leads to the
|
||||
// following issue:
|
||||
@ -566,37 +574,37 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
// the conditional -0.5 ensures that plastic can never touch on the diagonal
|
||||
// downward when the z_distance_top_layers = 1. It is assumed to be better to
|
||||
// not support an overhang<90 degree than to risk fusing to it.
|
||||
|
||||
collision_areas_original = offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2);
|
||||
append(collisions, collision_areas_original);
|
||||
append(collisions, offset(union_ex(collision_areas_original), radius + required_range_x, ClipperLib::jtMiter, 1.2));
|
||||
}
|
||||
}
|
||||
collisions = last && layer_idx < int(anti_overhang.size()) ? union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) : union_(collisions);
|
||||
auto &dst = data[layer_idx - (min_layer_last + 1)];
|
||||
if (last) {
|
||||
collisions = processing_last_mesh && layer_idx < int(anti_overhang.size()) ?
|
||||
union_(collisions, offset(union_ex(anti_overhang[layer_idx]), radius, ClipperLib::jtMiter, 1.2)) :
|
||||
union_(collisions);
|
||||
auto &dst = data[layer_idx];
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
collisions = union_(collisions, dst);
|
||||
dst = polygons_simplify(collisions, min_resolution);
|
||||
} else
|
||||
append(dst, collisions);
|
||||
append(dst, std::move(collisions));
|
||||
throw_on_cancel();
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Optionally calculate placables.
|
||||
if (calculate_placable) {
|
||||
// Calculating both the collision areas and placable areas.
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(min_layer_last + 1, z_distance_bottom_layers + 1), max_layer_idx + 1),
|
||||
[&collision_areas_offsetted, &anti_overhang = m_anti_overhang, min_layer_bottom, z_distance_bottom_layers, last, min_resolution = m_min_resolution, &data_placeable, min_layer_last, &throw_on_cancel]
|
||||
// Now calculate the placable areas.
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(std::max(data.idx_begin, 1), data.idx_end),
|
||||
[&collision_areas_offsetted, &anti_overhang = m_anti_overhang, processing_last_mesh,
|
||||
min_resolution = m_min_resolution, &data_placeable, &throw_on_cancel]
|
||||
(const tbb::blocked_range<LayerIndex>& range) {
|
||||
for (LayerIndex layer_idx = range.begin(); layer_idx != range.end(); ++ layer_idx) {
|
||||
LayerIndex layer_idx_below = layer_idx - (z_distance_bottom_layers + 1) - min_layer_bottom;
|
||||
LayerIndex layer_idx_below = layer_idx - 1;
|
||||
assert(layer_idx_below >= 0);
|
||||
auto ¤t = collision_areas_offsetted[layer_idx - min_layer_bottom];
|
||||
auto &below = collision_areas_offsetted[layer_idx_below];
|
||||
auto placable = diff(below, layer_idx < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx - (z_distance_bottom_layers + 1)]) : current);
|
||||
auto &dst = data_placeable[layer_idx - (min_layer_last + 1)];
|
||||
if (last) {
|
||||
const Polygons ¤t = collision_areas_offsetted[layer_idx];
|
||||
const Polygons &below = collision_areas_offsetted[layer_idx_below];
|
||||
Polygons placable = diff(below, layer_idx_below < int(anti_overhang.size()) ? union_(current, anti_overhang[layer_idx_below]) : current);
|
||||
auto &dst = data_placeable[layer_idx];
|
||||
if (processing_last_mesh) {
|
||||
if (! dst.empty())
|
||||
placable = union_(placable, dst);
|
||||
dst = polygons_simplify(placable, min_resolution);
|
||||
@ -619,9 +627,9 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
}
|
||||
#endif
|
||||
throw_on_cancel();
|
||||
m_collision_cache.insert(std::move(data), min_layer_last + 1, radius);
|
||||
m_collision_cache.insert(std::move(data), radius);
|
||||
if (calculate_placable)
|
||||
m_placeable_areas_cache.insert(std::move(data_placeable), min_layer_last + 1, radius);
|
||||
m_placeable_areas_cache.insert(std::move(data_placeable), radius);
|
||||
}
|
||||
|
||||
void TreeModelVolumes::calculateCollisionHolefree(const std::vector<RadiusLayerPair> &keys, std::function<void()> throw_on_cancel)
|
||||
|
@ -328,6 +328,26 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Caching polygons for a range of layers.
|
||||
struct LayerPolygonCache {
|
||||
std::vector<Polygons> polygons;
|
||||
LayerIndex idx_begin;
|
||||
LayerIndex idx_end;
|
||||
|
||||
void allocate(LayerIndex aidx_begin, LayerIndex aidx_end) {
|
||||
this->idx_begin = aidx_begin;
|
||||
this->idx_end = aidx_end;
|
||||
this->polygons.assign(aidx_end - aidx_begin, {});
|
||||
}
|
||||
|
||||
LayerIndex begin() const { return idx_begin; }
|
||||
LayerIndex end() const { return idx_end; }
|
||||
size_t size() const { return polygons.size(); }
|
||||
|
||||
bool has(LayerIndex idx) const { return idx >= idx_begin && idx < idx_end; }
|
||||
Polygons& operator[](LayerIndex idx) { return polygons[idx + idx_begin]; }
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Convenience typedef for the keys to the caches
|
||||
*/
|
||||
@ -363,6 +383,13 @@ private:
|
||||
for (auto &d : in)
|
||||
m_data[first_layer_idx ++].emplace(radius, std::move(d));
|
||||
}
|
||||
void insert(LayerPolygonCache &&in, coord_t radius) {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
LayerIndex i = in.idx_begin;
|
||||
allocate_layers(i + LayerIndex(in.size()));
|
||||
for (auto &d : in.polygons)
|
||||
m_data[i ++].emplace(radius, std::move(d));
|
||||
}
|
||||
/*!
|
||||
* \brief Checks a cache for a given RadiusLayerPair and returns it if it is found
|
||||
* \param key RadiusLayerPair of the requested areas. The radius will be calculated up to the provided layer.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -122,7 +122,7 @@ struct SupportElementStateBits {
|
||||
bool use_min_xy_dist : 1;
|
||||
|
||||
/*!
|
||||
* \brief True if this Element or any parent provides support to a support roof.
|
||||
* \brief True if this Element or any parent (element above) provides support to a support roof.
|
||||
*/
|
||||
bool supports_roof : 1;
|
||||
|
||||
@ -193,7 +193,7 @@ struct SupportElementState : public SupportElementStateBits
|
||||
double elephant_foot_increases;
|
||||
|
||||
/*!
|
||||
* \brief The element trys not to move until this dtt is reached, is set to 0 if the element had to move.
|
||||
* \brief The element tries to not move until this dtt is reached, is set to 0 if the element had to move.
|
||||
*/
|
||||
uint32_t dont_move_until;
|
||||
|
||||
@ -218,6 +218,8 @@ struct SupportElementState : public SupportElementStateBits
|
||||
dst.skip_ovalisation = false;
|
||||
return dst;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; }
|
||||
};
|
||||
|
||||
struct SupportElement
|
||||
|
@ -34,6 +34,10 @@
|
||||
|
||||
// #define SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
#define DEBUG_INTERSECTIONLINE
|
||||
#endif
|
||||
|
||||
#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
|
||||
#include "SVG.hpp"
|
||||
#endif
|
||||
@ -125,7 +129,7 @@ public:
|
||||
};
|
||||
uint32_t flags { 0 };
|
||||
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
#ifdef DEBUG_INTERSECTIONLINE
|
||||
enum class Source {
|
||||
BottomPlane,
|
||||
TopPlane,
|
||||
@ -1446,19 +1450,19 @@ static std::vector<Polygons> make_slab_loops(
|
||||
for (const IntersectionLine &l : lines.at_slice[slice_below])
|
||||
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
|
||||
in.emplace_back(l);
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
#ifdef DEBUG_INTERSECTIONLINE
|
||||
in.back().source = IntersectionLine::Source::BottomPlane;
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
}
|
||||
}
|
||||
{
|
||||
// Edges in between slice_below and slice_above.
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
#ifdef DEBUG_INTERSECTIONLINE
|
||||
size_t old_size = in.size();
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
// Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges.
|
||||
append(in, lines.between_slices[line_idx]);
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
#ifdef DEBUG_INTERSECTIONLINE
|
||||
for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
|
||||
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
|
||||
it->source = IntersectionLine::Source::Slab;
|
||||
@ -1476,7 +1480,7 @@ static std::vector<Polygons> make_slab_loops(
|
||||
l.edge_a_id += num_edges;
|
||||
if (l.edge_b_id >= 0)
|
||||
l.edge_b_id += num_edges;
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
#ifdef DEBUG_INTERSECTIONLINE
|
||||
l.source = IntersectionLine::Source::TopPlane;
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
}
|
||||
|
@ -157,26 +157,23 @@ Point Bed3D::point_projection(const Point& point) const
|
||||
return m_polygon.point_projection(point);
|
||||
}
|
||||
|
||||
void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes, bool show_texture)
|
||||
void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture)
|
||||
{
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture, false);
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false);
|
||||
}
|
||||
|
||||
void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor)
|
||||
{
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, false, true);
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true);
|
||||
}
|
||||
|
||||
void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
|
||||
bool show_axes, bool show_texture, bool picking)
|
||||
bool show_texture, bool picking)
|
||||
{
|
||||
m_scale_factor = scale_factor;
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
if (show_axes)
|
||||
render_axes();
|
||||
|
||||
m_model.model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR);
|
||||
|
||||
switch (m_type)
|
||||
@ -366,9 +363,6 @@ std::tuple<Bed3D::Type, std::string, std::string> Bed3D::detect_type(const Point
|
||||
|
||||
void Bed3D::render_axes()
|
||||
{
|
||||
if (!m_show_axes)
|
||||
return;
|
||||
|
||||
if (m_build_volume.valid())
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
m_axes.render(Transform3d::Identity(), 0.25f);
|
||||
|
@ -84,7 +84,6 @@ private:
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
|
||||
float m_scale_factor{ 1.0f };
|
||||
bool m_show_axes{ true };
|
||||
|
||||
public:
|
||||
Bed3D() = default;
|
||||
@ -112,9 +111,8 @@ public:
|
||||
bool contains(const Point& point) const;
|
||||
Point point_projection(const Point& point) const;
|
||||
|
||||
void toggle_show_axes() { m_show_axes = !m_show_axes; }
|
||||
|
||||
void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes, bool show_texture);
|
||||
void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture);
|
||||
void render_axes();
|
||||
void render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor);
|
||||
|
||||
private:
|
||||
@ -125,8 +123,7 @@ private:
|
||||
void init_contourlines();
|
||||
static std::tuple<Type, std::string, std::string> detect_type(const Pointfs& shape);
|
||||
void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
|
||||
bool show_axes, bool show_texture, bool picking);
|
||||
void render_axes();
|
||||
bool show_texture, bool picking);
|
||||
void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture);
|
||||
void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
|
@ -291,7 +291,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
(config->opt_bool("support_material") ||
|
||||
config->opt_int("support_material_enforce_layers") > 0);
|
||||
for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter",
|
||||
"support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_top_rate" })
|
||||
"support_tree_branch_diameter_angle", "support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" })
|
||||
toggle_field(key, has_organic_supports);
|
||||
|
||||
for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder",
|
||||
|
@ -206,7 +206,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
|
||||
reduce_stack(stack, struct_size);
|
||||
}
|
||||
if (!file.has_extension() && stack.size() == struct_size)
|
||||
stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), GUI::format_wxstr(file.filename().string()), true)); // filename string to wstring?
|
||||
stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), boost::nowide::widen(file.filename().string()), true)); // filename string to wstring?
|
||||
return struct_size + 1;
|
||||
};
|
||||
|
||||
@ -223,7 +223,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
|
||||
path = boost::filesystem::path(extra.substr(0, extra_size));
|
||||
} else {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = GUI::format(wname);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
path = boost::filesystem::path(name);
|
||||
}
|
||||
assert(!path.empty());
|
||||
@ -247,7 +247,7 @@ FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* ar
|
||||
if (!stack.empty())
|
||||
parent = stack.back();
|
||||
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
|
||||
m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
|
||||
m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
|
||||
entry_count++;
|
||||
}
|
||||
}
|
||||
|
@ -1573,8 +1573,10 @@ void GLCanvas3D::render()
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque);
|
||||
_render_sla_slices();
|
||||
_render_selection();
|
||||
if (m_show_bed_axes)
|
||||
_render_bed_axes();
|
||||
if (is_looking_downward)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false, true);
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), false);
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
_render_gcode();
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent);
|
||||
@ -1597,7 +1599,7 @@ void GLCanvas3D::render()
|
||||
_render_selection_sidebar_hints();
|
||||
_render_current_gizmo();
|
||||
if (!is_looking_downward)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true, true);
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), true);
|
||||
|
||||
#if ENABLE_RAYCAST_PICKING_DEBUG
|
||||
if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
|
||||
@ -2355,7 +2357,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
||||
#else /* __APPLE__ */
|
||||
case WXK_CONTROL_D:
|
||||
#endif /* __APPLE__ */
|
||||
m_bed.toggle_show_axes();
|
||||
m_show_bed_axes = !m_show_bed_axes;
|
||||
m_dirty = true;
|
||||
break;
|
||||
#ifdef __APPLE__
|
||||
@ -4423,7 +4425,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
if (thumbnail_params.show_bed)
|
||||
_render_bed(view_matrix, projection_matrix, !camera.is_looking_downward(), false);
|
||||
_render_bed(view_matrix, projection_matrix, !camera.is_looking_downward());
|
||||
|
||||
// restore background color
|
||||
if (thumbnail_params.transparent_background)
|
||||
@ -5477,7 +5479,7 @@ void GLCanvas3D::_render_background()
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes)
|
||||
void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom)
|
||||
{
|
||||
float scale_factor = 1.0;
|
||||
#if ENABLE_RETINA_GL
|
||||
@ -5491,7 +5493,12 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation);
|
||||
|
||||
m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes, show_texture);
|
||||
m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_texture);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_bed_axes()
|
||||
{
|
||||
m_bed.render_axes();
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom)
|
||||
|
@ -522,6 +522,7 @@ private:
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
std::vector<int> m_hover_volume_idxs;
|
||||
bool m_show_bed_axes{ true };
|
||||
|
||||
// Following variable is obsolete and it should be safe to remove it.
|
||||
// I just don't want to do it now before a release (Lukas Matena 24.3.2019)
|
||||
@ -984,7 +985,8 @@ private:
|
||||
void _picking_pass();
|
||||
void _rectangular_selection_picking_pass();
|
||||
void _render_background();
|
||||
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes);
|
||||
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom);
|
||||
void _render_bed_axes();
|
||||
void _render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom);
|
||||
void _render_objects(GLVolumeCollection::ERenderType type);
|
||||
void _render_gcode();
|
||||
|
@ -2305,18 +2305,6 @@ bool GUI_App::load_language(wxString language, bool initial)
|
||||
// Override language at the active wxTranslations class (which is stored in the active m_wxLocale)
|
||||
// to load possibly different dictionary, for example, load Czech dictionary for Slovak language.
|
||||
wxTranslations::Get()->SetLanguage(language_dict);
|
||||
{
|
||||
// ysFIXME after fix for wxWidgets issue (https://github.com/wxWidgets/wxWidgets/issues/23210)
|
||||
// UKR Localization specific workaround till the wxWidgets doesn't fixed:
|
||||
// From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472),
|
||||
// where LocaleTag is a Tag of locale in BCP 47 - like notation.
|
||||
// For Ukrainian Language LocaleTag is "uk".
|
||||
// But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk",
|
||||
// and, as a result, locales are set to English_United Kingdom
|
||||
|
||||
if (language_info->CanonicalName == "uk")
|
||||
setlocale(0, language_info->GetCanonicalWithRegion().data());
|
||||
}
|
||||
m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
|
||||
m_imgui->set_language(into_u8(language_info->CanonicalName));
|
||||
//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
|
||||
|
@ -707,6 +707,12 @@ void ObjectManipulation::update_ui_from_settings()
|
||||
|
||||
void ObjectManipulation::update_settings_value(const Selection& selection)
|
||||
{
|
||||
if (selection.is_empty()) {
|
||||
// No selection, reset the cache.
|
||||
reset_settings_value();
|
||||
return;
|
||||
}
|
||||
|
||||
m_new_move_label_string = L("Position");
|
||||
m_new_rotate_label_string = L("Rotation");
|
||||
m_new_scale_label_string = L("Scale factors");
|
||||
@ -836,11 +842,6 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
m_new_enabled = true;
|
||||
}
|
||||
else {
|
||||
// No selection, reset the cache.
|
||||
// assert(selection.is_empty());
|
||||
reset_settings_value();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_if_dirty()
|
||||
@ -1395,8 +1396,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
|
||||
if (new_value > 0.0)
|
||||
change_size_value(axis, new_value);
|
||||
else {
|
||||
new_value = m_cache.size(axis);
|
||||
m_cache.size(axis) = 0.0;
|
||||
Vec3d& size = m_imperial_units ? m_cache.size_inches : m_cache.size;
|
||||
new_value = size(axis);
|
||||
size(axis) = 0.0;
|
||||
m_cache.size_rounded(axis) = DBL_MAX;
|
||||
change_size_value(axis, new_value);
|
||||
}
|
||||
|
@ -799,7 +799,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
|
||||
|
||||
void GLGizmoCut3D::render_cut_line()
|
||||
{
|
||||
if (!cut_line_processing() || m_line_end == Vec3d::Zero())
|
||||
if (!cut_line_processing() || m_line_end.isApprox(Vec3d::Zero()))
|
||||
return;
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
@ -1130,7 +1130,7 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data)
|
||||
rotation[m_hover_id] = theta;
|
||||
|
||||
const Transform3d rotation_tmp = m_start_dragging_m * rotation_transform(rotation);
|
||||
const bool update_tbb = m_rotation_m.rotation() != rotation_tmp.rotation();
|
||||
const bool update_tbb = !m_rotation_m.rotation().isApprox(rotation_tmp.rotation());
|
||||
m_rotation_m = rotation_tmp;
|
||||
if (update_tbb)
|
||||
m_transformed_bounding_box = transformed_bounding_box(m_plane_center, m_rotation_m);
|
||||
@ -1262,7 +1262,7 @@ void GLGizmoCut3D::update_bb()
|
||||
const BoundingBoxf3 box = bounding_box();
|
||||
if (!box.defined)
|
||||
return;
|
||||
if (m_max_pos != box.max || m_min_pos != box.min) {
|
||||
if (!m_max_pos.isApprox(box.max) || !m_min_pos.isApprox(box.min)) {
|
||||
|
||||
m_bounding_box = box;
|
||||
|
||||
@ -1679,7 +1679,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
|
||||
|
||||
const bool has_connectors = !connectors.empty();
|
||||
|
||||
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center == m_plane_center;
|
||||
const bool is_cut_plane_init = m_rotation_m.isApprox(Transform3d::Identity()) && m_bb_center.isApprox(m_plane_center);
|
||||
m_imgui->disabled_begin(is_cut_plane_init);
|
||||
wxString act_name = _L("Reset cutting plane");
|
||||
if (render_reset_button("cut_plane", into_u8(act_name))) {
|
||||
@ -2247,7 +2247,7 @@ void GLGizmoCut3D::update_connector_shape()
|
||||
|
||||
bool GLGizmoCut3D::cut_line_processing() const
|
||||
{
|
||||
return m_line_beg != Vec3d::Zero();
|
||||
return !m_line_beg.isApprox(Vec3d::Zero());
|
||||
}
|
||||
|
||||
void GLGizmoCut3D::discard_cut_line_processing()
|
||||
|
@ -5576,7 +5576,7 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = GUI::format(wname);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
fs::path archive_path(name);
|
||||
|
||||
std::string extra(1024, 0);
|
||||
@ -6503,9 +6503,9 @@ void Plater::export_stl_obj(bool extended, bool selection_only)
|
||||
}
|
||||
}
|
||||
|
||||
if (path.EndsWith(".stl"))
|
||||
if (path.Lower().EndsWith(".stl"))
|
||||
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
|
||||
else if (path.EndsWith(".obj"))
|
||||
else if (path.Lower().EndsWith(".obj"))
|
||||
Slic3r::store_obj(path_u8.c_str(), &mesh);
|
||||
// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
|
||||
}
|
||||
|
@ -1531,6 +1531,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("support_tree_branch_diameter", category_path + "tree_branch_diameter");
|
||||
optgroup->append_single_option_line("support_tree_branch_diameter_angle", category_path + "tree_branch_diameter_angle");
|
||||
optgroup->append_single_option_line("support_tree_tip_diameter", category_path + "tree_tip_diameter");
|
||||
optgroup->append_single_option_line("support_tree_branch_distance", category_path + "tree_branch_distance");
|
||||
optgroup->append_single_option_line("support_tree_top_rate", category_path + "tree_top_rate");
|
||||
|
||||
page = add_options_page(L("Speed"), "time");
|
||||
|
@ -1024,9 +1024,15 @@ TEST_CASE("pointOnPolygonContour", "[Geometry]") {
|
||||
REQUIRE(getX(first) == getX(ecache.coords(0)));
|
||||
REQUIRE(getY(first) == getY(ecache.coords(0)));
|
||||
|
||||
if constexpr (ClosureTypeV<PolygonImpl> == Closure::CLOSED) {
|
||||
auto last = *std::prev(input.end());
|
||||
REQUIRE(getX(last) == getX(ecache.coords(1.0)));
|
||||
REQUIRE(getY(last) == getY(ecache.coords(1.0)));
|
||||
} else {
|
||||
auto last = *input.begin();
|
||||
REQUIRE(getX(last) == getX(ecache.coords(1.0)));
|
||||
REQUIRE(getY(last) == getY(ecache.coords(1.0)));
|
||||
}
|
||||
|
||||
for(int i = 0; i <= 100; i++) {
|
||||
auto v = ecache.coords(i*(0.01));
|
||||
|
Loading…
Reference in New Issue
Block a user