Implement modifier volumes and port _merge_loops() to XS

evgthin
Alessandro Ranellucci 2014-01-07 15:40:38 +01:00
parent b17d06f9d1
commit c8a48b4527
10 changed files with 195 additions and 109 deletions

View File

@ -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

View File

@ -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;

View File

@ -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};

View File

@ -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;

View File

@ -79,12 +79,12 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject,
const Slic3r::Polygons &clip, Slic3r::Polylines &retval);
template <class T>
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 <class T>
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);

View File

@ -5,6 +5,13 @@
namespace Slic3r {
Polygon::operator Polygons() const
{
Polygons pp;
pp.push_back(*this);
return pp;
}
Point*
Polygon::last_point() const
{

View File

@ -14,6 +14,7 @@ typedef std::vector<Polygon> Polygons;
class Polygon : public MultiPoint {
public:
operator Polygons() const;
Point* last_point() const;
Lines lines() const;
Polyline* split_at(const Point* point) const;

View File

@ -1,6 +1,7 @@
#include "TriangleMesh.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include <cmath>
#include <queue>
#include <deque>
#include <set>
@ -164,7 +165,7 @@ void TriangleMesh::rotate(double angle, Point* center)
}
void
TriangleMesh::slice(const std::vector<double> &z, std::vector<Polygons> &layers)
TriangleMesh::slice(const std::vector<double> &z, std::vector<Polygons>* layers)
{
/*
This method gets called with a list of unscaled Z coordinates and outputs
@ -385,7 +386,7 @@ TriangleMesh::slice(const std::vector<double> &z, std::vector<Polygons> &layers)
free(v_scaled_shared);
// build loops
layers.resize(z.size());
layers->resize(z.size());
for (std::vector<IntersectionLines>::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<double> &z, std::vector<Polygons> &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<double> &z, std::vector<Polygons> &layers)
}
}
}
class _area_comp {
public:
_area_comp(std::vector<double>* _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<double>* abs_area;
};
void
TriangleMesh::slice(const std::vector<double> &z, std::vector<ExPolygons>* layers)
{
std::vector<Polygons> 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<Polygons>::const_iterator loops = layers_p.begin(); loops != layers_p.end(); ++loops) {
size_t layer_id = loops - layers_p.begin();
std::vector<double> area;
std::vector<double> abs_area;
std::vector<size_t> 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<size_t>::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

View File

@ -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<double> &z, std::vector<Polygons> &layers);
void slice(const std::vector<double> &z, std::vector<Polygons>* layers);
void slice(const std::vector<double> &z, std::vector<ExPolygons>* layers);
TriangleMeshPtrs split() const;
void merge(const TriangleMesh* mesh);
void horizontal_projection(ExPolygons &retval) const;

View File

@ -137,19 +137,19 @@ SV*
TriangleMesh::slice(z)
std::vector<double>* z
CODE:
std::vector<Polygons> layers;
THIS->slice(*z, layers);
std::vector<ExPolygons> 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: