From c8a48b4527b853323b60e3e55da12fcc6a8bc162 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 7 Jan 2014 15:40:38 +0100 Subject: [PATCH] Implement modifier volumes and port _merge_loops() to XS --- lib/Slic3r/Layer/Region.pm | 67 +----------------------- lib/Slic3r/Model.pm | 10 ++-- lib/Slic3r/Print.pm | 3 +- lib/Slic3r/Print/Object.pm | 104 ++++++++++++++++++++++++++++--------- xs/src/ClipperUtils.hpp | 4 +- xs/src/Polygon.cpp | 7 +++ xs/src/Polygon.hpp | 1 + xs/src/TriangleMesh.cpp | 91 ++++++++++++++++++++++++++++++-- xs/src/TriangleMesh.hpp | 3 +- xs/xsp/TriangleMesh.xsp | 14 ++--- 10 files changed, 195 insertions(+), 109 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 53b3834d37..7172072752 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -58,69 +58,6 @@ sub flow { ); } -# build polylines from lines -sub make_surfaces { - my $self = shift; - my ($loops) = @_; - - return if !@$loops; - $self->slices->clear; - $self->slices->append($self->_merge_loops($loops)); - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("surfaces.svg", - #polylines => $loops, - red_polylines => [ grep $_->is_counter_clockwise, @$loops ], - green_polylines => [ grep !$_->is_counter_clockwise, @$loops ], - expolygons => [ map $_->expolygon, @{$self->slices} ], - ); - } -} - -sub _merge_loops { - my ($self, $loops, $safety_offset) = @_; - - # Input loops are not suitable for evenodd nor nonzero fill types, as we might get - # two consecutive concentric loops having the same winding order - and we have to - # respect such order. In that case, evenodd would create wrong inversions, and nonzero - # would ignore holes inside two concentric contours. - # So we're ordering loops and collapse consecutive concentric loops having the same - # winding order. - # TODO: find a faster algorithm for this, maybe with some sort of binary search. - # If we computed a "nesting tree" we could also just remove the consecutive loops - # having the same winding order, and remove the extra one(s) so that we could just - # supply everything to offset_ex() instead of performing several union/diff calls. - - # we sort by area assuming that the outermost loops have larger area; - # the previous sorting method, based on $b->contains_point($a->[0]), failed to nest - # loops correctly in some edge cases when original model had overlapping facets - my @abs_area = map abs($_), my @area = map $_->area, @$loops; - my @sorted = sort { $abs_area[$b] <=> $abs_area[$a] } 0..$#$loops; # outer first - - # we don't perform a safety offset now because it might reverse cw loops - my $slices = []; - for my $i (@sorted) { - # we rely on the already computed area to determine the winding order - # of the loops, since the Orientation() function provided by Clipper - # would do the same, thus repeating the calculation - $slices = ($area[$i] >= 0) - ? [ $loops->[$i], @$slices ] - : diff($slices, [$loops->[$i]]); - } - - # perform a safety offset to merge very close facets (TODO: find test case for this) - $safety_offset //= scale 0.0499; - $slices = offset2_ex($slices, +$safety_offset, -$safety_offset); - - Slic3r::debugf "Layer %d (slice_z = %.2f, print_z = %.2f): %d surface(s) having %d holes detected from %d polylines\n", - $self->id, $self->slice_z, $self->print_z, - scalar(@$slices), scalar(map @{$_->holes}, @$slices), scalar(@$loops) - if $Slic3r::debug; - - return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$slices; -} - sub make_perimeters { my $self = shift; @@ -318,8 +255,8 @@ sub _fill_gaps { $filler->angle($self->config->fill_angle); $filler->layer_id($self->layer->id); - # we should probably use this code to handle thin walls and remove that logic from - # make_surfaces(), but we need to enable dynamic extrusion width before as we can't + # we should probably use this code to handle thin walls + # but we need to enable dynamic extrusion width before as we can't # use zigzag for thin walls. # medial axis-based gap fill should benefit from detection of larger gaps too, so diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 1c0f542502..31b55c9d92 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -50,6 +50,7 @@ sub add_object { $new_object->add_volume( material_id => $volume->material_id, mesh => $volume->mesh->clone, + modifier => $volume->modifier, ); if (defined $volume->material_id) { @@ -361,7 +362,7 @@ sub raw_mesh { my $self = shift; my $mesh = Slic3r::TriangleMesh->new; - $mesh->merge($_->mesh) for @{ $self->volumes }; + $mesh->merge($_->mesh) for grep !$_->modifier, @{ $self->volumes }; return $mesh; } @@ -458,12 +459,12 @@ sub unique_materials { sub facets_count { my $self = shift; - return sum(map $_->mesh->facets_count, @{$self->volumes}); + return sum(map $_->mesh->facets_count, grep !$_->modifier, @{$self->volumes}); } sub needed_repair { my $self = shift; - return (first { !$_->mesh->needed_repair } @{$self->volumes}) ? 0 : 1; + return (first { !$_->mesh->needed_repair } grep !$_->modifier, @{$self->volumes}) ? 0 : 1; } sub mesh_stats { @@ -494,7 +495,7 @@ sub print_info { printf " needed repair: no\n"; } } else { - printf " number of facets: %d\n", scalar(map @{$_->facets}, @{$self->volumes}); + printf " number of facets: %d\n", scalar(map @{$_->facets}, grep !$_->modifier, @{$self->volumes}); } } @@ -504,6 +505,7 @@ use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); has 'material_id' => (is => 'rw'); has 'mesh' => (is => 'rw', required => 1); +has 'modifier' => (is => 'rw', defualt => sub { 0 }); package Slic3r::Model::Instance; use Moo; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c791bf8e45..4df77cae28 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -528,8 +528,7 @@ sub export_svg { my $self = shift; my %params = @_; - # this shouldn't be needed, but we're currently relying on ->make_surfaces() which - # calls ->perimeter_flow + # is this needed? $self->init_extruders; $_->slice for @{$self->objects}; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 2fee618ac0..4b1738723a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -178,35 +178,57 @@ sub slice { $layer->region($_) for 0 .. ($regions_count-1); } - # process facets + # get array of Z coordinates for slicing + my @z = map $_->slice_z, @{$self->layers}; + + # slice all non-modifier volumes for my $region_id (0..$#{$self->region_volumes}) { - next if !defined $self->region_volumes->[$region_id]; - - # compose mesh - my $mesh; - foreach my $volume_id (@{$self->region_volumes->[$region_id]}) { - if (defined $mesh) { - $mesh->merge($self->model_object->volumes->[$volume_id]->mesh); - } else { - $mesh = $self->model_object->volumes->[$volume_id]->mesh->clone; + my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 0); + for my $layer_id (0..$#$expolygons_by_layer) { + my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; + $layerm->slices->clear; + foreach my $expolygon (@{ $expolygons_by_layer->[$layer_id] }) { + $layerm->slices->append(Slic3r::Surface->new( + expolygon => $expolygon, + surface_type => S_TYPE_INTERNAL, + )); } } - - # transform mesh - # we ignore the per-instance transformations currently and only - # consider the first one - $self->model_object->instances->[0]->transform_mesh($mesh, 1); - - # align mesh to Z = 0 and apply XY shift - $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); - - { - my $loops = $mesh->slice([ map $_->slice_z, @{$self->layers} ]); - for my $layer_id (0..$#$loops) { - my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; - $layerm->make_surfaces($loops->[$layer_id]); + } + + # then slice all modifier volumes + if (@{$self->region_volumes} > 1) { + for my $region_id (0..$#{$self->region_volumes}) { + my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 1); + + # loop through the other regions and 'steal' the slices belonging to this one + for my $other_region_id (0..$#{$self->region_volumes}) { + next if $other_region_id == $region_id; + + for my $layer_id (0..$#$expolygons_by_layer) { + my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; + my $other_layerm = $self->layers->[$layer_id]->regions->[$other_region_id]; + + my $other_slices = [ map $_->p, @{$other_layerm->slices} ]; # Polygons + my $my_parts = intersection_ex( + $other_slices, + [ map @$_, @{ $expolygons_by_layer->[$layer_id] } ], + ); + next if !@$my_parts; + + # append new parts to our region + foreach my $expolygon (@$my_parts) { + $layerm->slices->append(Slic3r::Surface->new( + expolygon => $expolygon, + surface_type => S_TYPE_INTERNAL, + )); + } + + # remove such parts from original region + $other_layerm->slices->clear; + $other_layerm->append($_) for @{ diff($other_slices, $my_parts) }; + } } - # TODO: read slicing_errors } } @@ -285,6 +307,38 @@ sub slice { } } +sub _slice_region { + my ($self, $region_id, $z, $modifier) = @_; + + return [] if !defined $self->region_volumes->[$region_id]; + + # compose mesh + my $mesh; + foreach my $volume_id (@{$self->region_volumes->[$region_id]}) { + my $volume = $self->model_object->volumes->[$volume_id]; + next if $volume->modifier && !$modifier; + next if !$volume->modifier && $modifier; + + if (defined $mesh) { + $mesh->merge($volume->mesh); + } else { + $mesh = $volume->mesh->clone; + } + } + next if !defined $mesh; + + # transform mesh + # we ignore the per-instance transformations currently and only + # consider the first one + $self->model_object->instances->[0]->transform_mesh($mesh, 1); + + # align mesh to Z = 0 and apply XY shift + $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); + + # perform actual slicing + return $mesh->slice($z); +} + sub make_perimeters { my $self = shift; diff --git a/xs/src/ClipperUtils.hpp b/xs/src/ClipperUtils.hpp index e5e80c9422..4bd62c6c79 100644 --- a/xs/src/ClipperUtils.hpp +++ b/xs/src/ClipperUtils.hpp @@ -79,12 +79,12 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); template -void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_); +void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false); void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); template -void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_); +void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, T &retval, bool safety_offset_ = false); void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp index b21b458f4d..0fae5799df 100644 --- a/xs/src/Polygon.cpp +++ b/xs/src/Polygon.cpp @@ -5,6 +5,13 @@ namespace Slic3r { +Polygon::operator Polygons() const +{ + Polygons pp; + pp.push_back(*this); + return pp; +} + Point* Polygon::last_point() const { diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index c630098ac3..e4e9dab92c 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -14,6 +14,7 @@ typedef std::vector Polygons; class Polygon : public MultiPoint { public: + operator Polygons() const; Point* last_point() const; Lines lines() const; Polyline* split_at(const Point* point) const; diff --git a/xs/src/TriangleMesh.cpp b/xs/src/TriangleMesh.cpp index ff8108b675..c5f590da7d 100644 --- a/xs/src/TriangleMesh.cpp +++ b/xs/src/TriangleMesh.cpp @@ -1,6 +1,7 @@ #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include #include #include #include @@ -164,7 +165,7 @@ void TriangleMesh::rotate(double angle, Point* center) } void -TriangleMesh::slice(const std::vector &z, std::vector &layers) +TriangleMesh::slice(const std::vector &z, std::vector* layers) { /* This method gets called with a list of unscaled Z coordinates and outputs @@ -385,7 +386,7 @@ TriangleMesh::slice(const std::vector &z, std::vector &layers) free(v_scaled_shared); // build loops - layers.resize(z.size()); + layers->resize(z.size()); for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { int layer_idx = it - lines.begin(); #ifdef SLIC3R_DEBUG @@ -478,7 +479,7 @@ TriangleMesh::slice(const std::vector &z, std::vector &layers) for (IntersectionLinePtrs::iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { p.points.push_back((*lineptr)->a); } - layers[layer_idx].push_back(p); + (*layers)[layer_idx].push_back(p); #ifdef SLIC3R_DEBUG printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); @@ -505,6 +506,90 @@ TriangleMesh::slice(const std::vector &z, std::vector &layers) } } } + +class _area_comp { + public: + _area_comp(std::vector* _aa) : abs_area(_aa) {}; + bool operator() (const size_t &a, const size_t &b) { + return (*this->abs_area)[a] > (*this->abs_area)[b]; + } + + private: + std::vector* abs_area; +}; + +void +TriangleMesh::slice(const std::vector &z, std::vector* layers) +{ + std::vector layers_p; + this->slice(z, &layers_p); + + /* + Input loops are not suitable for evenodd nor nonzero fill types, as we might get + two consecutive concentric loops having the same winding order - and we have to + respect such order. In that case, evenodd would create wrong inversions, and nonzero + would ignore holes inside two concentric contours. + So we're ordering loops and collapse consecutive concentric loops having the same + winding order. + TODO: find a faster algorithm for this, maybe with some sort of binary search. + If we computed a "nesting tree" we could also just remove the consecutive loops + having the same winding order, and remove the extra one(s) so that we could just + supply everything to offset_ex() instead of performing several union/diff calls. + + we sort by area assuming that the outermost loops have larger area; + the previous sorting method, based on $b->contains_point($a->[0]), failed to nest + loops correctly in some edge cases when original model had overlapping facets + */ + + layers->resize(z.size()); + + for (std::vector::const_iterator loops = layers_p.begin(); loops != layers_p.end(); ++loops) { + size_t layer_id = loops - layers_p.begin(); + + std::vector area; + std::vector abs_area; + std::vector sorted_area; // vector of indices + for (Polygons::const_iterator loop = loops->begin(); loop != loops->end(); ++loop) { + double a = loop->area(); + area.push_back(a); + abs_area.push_back(std::fabs(a)); + sorted_area.push_back(loop - loops->begin()); + } + + std::sort(sorted_area.begin(), sorted_area.end(), _area_comp(&abs_area)); // outer first + + // we don't perform a safety offset now because it might reverse cw loops + Polygons slices; + for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++loop_idx) { + /* we rely on the already computed area to determine the winding order + of the loops, since the Orientation() function provided by Clipper + would do the same, thus repeating the calculation */ + Polygons::const_iterator loop = loops->begin() + *loop_idx; + if (area[*loop_idx] >= 0) { + slices.push_back(*loop); + } else { + diff(slices, *loop, slices); + } + } + + // perform a safety offset to merge very close facets (TODO: find test case for this) + double safety_offset = scale_(0.0499); + ExPolygons ex_slices; + offset2_ex(slices, ex_slices, +safety_offset, -safety_offset); + + #ifdef SLIC3R_DEBUG + size_t holes_count = 0; + for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++e) { + holes_count += e->holes.count(); + } + printf("Layer %d (slice_z = %.2f): %d surface(s) having %d holes detected from %d polylines\n", + layer_id, z[layer_id], ex_slices.count(), holes_count, loops->count()); + #endif + + ExPolygons* layer = &(*layers)[layer_id]; + layer->insert(layer->end(), ex_slices.begin(), ex_slices.end()); + } +} TriangleMeshPtrs TriangleMesh::split() const diff --git a/xs/src/TriangleMesh.hpp b/xs/src/TriangleMesh.hpp index b151dcc6e3..0558177391 100644 --- a/xs/src/TriangleMesh.hpp +++ b/xs/src/TriangleMesh.hpp @@ -30,7 +30,8 @@ class TriangleMesh void translate(float x, float y, float z); void align_to_origin(); void rotate(double angle, Point* center); - void slice(const std::vector &z, std::vector &layers); + void slice(const std::vector &z, std::vector* layers); + void slice(const std::vector &z, std::vector* layers); TriangleMeshPtrs split() const; void merge(const TriangleMesh* mesh); void horizontal_projection(ExPolygons &retval) const; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index c27751891c..f026390c40 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -137,19 +137,19 @@ SV* TriangleMesh::slice(z) std::vector* z CODE: - std::vector layers; - THIS->slice(*z, layers); + std::vector layers; + THIS->slice(*z, &layers); AV* layers_av = newAV(); av_extend(layers_av, layers.size()-1); for (unsigned int i = 0; i < layers.size(); i++) { - AV* polygons_av = newAV(); - av_extend(polygons_av, layers[i].size()-1); + AV* expolygons_av = newAV(); + av_extend(expolygons_av, layers[i].size()-1); unsigned int j = 0; - for (Polygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { - av_store(polygons_av, j++, (*it).to_SV_clone_ref()); + for (ExPolygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { + av_store(expolygons_av, j++, (*it).to_SV_clone_ref()); } - av_store(layers_av, i, newRV_noinc((SV*)polygons_av)); + av_store(layers_av, i, newRV_noinc((SV*)expolygons_av)); } RETVAL = (SV*)newRV_noinc((SV*)layers_av); OUTPUT: