From 86c8207d31d7f6c08a5f9aa956f6ebb300c4dd33 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 26 Nov 2016 12:28:39 +0100 Subject: [PATCH] Ported make_perimeters() and infill() to C++/XS, use pure C++ threads, cherry picked from @alexrj 66591bcc556c01572ec7519b1f8cb4ee2d430685 --- lib/Slic3r/Print.pm | 4 + lib/Slic3r/Print/Object.pm | 134 +--------------------- xs/src/libslic3r/Print.hpp | 10 +- xs/src/libslic3r/PrintObject.cpp | 185 +++++++++++++++++++++++++++++++ xs/xsp/Print.xsp | 2 + 5 files changed, 202 insertions(+), 133 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 303058c3f..47a58c3a0 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -41,8 +41,12 @@ sub size { sub process { my ($self) = @_; + $self->status_cb->(20, "Generating perimeters"); $_->make_perimeters for @{$self->objects}; + + $self->status_cb->(70, "Infilling layers"); $_->infill for @{$self->objects}; + $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; # must come after make_skirt diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 00b3ab669..709bc65e4 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -371,114 +371,11 @@ sub _slice_region { # 2) Increases an "extra perimeters" counter at region slices where needed. # 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). sub make_perimeters { - my $self = shift; + my ($self) = @_; # prerequisites $self->slice; - - return if $self->step_done(STEP_PERIMETERS); - $self->set_step_started(STEP_PERIMETERS); - $self->print->status_cb->(20, "Generating perimeters"); - - # Merge region slices if they were split into types. - # FIXME this is using a safety offset, so the region slices will be slightly bigger with each iteration. - if ($self->typed_slices) { - $_->merge_slices for @{$self->layers}; - $self->set_typed_slices(0); - $self->invalidate_step(STEP_PREPARE_INFILL); - } - - # compare each layer to the one below, and mark those slices needing - # one additional inner perimeter, like the top of domed objects- - - # this algorithm makes sure that at least one perimeter is overlapping - # but we don't generate any extra perimeter if fill density is zero, as they would be floating - # inside the object - infill_only_where_needed should be the method of choice for printing - # hollow objects - for my $region_id (0 .. ($self->print->region_count-1)) { - my $region = $self->print->regions->[$region_id]; - my $region_perimeters = $region->config->perimeters; - - next if !$region->config->extra_perimeters; - next if $region_perimeters == 0; - next if $region->config->fill_density == 0; - - for my $i (0 .. ($self->layer_count - 2)) { - my $layerm = $self->get_layer($i)->get_region($region_id); - my $upper_layerm = $self->get_layer($i+1)->get_region($region_id); - my $upper_layerm_polygons = [ map $_->p, @{$upper_layerm->slices} ]; - # Filter upper layer polygons in intersection_ppl by their bounding boxes? - # my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; - my $total_loop_length = sum(map $_->length, @$upper_layerm_polygons) // 0; - - my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; - my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER); - my $ext_perimeter_width = $ext_perimeter_flow->scaled_width; - my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing; - - foreach my $slice (@{$layerm->slices}) { - while (1) { - # compute the total thickness of perimeters - my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2 - + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing; - - # define a critical area where we don't want the upper slice to fall into - # (it should either lay over our perimeters or outside this area) - my $critical_area_depth = $perimeter_spacing*1.5; - my $critical_area = diff( - offset($slice->expolygon->arrayref, -$perimeters_thickness), - offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)), - ); - - # check whether a portion of the upper slices falls inside the critical area - my $intersection = intersection_ppl( - $upper_layerm_polygons, - $critical_area, - ); - - # only add an additional loop if at least 30% of the slice loop would benefit from it - my $total_intersection_length = sum(map $_->length, @$intersection) // 0; - last unless $total_intersection_length > $total_loop_length*0.3; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "extra.svg", - no_arrows => 1, - expolygons => union_ex($critical_area), - polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], - ); - } - - $slice->extra_perimeters($slice->extra_perimeters + 1); - } - Slic3r::debugf " adding %d more perimeter(s) at layer %d\n", - $slice->extra_perimeters, $layerm->layer->id - if $slice->extra_perimeters > 0; - } - } - } - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0 .. ($self->layer_count - 1) }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_perimeters; - } - }, - no_threads_cb => sub { - $_->make_perimeters for @{$self->layers}; - }, - ); - - # simplify slices (both layer and region slices), - # we only need the max resolution for perimeters - ### This makes this method not-idempotent, so we keep it disabled for now. - ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); - - $self->set_step_done(STEP_PERIMETERS); + $self->_make_perimeters; } sub prepare_infill { @@ -598,32 +495,7 @@ sub infill { # prerequisites $self->prepare_infill; - - return if $self->step_done(STEP_INFILL); - $self->set_step_started(STEP_INFILL); - $self->print->status_cb->(70, "Infilling layers"); - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0..$#{$self->layers} }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_fills; - } - }, - no_threads_cb => sub { - foreach my $layer (@{$self->layers}) { - $layer->make_fills; - } - }, - ); - - ### we could free memory now, but this would make this step not idempotent - ### Vojtech: Cannot release the fill_surfaces, they are used by the support generator. - ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; - - $self->set_step_done(STEP_INFILL); + $self->_infill; } sub generate_support_material { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 30b1f40cc..3b0020b08 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -2,9 +2,11 @@ #define slic3r_Print_hpp_ #include "libslic3r.h" +#include #include -#include #include +#include +#include #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" @@ -142,6 +144,8 @@ public: void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); + void _make_perimeters(); + void _infill(); private: Print* _print; @@ -151,7 +155,9 @@ private: // TODO: call model_object->get_bounding_box() instead of accepting // parameter PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); - ~PrintObject() {} + ~PrintObject(); + void _make_perimeters_do(std::queue* queue, boost::mutex* queue_mutex); + void _infill_do(std::queue* queue, boost::mutex* queue_mutex); }; typedef std::vector PrintObjectPtrs; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index d870c7fc3..742f55da4 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -910,4 +910,189 @@ PrintObject::bridge_over_infill() } } +void +PrintObject::_make_perimeters() +{ + if (this->state.is_done(posPerimeters)) return; + this->state.set_started(posPerimeters); + + // merge slices if they were split into types + if (this->typed_slices) { + FOREACH_LAYER(this, layer_it) + (*layer_it)->merge_slices(); + this->typed_slices = false; + this->state.invalidate(posPrepareInfill); + } + + // compare each layer to the one below, and mark those slices needing + // one additional inner perimeter, like the top of domed objects- + + // this algorithm makes sure that at least one perimeter is overlapping + // but we don't generate any extra perimeter if fill density is zero, as they would be floating + // inside the object - infill_only_where_needed should be the method of choice for printing + // hollow objects + FOREACH_REGION(this->_print, region_it) { + size_t region_id = region_it - this->_print->regions.begin(); + const PrintRegion ®ion = **region_it; + + + if (!region.config.extra_perimeters + || region.config.perimeters == 0 + || region.config.fill_density == 0) continue; + + for (size_t i = 0; i <= (this->layer_count()-2); ++i) { + LayerRegion &layerm = *this->get_layer(i)->get_region(region_id); + const LayerRegion &upper_layerm = *this->get_layer(i+1)->get_region(region_id); + const Polygons upper_layerm_polygons = upper_layerm.slices; + + // Filter upper layer polygons in intersection_ppl by their bounding boxes? + // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; + double total_loop_length = 0; + for (Polygons::const_iterator it = upper_layerm_polygons.begin(); it != upper_layerm_polygons.end(); ++it) + total_loop_length += it->length(); + + const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing(); + const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter); + const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); + const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); + + for (Surfaces::iterator slice = layerm.slices.surfaces.begin(); + slice != layerm.slices.surfaces.end(); ++slice) { + while (true) { + // compute the total thickness of perimeters + const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 + + (region.config.perimeters-1 + region.config.extra_perimeters) * perimeter_spacing; + + // define a critical area where we don't want the upper slice to fall into + // (it should either lay over our perimeters or outside this area) + const coord_t critical_area_depth = perimeter_spacing * 1.5; + const Polygons critical_area = diff( + offset(slice->expolygon, -perimeters_thickness), + offset(slice->expolygon, -(perimeters_thickness + critical_area_depth)) + ); + + // check whether a portion of the upper slices falls inside the critical area + const Polylines intersection = intersection_pl( + upper_layerm_polygons, + critical_area + ); + + // only add an additional loop if at least 30% of the slice loop would benefit from it + { + double total_intersection_length = 0; + for (Polylines::const_iterator it = intersection.begin(); it != intersection.end(); ++it) + total_intersection_length += it->length(); + if (total_intersection_length <= total_loop_length*0.3) break; + } + + /* + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "extra.svg", + no_arrows => 1, + expolygons => union_ex($critical_area), + polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], + ); + } + */ + + slice->extra_perimeters++; + } + + #ifdef DEBUG + if (slice->extra_perimeters > 0) + printf(" adding %d more perimeter(s) at layer %zu\n", slice->extra_perimeters, layer->id(); + #endif + } + } + } + + { + // queue all the layer numbers + std::queue queue; + boost::mutex queue_mutex; + for (size_t i = 0; i < this->layer_count(); ++i) + queue.push(i); + + boost::thread_group workers; + for (int i = 0; i < this->_print->config.threads; i++) + workers.add_thread(new boost::thread(&Slic3r::PrintObject::_make_perimeters_do, this, &queue, &queue_mutex)); + workers.join_all(); + } + + /* + simplify slices (both layer and region slices), + we only need the max resolution for perimeters + ### This makes this method not-idempotent, so we keep it disabled for now. + ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); + */ + + this->state.set_done(posPerimeters); +} + +void +PrintObject::_make_perimeters_do(std::queue* queue, boost::mutex* queue_mutex) +{ + //std::cout << "THREAD STARTED: " << boost::this_thread::get_id() << std::endl; + + while (true) { + size_t layer_id; + { + boost::lock_guard l(*queue_mutex); + if (queue->empty()) return; + layer_id = queue->front(); + queue->pop(); + } + //std::cout << " Layer " << layer_id << " (" << boost::this_thread::get_id() << ")" << std::endl; + this->get_layer(layer_id)->make_perimeters(); + boost::this_thread::interruption_point(); + } +} + +void +PrintObject::_infill() +{ + if (this->state.is_done(posInfill)) return; + this->state.set_started(posInfill); + + { + // queue all the layer numbers + std::queue queue; + boost::mutex queue_mutex; + for (size_t i = 0; i < this->layer_count(); ++i) + queue.push(i); + + boost::thread_group workers; + for (int i = 0; i < this->_print->config.threads; i++) + workers.add_thread(new boost::thread(&Slic3r::PrintObject::_infill_do, this, &queue, &queue_mutex)); + workers.join_all(); + } + + /* we could free memory now, but this would make this step not idempotent + ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; + */ + + this->state.set_done(posInfill); +} + +void +PrintObject::_infill_do(std::queue* queue, boost::mutex* queue_mutex) +{ + //std::cout << "THREAD STARTED: " << boost::this_thread::get_id() << std::endl; + + while (true) { + size_t layer_id; + { + boost::lock_guard l(*queue_mutex); + if (queue->empty()) return; + layer_id = queue->front(); + queue->pop(); + } + //std::cout << " Layer " << layer_id << " (" << boost::this_thread::get_id() << ")" << std::endl; + this->get_layer(layer_id)->make_fills(); + boost::this_thread::interruption_point(); + } +} + } diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index ad90eb32e..d36134839 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -111,6 +111,8 @@ _constant() void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); + void _make_perimeters(); + void _infill(); int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %};